diff --git a/nacl/__init__.py b/nacl/__init__.py index 2dcf301c7ebbff9aeb40769984d5ffb1c74e0722..4107460e32739e454d63bfdd56f2269cf8e7bf66 100644 --- a/nacl/__init__.py +++ b/nacl/__init__.py @@ -4,10 +4,11 @@ from __future__ import division from . import __about__ from . import hash # pylint: disable=W0622 from . import signing +from .encoding import encoder from .random import random -__all__ = ["hash", "random"] + __about__.__all__ +__all__ = ["encoder", "hash", "random"] + __about__.__all__ # - Meta Information - diff --git a/nacl/encoding.py b/nacl/encoding.py new file mode 100644 index 0000000000000000000000000000000000000000..92db5ffd2cab0b2a2bbb0f1507e6026dbba732ba --- /dev/null +++ b/nacl/encoding.py @@ -0,0 +1,78 @@ +import base64 +import binascii + +from . import six + + +class Encoder(object): + + def __init__(self): + self._registry = {} + + def __getitem__(self, name): + if isinstance(name, six.string_types): + return self._registry[name] + return name + + def register(self, name, cls=None): + if cls is None: + def inner(cls): + self._registry[name] = cls() + return cls + return inner + else: + self._registry[name] = cls() + + +# Global encoder +encoder = Encoder() + + +@encoder.register("raw") +class RawEncoder(object): + + def encode(self, data): + return data + + def decode(self, data): + return data + + +@encoder.register("hex") +class HexEncoder(object): + + def encode(self, data): + return binascii.hexlify(data) + + def decode(self, data): + return binascii.unhexlify(data) + + +@encoder.register("base16") +class Base16Encoder(object): + + def encode(self, data): + return base64.b16encode(data) + + def decode(self, data): + return base64.b16decode(data) + + +@encoder.register("base32") +class Base32Encoder(object): + + def encode(self, data): + return base64.b32encode(data) + + def decode(self, data): + return base64.b32decode(data) + + +@encoder.register("base64") +class Base64Encoder(object): + + def encode(self, data): + return base64.b64encode(data) + + def decode(self, data): + return base64.b64decode(data) diff --git a/nacl/hash.py b/nacl/hash.py index c2ef41124708b8c56fd818e3f7fa4ef6a5877bd3..b8e80a568c04fcb02a496cd18e30d1cff7a3f49d 100644 --- a/nacl/hash.py +++ b/nacl/hash.py @@ -1,29 +1,24 @@ from __future__ import absolute_import from __future__ import division -import binascii - from . import nacl +from .encoding import encoder from .exceptions import CryptoError -def sha256(message, binary=False): +def sha256(message, encoding="hex"): digest = nacl.ffi.new("unsigned char[]", nacl.lib.crypto_hash_sha256_BYTES) if not nacl.lib.crypto_hash_sha256(digest, message, len(message)): raise CryptoError("Hashing failed") digest = nacl.ffi.buffer(digest, nacl.lib.crypto_hash_sha256_BYTES)[:] - if binary: - return digest - return binascii.hexlify(digest) + return encoder[encoding].encode(digest) -def sha512(message, binary=False): +def sha512(message, encoding="hex"): digest = nacl.ffi.new("unsigned char[]", nacl.lib.crypto_hash_sha512_BYTES) if not nacl.lib.crypto_hash_sha512(digest, message, len(message)): raise CryptoError("Hashing failed") digest = nacl.ffi.buffer(digest, nacl.lib.crypto_hash_sha512_BYTES)[:] - if binary: - return digest - return binascii.hexlify(digest) + return encoder[encoding].encode(digest) diff --git a/nacl/signing.py b/nacl/signing.py index 53a33112462be63ddfa3e6e899c493cb710a890d..b99001a29d9c3dc87f01f4b1189c0b142369ec5e 100644 --- a/nacl/signing.py +++ b/nacl/signing.py @@ -4,6 +4,7 @@ from __future__ import division from . import six from . import nacl +from .encoding import encoder from .exceptions import CryptoError from .random import random @@ -19,19 +20,26 @@ class SignedMessage(six.binary_type): A bytes subclass that holds a messaged that has been signed by a :class:`SigningKey`. """ + @classmethod + def _from_parts(cls, signature, message, combined): + obj = cls(combined) + obj._signature = signature + obj._message = message + return obj + @property def signature(self): """ The signature contained within the :class:`SignedMessage`. """ - return self[:nacl.lib.crypto_sign_BYTES] + return self._signature @property def message(self): """ The message contained within the :class:`SignedMessage`. """ - return self[nacl.lib.crypto_sign_BYTES:] + return self._message class VerifyKey(object): @@ -42,14 +50,17 @@ class VerifyKey(object): :param key: [:class:`bytes`] Serialized Ed25519 public key """ - def __init__(self, key): + def __init__(self, key, encoding="raw"): + # Decode the key + key = encoder[encoding].decode(key) + if len(key) != nacl.lib.crypto_sign_PUBLICKEYBYTES: raise ValueError("The key must be exactly %s bytes long" % nacl.lib.crypto_sign_PUBLICKEYBYTES) self._key = key - def verify(self, smessage, signature=None): + def verify(self, smessage, signature=None, encoding="raw"): """ Verifies the signature of a signed message, returning the message if it has not been tampered with else raising @@ -66,6 +77,9 @@ class VerifyKey(object): # them. smessage = signature + smessage + # Decode the signed message + smessage = encoder[encoding].decode(smessage) + message = nacl.ffi.new("unsigned char[]", len(smessage)) message_len = nacl.ffi.new("unsigned long long *") @@ -93,7 +107,10 @@ class SigningKey(object): (i.e. public) key that corresponds with this signing key. """ - def __init__(self, seed): + def __init__(self, seed, encoding="raw"): + # Decode the seed + seed = encoder[encoding].decode(seed) + # Verify that our seed is the proper size seed_size = nacl.lib.crypto_sign_SECRETKEYBYTES // 2 if len(seed) != seed_size: @@ -120,9 +137,11 @@ class SigningKey(object): :rtype: :class:`~nacl.signing.SigningKey` """ - return cls(random(nacl.lib.crypto_sign_SECRETKEYBYTES // 2)) + return cls(random(nacl.lib.crypto_sign_SECRETKEYBYTES // 2), + encoding="raw", + ) - def sign(self, message): + def sign(self, message, encoding="raw"): """ Sign a message using this key. @@ -135,4 +154,10 @@ class SigningKey(object): if not nacl.lib.crypto_sign(sm, smlen, message, len(message), self._signing_key): raise CryptoError("Failed to sign the message") - return SignedMessage(nacl.ffi.buffer(sm, smlen[0])[:]) + raw_signed = nacl.ffi.buffer(sm, smlen[0])[:] + + signature = encoder[encoding].encode(raw_signed[:nacl.lib.crypto_sign_BYTES]) + message = encoder[encoding].encode(raw_signed[nacl.lib.crypto_sign_BYTES:]) + signed = encoder[encoding].encode(raw_signed) + + return SignedMessage._from_parts(signature, message, signed) diff --git a/tests/test_hash.py b/tests/test_hash.py index 700c3bd041240d93d34158b6be9fa6a902cf1e1d..bc4ecea87ccad73f38f91c28e670453d9000114f 100644 --- a/tests/test_hash.py +++ b/tests/test_hash.py @@ -27,7 +27,7 @@ def test_sha256_hex(inp, expected): ) ]) def test_sha256_binary(inp, expected): - assert nacl.hash.sha256(inp, binary=True) == expected + assert nacl.hash.sha256(inp, encoding="raw") == expected @pytest.mark.parametrize(("inp", "expected"), [ @@ -55,4 +55,4 @@ def test_sha512_hex(inp, expected): ) ]) def test_sha512_binary(inp, expected): - assert nacl.hash.sha512(inp, binary=True) == expected + assert nacl.hash.sha512(inp, encoding="raw") == expected diff --git a/tests/test_signing.py b/tests/test_signing.py index fe57c970f15eb44e7fea66e00968578c9e259d84..3607c3fd166754c660f6a02f0a755e830c902656 100644 --- a/tests/test_signing.py +++ b/tests/test_signing.py @@ -45,12 +45,12 @@ class TestSigningKey: for x in ed25519_known_answers()] ) def test_message_signing(self, seed, message, signature, expected): - signing_key = nacl.signing.SigningKey(binascii.unhexlify(seed)) - signed = signing_key.sign(binascii.unhexlify(message)) + signing_key = nacl.signing.SigningKey(seed, encoding="hex") + signed = signing_key.sign(binascii.unhexlify(message), encoding="hex") - assert binascii.hexlify(signed) == expected - assert binascii.hexlify(signed.message) == message - assert binascii.hexlify(signed.signature) == signature + assert signed == expected + assert signed.message == message + assert signed.signature == signature class TestVerifyKey: @@ -59,13 +59,10 @@ class TestVerifyKey: [(x["public_key"], x["signed"], x["message"], x["signature"]) for x in ed25519_known_answers()] ) def test_valid_signed_message(self, public_key, signed, message, signature): - key = nacl.signing.VerifyKey(binascii.unhexlify(public_key)) - signedb = binascii.unhexlify(signed) - messageb = binascii.unhexlify(message) - signatureb = binascii.unhexlify(signature) + key = nacl.signing.VerifyKey(public_key, encoding="hex") - assert binascii.hexlify(key.verify(signedb)) == message - assert binascii.hexlify(key.verify(messageb, signatureb)) == message + assert binascii.hexlify(key.verify(signed, encoding="hex")) == message + assert binascii.hexlify(key.verify(message, signature, encoding="hex")) == message def test_invalid_signed_message(self): skey = nacl.signing.SigningKey.generate()