diff --git a/README.rst b/README.rst index 8e16300984d9af785d6aaaf0b1123258578e98e9..1eb40b5921fd63956aac78bd714bd7d74b5cef50 100644 --- a/README.rst +++ b/README.rst @@ -37,3 +37,14 @@ Features * Secret-key encryption * Public-key encryption * HMAC (coming soon) + + +Changes +------- + +* 0.3.0: the low-level API (`nacl.c.*`) has been changed to match the + upstream NaCl C/C++ conventions (as well as those of other NaCl bindings). + The order of arguments and return values has changed significantly. If you + have code which calls these functions (e.g. `nacl.c.crypto_box_keypair()`), + you must review the new docstrings and update your code to match the new + conventions. diff --git a/src/nacl/__init__.py b/src/nacl/__init__.py index 95d307725fea6e1f174a56a19dcb8bf8db08fc27..a6f7b59e8f84ee8177d09031ecba8d58ddf8cd3a 100644 --- a/src/nacl/__init__.py +++ b/src/nacl/__init__.py @@ -24,7 +24,7 @@ __summary__ = ("Python binding to the Networking and Cryptography (NaCl) " "library") __uri__ = "https://github.com/pyca/pynacl/" -__version__ = "0.2.3" +__version__ = "0.3.0" __author__ = "Donald Stufft" __email__ = "donald@stufft.io" diff --git a/src/nacl/c/crypto_box.py b/src/nacl/c/crypto_box.py index 069ff5c0a3cda089c3bc3ec1041bdce729d39296..c63fd0e10360dc8d1818df7ce1982d5314366a9a 100644 --- a/src/nacl/c/crypto_box.py +++ b/src/nacl/c/crypto_box.py @@ -30,41 +30,41 @@ crypto_box_BEFORENMBYTES = lib.crypto_box_beforenmbytes() def crypto_box_keypair(): """ - Returns a randomly generated secret and public key. + Returns a randomly generated public and secret key. - :rtype: (bytes(secret_key), bytes(public_key)) + :rtype: (bytes(public_key), bytes(secret_key)) """ - sk = lib.ffi.new("unsigned char[]", crypto_box_SECRETKEYBYTES) pk = lib.ffi.new("unsigned char[]", crypto_box_PUBLICKEYBYTES) + sk = lib.ffi.new("unsigned char[]", crypto_box_SECRETKEYBYTES) if lib.crypto_box_keypair(pk, sk) != 0: raise CryptoError("An error occurred trying to generate the keypair") return ( - lib.ffi.buffer(sk, crypto_box_SECRETKEYBYTES)[:], lib.ffi.buffer(pk, crypto_box_PUBLICKEYBYTES)[:], + lib.ffi.buffer(sk, crypto_box_SECRETKEYBYTES)[:], ) -def crypto_box(sk, pk, message, nonce): +def crypto_box(message, nonce, pk, sk): """ Encrypts and returns a message ``message`` using the secret key ``sk``, public key ``pk``, and the nonce ``nonce``. - :param sk: bytes - :param pk: bytes :param message: bytes :param nonce: bytes + :param pk: bytes + :param sk: bytes :rtype: bytes """ - if len(sk) != crypto_box_SECRETKEYBYTES: - raise ValueError("Invalid secret key") + if len(nonce) != crypto_box_NONCEBYTES: + raise ValueError("Invalid nonce size") if len(pk) != crypto_box_PUBLICKEYBYTES: raise ValueError("Invalid public key") - if len(nonce) != crypto_box_NONCEBYTES: - raise ValueError("Invalid nonce size") + if len(sk) != crypto_box_SECRETKEYBYTES: + raise ValueError("Invalid secret key") padded = (b"\x00" * crypto_box_ZEROBYTES) + message ciphertext = lib.ffi.new("unsigned char[]", len(padded)) @@ -75,25 +75,25 @@ def crypto_box(sk, pk, message, nonce): return lib.ffi.buffer(ciphertext, len(padded))[crypto_box_BOXZEROBYTES:] -def crypto_box_open(sk, pk, ciphertext, nonce): +def crypto_box_open(ciphertext, nonce, pk, sk): """ Decrypts and returns an encrypted message ``ciphertext``, using the secret key ``sk``, public key ``pk``, and the nonce ``nonce``. - :param sk: bytes - :param pk: bytes :param ciphertext: bytes :param nonce: bytes + :param pk: bytes + :param sk: bytes :rtype: bytes """ - if len(sk) != crypto_box_SECRETKEYBYTES: - raise ValueError("Invalid secret key") + if len(nonce) != crypto_box_NONCEBYTES: + raise ValueError("Invalid nonce size") if len(pk) != crypto_box_PUBLICKEYBYTES: raise ValueError("Invalid public key") - if len(nonce) != crypto_box_NONCEBYTES: - raise ValueError("Invalid nonce size") + if len(sk) != crypto_box_SECRETKEYBYTES: + raise ValueError("Invalid secret key") padded = (b"\x00" * crypto_box_BOXZEROBYTES) + ciphertext plaintext = lib.ffi.new("unsigned char[]", len(padded)) @@ -104,22 +104,22 @@ def crypto_box_open(sk, pk, ciphertext, nonce): return lib.ffi.buffer(plaintext, len(padded))[crypto_box_ZEROBYTES:] -def crypto_box_beforenm(sk, pk): +def crypto_box_beforenm(pk, sk): """ - Computes and returns the shared key for the secret key ``sk`` and the - public key ``pk``. This can be used to speed up operations where the same + Computes and returns the shared key for the public key ``pk`` and the + secret key ``sk``. This can be used to speed up operations where the same set of keys is going to be used multiple times. - :param sk: bytes :param pk: bytes + :param sk: bytes :rtype: bytes """ - if len(sk) != crypto_box_SECRETKEYBYTES: - raise ValueError("Invalid secret key") - if len(pk) != crypto_box_PUBLICKEYBYTES: raise ValueError("Invalid public key") + if len(sk) != crypto_box_SECRETKEYBYTES: + raise ValueError("Invalid secret key") + k = lib.ffi.new("unsigned char[]", crypto_box_BEFORENMBYTES) if lib.crypto_box_beforenm(k, pk, sk) != 0: @@ -128,22 +128,22 @@ def crypto_box_beforenm(sk, pk): return lib.ffi.buffer(k, crypto_box_BEFORENMBYTES)[:] -def crypto_box_afternm(k, message, nonce): +def crypto_box_afternm(message, nonce, k): """ Encrypts and returns the message ``message`` using the shared key ``k`` and the nonce ``nonce``. - :param k: bytes :param message: bytes :param nonce: bytes + :param k: bytes :rtype: bytes """ - if len(k) != crypto_box_BEFORENMBYTES: - raise ValueError("Invalid shared key") - if len(nonce) != crypto_box_NONCEBYTES: raise ValueError("Invalid nonce") + if len(k) != crypto_box_BEFORENMBYTES: + raise ValueError("Invalid shared key") + padded = b"\x00" * crypto_box_ZEROBYTES + message ciphertext = lib.ffi.new("unsigned char[]", len(padded)) @@ -153,22 +153,22 @@ def crypto_box_afternm(k, message, nonce): return lib.ffi.buffer(ciphertext, len(padded))[crypto_box_BOXZEROBYTES:] -def crypto_box_open_afternm(k, ciphertext, nonce): +def crypto_box_open_afternm(ciphertext, nonce, k): """ Decrypts and returns the encrypted message ``ciphertext``, using the shared key ``k`` and the nonce ``nonce``. - :param k: bytes :param ciphertext: bytes :param nonce: bytes + :param k: bytes :rtype: bytes """ - if len(k) != crypto_box_BEFORENMBYTES: - raise ValueError("Invalid shared key") - if len(nonce) != crypto_box_NONCEBYTES: raise ValueError("Invalid nonce") + if len(k) != crypto_box_BEFORENMBYTES: + raise ValueError("Invalid shared key") + padded = (b"\x00" * crypto_box_BOXZEROBYTES) + ciphertext plaintext = lib.ffi.new("unsigned char[]", len(padded)) diff --git a/src/nacl/c/crypto_secretbox.py b/src/nacl/c/crypto_secretbox.py index 94e6a66a35c2c0e78986571f4f070c4417de91af..e07caa9c4586e860bb18929d14f0548684d58f64 100644 --- a/src/nacl/c/crypto_secretbox.py +++ b/src/nacl/c/crypto_secretbox.py @@ -23,14 +23,14 @@ crypto_secretbox_ZEROBYTES = lib.crypto_secretbox_zerobytes() crypto_secretbox_BOXZEROBYTES = lib.crypto_secretbox_boxzerobytes() -def crypto_secretbox(key, message, nonce): +def crypto_secretbox(message, nonce, key): """ Encrypts and returns the message ``message`` with the secret ``key`` and the nonce ``nonce``. - :param key: bytes :param message: bytes :param nonce: bytes + :param key: bytes :rtype: bytes """ if len(key) != crypto_secretbox_KEYBYTES: @@ -49,14 +49,14 @@ def crypto_secretbox(key, message, nonce): return ciphertext[crypto_secretbox_BOXZEROBYTES:] -def crypto_secretbox_open(key, ciphertext, nonce): +def crypto_secretbox_open(ciphertext, nonce, key): """ Decrypt and returns the encrypted message ``ciphertext`` with the secret ``key`` and the nonce ``nonce``. - :param key: bytes :param ciphertext: bytes :param nonce: bytes + :param key: bytes :rtype: bytes """ if len(key) != crypto_secretbox_KEYBYTES: diff --git a/src/nacl/c/crypto_sign.py b/src/nacl/c/crypto_sign.py index 7286de062889e0743feecb5e4c730af91df49397..ba6a881e6d8d3b89538e4693b44274568b1ee5e9 100644 --- a/src/nacl/c/crypto_sign.py +++ b/src/nacl/c/crypto_sign.py @@ -26,51 +26,51 @@ crypto_sign_SECRETKEYBYTES = lib.crypto_sign_secretkeybytes() def crypto_sign_keypair(): """ - Returns a randomly generated secret key and public key. + Returns a randomly generated public key and secret key. - :rtype: (bytes(secret_key), bytes(public_key)) + :rtype: (bytes(public_key), bytes(secret_key)) """ - sk = lib.ffi.new("unsigned char[]", crypto_sign_SECRETKEYBYTES) pk = lib.ffi.new("unsigned char[]", crypto_sign_PUBLICKEYBYTES) + sk = lib.ffi.new("unsigned char[]", crypto_sign_SECRETKEYBYTES) if lib.crypto_sign_keypair(pk, sk) != 0: raise CryptoError("An error occurred while generating keypairs") return ( - lib.ffi.buffer(sk, crypto_sign_SECRETKEYBYTES)[:], lib.ffi.buffer(pk, crypto_sign_PUBLICKEYBYTES)[:], + lib.ffi.buffer(sk, crypto_sign_SECRETKEYBYTES)[:], ) def crypto_sign_seed_keypair(seed): """ - Computes and returns the secret key and public key using the seed ``seed``. + Computes and returns the public key and secret key using the seed ``seed``. :param seed: bytes - :rtype: (bytes(secret_key), bytes(public_key)) + :rtype: (bytes(public_key), bytes(secret_key)) """ if len(seed) != crypto_sign_SEEDBYTES: raise ValueError("Invalid seed") - sk = lib.ffi.new("unsigned char[]", crypto_sign_SECRETKEYBYTES) pk = lib.ffi.new("unsigned char[]", crypto_sign_PUBLICKEYBYTES) + sk = lib.ffi.new("unsigned char[]", crypto_sign_SECRETKEYBYTES) if lib.crypto_sign_seed_keypair(pk, sk, seed) != 0: raise CryptoError("An error occured while generating keypairs") return ( - lib.ffi.buffer(sk, crypto_sign_SECRETKEYBYTES)[:], lib.ffi.buffer(pk, crypto_sign_PUBLICKEYBYTES)[:], + lib.ffi.buffer(sk, crypto_sign_SECRETKEYBYTES)[:], ) -def crypto_sign(sk, message): +def crypto_sign(message, sk): """ Signs the message ``message`` using the secret key ``sk`` and returns the signed message. - :param sk: bytes :param message: bytes + :param sk: bytes :rtype: bytes """ signed = lib.ffi.new("unsigned char[]", len(message) + crypto_sign_BYTES) @@ -82,13 +82,13 @@ def crypto_sign(sk, message): return lib.ffi.buffer(signed, signed_len[0])[:] -def crypto_sign_open(pk, signed): +def crypto_sign_open(signed, pk): """ Verifies the signature of the signed message ``signed`` using the public key ``pkg`` and returns the unsigned message. - :param pk: bytes :param signed: bytes + :param pk: bytes :rtype: bytes """ message = lib.ffi.new("unsigned char[]", len(signed)) diff --git a/src/nacl/public.py b/src/nacl/public.py index 25cbb2122c522a16e9f403e63705a2843738fa36..32bda571f636098b52b1f183fc01e0fd10de6403 100644 --- a/src/nacl/public.py +++ b/src/nacl/public.py @@ -114,8 +114,8 @@ class Box(encoding.Encodable, StringFixer, object): def __init__(self, private_key, public_key): if private_key and public_key: self._shared_key = nacl.c.crypto_box_beforenm( - private_key.encode(encoder=encoding.RawEncoder), public_key.encode(encoder=encoding.RawEncoder), + private_key.encode(encoder=encoding.RawEncoder), ) else: self._shared_key = None @@ -152,9 +152,9 @@ class Box(encoding.Encodable, StringFixer, object): self.NONCE_SIZE) ciphertext = nacl.c.crypto_box_afternm( - self._shared_key, plaintext, nonce, + self._shared_key, ) encoded_nonce = encoder.encode(nonce) @@ -190,9 +190,9 @@ class Box(encoding.Encodable, StringFixer, object): self.NONCE_SIZE) plaintext = nacl.c.crypto_box_open_afternm( - self._shared_key, ciphertext, nonce, + self._shared_key, ) return plaintext diff --git a/src/nacl/secret.py b/src/nacl/secret.py index 50107d5070296b15eccf3d6d07b5ec2c5604205f..6ae85876133c5cc1d47144d336ea0d211fc388b5 100644 --- a/src/nacl/secret.py +++ b/src/nacl/secret.py @@ -78,7 +78,7 @@ class SecretBox(encoding.Encodable, StringFixer, object): "The nonce must be exactly %s bytes long" % self.NONCE_SIZE, ) - ciphertext = nacl.c.crypto_secretbox(self._key, plaintext, nonce) + ciphertext = nacl.c.crypto_secretbox(plaintext, nonce, self._key) encoded_nonce = encoder.encode(nonce) encoded_ciphertext = encoder.encode(ciphertext) @@ -113,6 +113,6 @@ class SecretBox(encoding.Encodable, StringFixer, object): "The nonce must be exactly %s bytes long" % self.NONCE_SIZE, ) - plaintext = nacl.c.crypto_secretbox_open(self._key, ciphertext, nonce) + plaintext = nacl.c.crypto_secretbox_open(ciphertext, nonce, self._key) return plaintext diff --git a/src/nacl/signing.py b/src/nacl/signing.py index ddc027b43146e62c187868ebc9725d1205ec6304..e9f05deec3bc32057a722997244c45ac6b0488f6 100644 --- a/src/nacl/signing.py +++ b/src/nacl/signing.py @@ -96,7 +96,7 @@ class VerifyKey(encoding.Encodable, StringFixer, object): # Decode the signed message smessage = encoder.decode(smessage) - return nacl.c.crypto_sign_open(self._key, smessage) + return nacl.c.crypto_sign_open(smessage, self._key) class SigningKey(encoding.Encodable, StringFixer, object): @@ -129,7 +129,7 @@ class SigningKey(encoding.Encodable, StringFixer, object): nacl.c.crypto_sign_SEEDBYTES ) - secret_key, public_key = nacl.c.crypto_sign_seed_keypair(seed) + public_key, secret_key = nacl.c.crypto_sign_seed_keypair(seed) self._seed = seed self._signing_key = secret_key @@ -158,7 +158,7 @@ class SigningKey(encoding.Encodable, StringFixer, object): :param encoder: A class that is used to encode the signed message. :rtype: :class:`~nacl.signing.SignedMessage` """ - raw_signed = nacl.c.crypto_sign(self._signing_key, message) + raw_signed = nacl.c.crypto_sign(message, self._signing_key) signature = encoder.encode(raw_signed[:nacl.c.crypto_sign_BYTES]) message = encoder.encode(raw_signed[nacl.c.crypto_sign_BYTES:]) diff --git a/tests/test_raw.py b/tests/test_raw.py new file mode 100644 index 0000000000000000000000000000000000000000..a3a0ed4f75c6fa2591cc87d6c21d87872fe5b7d4 --- /dev/null +++ b/tests/test_raw.py @@ -0,0 +1,113 @@ +# Copyright 2013 Donald Stufft and individual contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from binascii import hexlify +from nacl import c +import hashlib + + +def tohex(b): + return hexlify(b).decode("ascii") + + +def test_hash(): + msg = b"message" + h1 = c.crypto_hash(msg) + assert len(h1) == c.crypto_hash_BYTES + assert tohex(h1) == ("f8daf57a3347cc4d6b9d575b31fe6077" + "e2cb487f60a96233c08cb479dbf31538" + "cc915ec6d48bdbaa96ddc1a16db4f4f9" + "6f37276cfcb3510b8246241770d5952c") + assert tohex(h1) == hashlib.sha512(msg).hexdigest() + + h2 = c.crypto_hash_sha512(msg) + assert len(h2) == c.crypto_hash_sha512_BYTES + assert tohex(h2) == tohex(h1) + + h3 = c.crypto_hash_sha256(msg) + assert len(h3) == c.crypto_hash_sha256_BYTES + assert tohex(h3) == ("ab530a13e45914982b79f9b7e3fba994" + "cfd1f3fb22f71cea1afbf02b460c6d1d") + assert tohex(h3) == hashlib.sha256(msg).hexdigest() + + +def test_secretbox(): + key = b"\x00" * c.crypto_secretbox_KEYBYTES + msg = b"message" + nonce = b"\x01" * c.crypto_secretbox_NONCEBYTES + ct = c.crypto_secretbox(msg, nonce, key) + assert len(ct) == len(msg) + c.crypto_secretbox_BOXZEROBYTES + assert tohex(ct) == "3ae84dfb89728737bd6e2c8cacbaf8af3d34cc1666533a" + msg2 = c.crypto_secretbox_open(ct, nonce, key) + assert msg2 == msg + + +def test_box(): + A_pubkey, A_secretkey = c.crypto_box_keypair() + assert len(A_secretkey) == c.crypto_box_SECRETKEYBYTES + assert len(A_pubkey) == c.crypto_box_PUBLICKEYBYTES + B_pubkey, B_secretkey = c.crypto_box_keypair() + + k1 = c.crypto_box_beforenm(B_pubkey, A_secretkey) + assert len(k1) == c.crypto_box_BEFORENMBYTES + k2 = c.crypto_box_beforenm(A_pubkey, B_secretkey) + assert tohex(k1) == tohex(k2) + + message = b"message" + nonce = b"\x01" * c.crypto_box_NONCEBYTES + ct1 = c.crypto_box_afternm(message, nonce, k1) + assert len(ct1) == len(message) + c.crypto_box_BOXZEROBYTES + + ct2 = c.crypto_box(message, nonce, B_pubkey, A_secretkey) + assert tohex(ct2) == tohex(ct1) + + m1 = c.crypto_box_open(ct1, nonce, A_pubkey, B_secretkey) + assert m1 == message + + m2 = c.crypto_box_open_afternm(ct1, nonce, k1) + assert m2 == message + + +def test_sign(): + seed = b"\x00" * c.crypto_sign_SEEDBYTES + pubkey, secretkey = c.crypto_sign_seed_keypair(seed) + assert len(pubkey) == c.crypto_sign_PUBLICKEYBYTES + assert len(secretkey) == c.crypto_sign_SECRETKEYBYTES + + pubkey, secretkey = c.crypto_sign_keypair() + assert len(pubkey) == c.crypto_sign_PUBLICKEYBYTES + assert len(secretkey) == c.crypto_sign_SECRETKEYBYTES + + msg = b"message" + sigmsg = c.crypto_sign(msg, secretkey) + assert len(sigmsg) == len(msg) + c.crypto_sign_BYTES + + msg2 = c.crypto_sign_open(sigmsg, pubkey) + assert msg2 == msg + + +def secret_scalar(): + pubkey, secretkey = c.crypto_box_keypair() + assert len(secretkey) == c.crypto_box_SECRETKEYBYTES + assert c.crypto_box_SECRETKEYBYTES == c.crypto_scalarmult_BYTES + return secretkey, pubkey + + +def test_scalarmult(): + x, xpub = secret_scalar() + assert len(x) == 32 + y, ypub = secret_scalar() + + bx = c.crypto_scalarmult_base(x) + assert tohex(bx) == tohex(xpub)