diff --git a/src/bindings/crypto_box.h b/src/bindings/crypto_box.h index a8d425e0..bfe26543 100644 --- a/src/bindings/crypto_box.h +++ b/src/bindings/crypto_box.h @@ -21,6 +21,7 @@ size_t crypto_box_boxzerobytes(); size_t crypto_box_noncebytes(); size_t crypto_box_beforenmbytes(); size_t crypto_box_sealbytes(); +size_t crypto_box_macbytes(); int crypto_box_keypair(unsigned char *pk, unsigned char *sk); @@ -53,3 +54,19 @@ int crypto_box_seal(unsigned char *c, const unsigned char *m, int crypto_box_seal_open(unsigned char *m, const unsigned char *c, unsigned long long clen, const unsigned char *pk, const unsigned char *sk); + +int crypto_box_easy_afternm(unsigned char *c, const unsigned char *m, + unsigned long long mlen, const unsigned char *n, + const unsigned char *k); + +int crypto_box_easy(unsigned char *c, const unsigned char *m, + unsigned long long mlen, const unsigned char *n, + const unsigned char *pk, const unsigned char *sk); + +int crypto_box_open_easy_afternm(unsigned char *m, const unsigned char *c, + unsigned long long clen, const unsigned char *n, + const unsigned char *k); + +int crypto_box_open_easy(unsigned char *m, const unsigned char *c, + unsigned long long clen, const unsigned char *n, + const unsigned char *pk, const unsigned char *sk); diff --git a/src/bindings/crypto_secretbox.h b/src/bindings/crypto_secretbox.h index d3666a2d..aa6ce8fa 100644 --- a/src/bindings/crypto_secretbox.h +++ b/src/bindings/crypto_secretbox.h @@ -28,3 +28,11 @@ int crypto_secretbox(unsigned char *c, const unsigned char *m, int crypto_secretbox_open(unsigned char *m, const unsigned char *c, unsigned long long clen, const unsigned char *n, const unsigned char *k); + +int crypto_secretbox_easy(unsigned char *c, const unsigned char *m, + unsigned long long mlen, const unsigned char *n, + const unsigned char *k); + +int crypto_secretbox_open_easy(unsigned char *m, const unsigned char *c, + unsigned long long clen, const unsigned char *n, + const unsigned char *k); diff --git a/src/nacl/bindings/__init__.py b/src/nacl/bindings/__init__.py index aebf4aa0..00b2ffd7 100644 --- a/src/nacl/bindings/__init__.py +++ b/src/nacl/bindings/__init__.py @@ -48,9 +48,13 @@ crypto_box_ZEROBYTES, crypto_box_afternm, crypto_box_beforenm, + crypto_box_easy, + crypto_box_easy_afternm, crypto_box_keypair, crypto_box_open, crypto_box_open_afternm, + crypto_box_open_easy, + crypto_box_open_easy_afternm, crypto_box_seal, crypto_box_seal_open, crypto_box_seed_keypair, @@ -182,7 +186,9 @@ crypto_secretbox_MESSAGEBYTES_MAX, crypto_secretbox_NONCEBYTES, crypto_secretbox_ZEROBYTES, + crypto_secretbox_easy, crypto_secretbox_open, + crypto_secretbox_open_easy, ) from nacl.bindings.crypto_secretstream import ( crypto_secretstream_xchacha20poly1305_ABYTES, @@ -279,6 +285,10 @@ "crypto_box_beforenm", "crypto_box_afternm", "crypto_box_open_afternm", + "crypto_box_easy", + "crypto_box_easy_afternm", + "crypto_box_open_easy", + "crypto_box_open_easy_afternm", "crypto_box_seal", "crypto_box_seal_open", "crypto_box_seed_keypair", @@ -343,7 +353,9 @@ "crypto_secretbox_MACBYTES", "crypto_secretbox_MESSAGEBYTES_MAX", "crypto_secretbox", + "crypto_secretbox_easy", "crypto_secretbox_open", + "crypto_secretbox_open_easy", "crypto_secretstream_xchacha20poly1305_ABYTES", "crypto_secretstream_xchacha20poly1305_HEADERBYTES", "crypto_secretstream_xchacha20poly1305_KEYBYTES", diff --git a/src/nacl/bindings/crypto_box.py b/src/nacl/bindings/crypto_box.py index 8d1c831b..da6e4cb4 100644 --- a/src/nacl/bindings/crypto_box.py +++ b/src/nacl/bindings/crypto_box.py @@ -29,6 +29,7 @@ crypto_box_BOXZEROBYTES: int = lib.crypto_box_boxzerobytes() crypto_box_BEFORENMBYTES: int = lib.crypto_box_beforenmbytes() crypto_box_SEALBYTES: int = lib.crypto_box_sealbytes() +crypto_box_MACBYTES: int = lib.crypto_box_macbytes() def crypto_box_keypair() -> Tuple[bytes, bytes]: @@ -227,6 +228,156 @@ def crypto_box_open_afternm( return ffi.buffer(plaintext, len(padded))[crypto_box_ZEROBYTES:] +def crypto_box_easy( + message: bytes, nonce: bytes, pk: bytes, sk: bytes +) -> bytes: + """ + Encrypts and returns a message ``message`` using the secret key ``sk``, + public key ``pk``, and the nonce ``nonce``. + + :param message: bytes + :param nonce: bytes + :param pk: bytes + :param sk: bytes + :rtype: bytes + """ + if len(nonce) != crypto_box_NONCEBYTES: + raise exc.ValueError("Invalid nonce size") + + if len(pk) != crypto_box_PUBLICKEYBYTES: + raise exc.ValueError("Invalid public key") + + if len(sk) != crypto_box_SECRETKEYBYTES: + raise exc.ValueError("Invalid secret key") + + _mlen = len(message) + _clen = crypto_box_MACBYTES + _mlen + + ciphertext = ffi.new("unsigned char[]", _clen) + + rc = lib.crypto_box_easy(ciphertext, message, _mlen, nonce, pk, sk) + ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError) + + return ffi.buffer(ciphertext, _clen)[:] + + +def crypto_box_open_easy( + ciphertext: bytes, nonce: bytes, pk: bytes, sk: bytes +) -> bytes: + """ + Decrypts and returns an encrypted message ``ciphertext``, using the secret + key ``sk``, public key ``pk``, and the nonce ``nonce``. + + :param ciphertext: bytes + :param nonce: bytes + :param pk: bytes + :param sk: bytes + :rtype: bytes + """ + if len(nonce) != crypto_box_NONCEBYTES: + raise exc.ValueError("Invalid nonce size") + + if len(pk) != crypto_box_PUBLICKEYBYTES: + raise exc.ValueError("Invalid public key") + + if len(sk) != crypto_box_SECRETKEYBYTES: + raise exc.ValueError("Invalid secret key") + + _clen = len(ciphertext) + + ensure( + _clen >= crypto_box_MACBYTES, + "Input ciphertext must be at least {} long".format( + crypto_box_MACBYTES + ), + raising=exc.TypeError, + ) + + _mlen = _clen - crypto_box_MACBYTES + + plaintext = ffi.new("unsigned char[]", max(1, _mlen)) + + res = lib.crypto_box_open_easy(plaintext, ciphertext, _clen, nonce, pk, sk) + ensure( + res == 0, + "An error occurred trying to decrypt the message", + raising=exc.CryptoError, + ) + + return ffi.buffer(plaintext, _mlen)[:] + + +def crypto_box_easy_afternm(message: bytes, nonce: bytes, k: bytes) -> bytes: + """ + Encrypts and returns the message ``message`` using the shared key ``k`` and + the nonce ``nonce``. + + :param message: bytes + :param nonce: bytes + :param k: bytes + :rtype: bytes + """ + if len(nonce) != crypto_box_NONCEBYTES: + raise exc.ValueError("Invalid nonce") + + if len(k) != crypto_box_BEFORENMBYTES: + raise exc.ValueError("Invalid shared key") + + _mlen = len(message) + _clen = crypto_box_MACBYTES + _mlen + + ciphertext = ffi.new("unsigned char[]", _clen) + + rc = lib.crypto_box_easy_afternm(ciphertext, message, _mlen, nonce, k) + ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError) + + return ffi.buffer(ciphertext, _clen)[:] + + +def crypto_box_open_easy_afternm( + ciphertext: bytes, nonce: bytes, k: bytes +) -> bytes: + """ + Decrypts and returns the encrypted message ``ciphertext``, using the shared + key ``k`` and the nonce ``nonce``. + + :param ciphertext: bytes + :param nonce: bytes + :param k: bytes + :rtype: bytes + """ + if len(nonce) != crypto_box_NONCEBYTES: + raise exc.ValueError("Invalid nonce") + + if len(k) != crypto_box_BEFORENMBYTES: + raise exc.ValueError("Invalid shared key") + + _clen = len(ciphertext) + + ensure( + _clen >= crypto_box_MACBYTES, + "Input ciphertext must be at least {} long".format( + crypto_box_MACBYTES + ), + raising=exc.TypeError, + ) + + _mlen = _clen - crypto_box_MACBYTES + + plaintext = ffi.new("unsigned char[]", max(1, _mlen)) + + res = lib.crypto_box_open_easy_afternm( + plaintext, ciphertext, _clen, nonce, k + ) + ensure( + res == 0, + "An error occurred trying to decrypt the message", + raising=exc.CryptoError, + ) + + return ffi.buffer(plaintext, _mlen)[:] + + def crypto_box_seal(message: bytes, pk: bytes) -> bytes: """ Encrypts and returns a message ``message`` using an ephemeral secret key diff --git a/src/nacl/bindings/crypto_secretbox.py b/src/nacl/bindings/crypto_secretbox.py index 2a632a2b..d1ad1133 100644 --- a/src/nacl/bindings/crypto_secretbox.py +++ b/src/nacl/bindings/crypto_secretbox.py @@ -84,3 +84,76 @@ def crypto_secretbox_open( plaintext = ffi.buffer(plaintext, len(padded)) return plaintext[crypto_secretbox_ZEROBYTES:] + + +def crypto_secretbox_easy(message: bytes, nonce: bytes, key: bytes) -> bytes: + """ + Encrypts and returns the message ``message`` with the secret ``key`` and + the nonce ``nonce``. + + :param message: bytes + :param nonce: bytes + :param key: bytes + :rtype: bytes + """ + if len(key) != crypto_secretbox_KEYBYTES: + raise exc.ValueError("Invalid key") + + if len(nonce) != crypto_secretbox_NONCEBYTES: + raise exc.ValueError("Invalid nonce") + + _mlen = len(message) + _clen = crypto_secretbox_MACBYTES + _mlen + + ciphertext = ffi.new("unsigned char[]", _clen) + + res = lib.crypto_secretbox_easy(ciphertext, message, _mlen, nonce, key) + ensure(res == 0, "Encryption failed", raising=exc.CryptoError) + + ciphertext = ffi.buffer(ciphertext, _clen) + return ciphertext[:] + + +def crypto_secretbox_open_easy( + ciphertext: bytes, nonce: bytes, key: bytes +) -> bytes: + """ + Decrypt and returns the encrypted message ``ciphertext`` with the secret + ``key`` and the nonce ``nonce``. + + :param ciphertext: bytes + :param nonce: bytes + :param key: bytes + :rtype: bytes + """ + if len(key) != crypto_secretbox_KEYBYTES: + raise exc.ValueError("Invalid key") + + if len(nonce) != crypto_secretbox_NONCEBYTES: + raise exc.ValueError("Invalid nonce") + + _clen = len(ciphertext) + + ensure( + _clen >= crypto_secretbox_MACBYTES, + "Input ciphertext must be at least {} long".format( + crypto_secretbox_MACBYTES + ), + raising=exc.TypeError, + ) + + _mlen = _clen - crypto_secretbox_MACBYTES + + plaintext = ffi.new("unsigned char[]", max(1, _mlen)) + + res = lib.crypto_secretbox_open_easy( + plaintext, ciphertext, _clen, nonce, key + ) + ensure( + res == 0, + "Decryption failed. Ciphertext failed verification", + raising=exc.CryptoError, + ) + + plaintext = ffi.buffer(plaintext, _mlen) + return plaintext[:] diff --git a/src/nacl/public.py b/src/nacl/public.py index c63665ac..ef2c2517 100644 --- a/src/nacl/public.py +++ b/src/nacl/public.py @@ -251,7 +251,7 @@ def encrypt( "The nonce must be exactly %s bytes long" % self.NONCE_SIZE ) - ciphertext = nacl.bindings.crypto_box_afternm( + ciphertext = nacl.bindings.crypto_box_easy_afternm( plaintext, nonce, self._shared_key, @@ -296,7 +296,7 @@ def decrypt( "The nonce must be exactly %s bytes long" % self.NONCE_SIZE ) - plaintext = nacl.bindings.crypto_box_open_afternm( + plaintext = nacl.bindings.crypto_box_open_easy_afternm( ciphertext, nonce, self._shared_key, diff --git a/src/nacl/secret.py b/src/nacl/secret.py index a1ac66bf..5c3064f8 100644 --- a/src/nacl/secret.py +++ b/src/nacl/secret.py @@ -103,7 +103,7 @@ def encrypt( "The nonce must be exactly %s bytes long" % self.NONCE_SIZE, ) - ciphertext = nacl.bindings.crypto_secretbox( + ciphertext = nacl.bindings.crypto_secretbox_easy( plaintext, nonce, self._key ) @@ -146,7 +146,7 @@ def decrypt( "The nonce must be exactly %s bytes long" % self.NONCE_SIZE, ) - plaintext = nacl.bindings.crypto_secretbox_open( + plaintext = nacl.bindings.crypto_secretbox_open_easy( ciphertext, nonce, self._key ) diff --git a/tests/test_bindings.py b/tests/test_bindings.py index 4ed08faf..a89c361c 100644 --- a/tests/test_bindings.py +++ b/tests/test_bindings.py @@ -76,6 +76,24 @@ def test_secretbox(): ) +def test_secretbox_easy(): + key = b"\x00" * c.crypto_secretbox_KEYBYTES + msg = b"message" + nonce = b"\x01" * c.crypto_secretbox_NONCEBYTES + ct = c.crypto_secretbox_easy(msg, nonce, key) + assert len(ct) == len(msg) + c.crypto_secretbox_BOXZEROBYTES + assert tohex(ct) == "3ae84dfb89728737bd6e2c8cacbaf8af3d34cc1666533a" + msg2 = c.crypto_secretbox_open_easy(ct, nonce, key) + assert msg2 == msg + + with pytest.raises(CryptoError): + c.crypto_secretbox_open_easy( + msg + b"!", + nonce, + key, + ) + + def test_secretbox_wrong_length(): with pytest.raises(ValueError): c.crypto_secretbox(b"", b"", b"") @@ -89,6 +107,21 @@ def test_secretbox_wrong_length(): ) +def test_secretbox_easy_wrong_length(): + with pytest.raises(ValueError): + c.crypto_secretbox_easy(b"", b"", b"") + with pytest.raises(ValueError): + c.crypto_secretbox_easy( + b"", b"", b"\x00" * c.crypto_secretbox_KEYBYTES + ) + with pytest.raises(ValueError): + c.crypto_secretbox_open_easy(b"", b"", b"") + with pytest.raises(ValueError): + c.crypto_secretbox_open_easy( + b"", b"", b"\x00" * c.crypto_secretbox_KEYBYTES + ) + + def test_box(): A_pubkey, A_secretkey = c.crypto_box_keypair() assert len(A_secretkey) == c.crypto_box_SECRETKEYBYTES @@ -118,6 +151,35 @@ def test_box(): c.crypto_box_open(message + b"!", nonce, A_pubkey, A_secretkey) +def test_box_easy(): + 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_easy_afternm(message, nonce, k1) + assert len(ct1) == len(message) + c.crypto_box_BOXZEROBYTES + + ct2 = c.crypto_box_easy(message, nonce, B_pubkey, A_secretkey) + assert tohex(ct2) == tohex(ct1) + + m1 = c.crypto_box_open_easy(ct1, nonce, A_pubkey, B_secretkey) + assert m1 == message + + m2 = c.crypto_box_open_easy_afternm(ct1, nonce, k1) + assert m2 == message + + with pytest.raises(CryptoError): + c.crypto_box_open_easy(message + b"!", nonce, A_pubkey, A_secretkey) + + def test_box_wrong_lengths(): A_pubkey, A_secretkey = c.crypto_box_keypair() with pytest.raises(ValueError): @@ -154,6 +216,48 @@ def test_box_wrong_lengths(): c.crypto_box_open_afternm(b"", b"\x00" * c.crypto_box_NONCEBYTES, b"") +def test_box_easy_wrong_lengths(): + A_pubkey, A_secretkey = c.crypto_box_keypair() + with pytest.raises(ValueError): + c.crypto_box_easy(b"abc", b"\x00", A_pubkey, A_secretkey) + with pytest.raises(ValueError): + c.crypto_box_easy( + b"abc", b"\x00" * c.crypto_box_NONCEBYTES, b"", A_secretkey + ) + with pytest.raises(ValueError): + c.crypto_box_easy( + b"abc", b"\x00" * c.crypto_box_NONCEBYTES, A_pubkey, b"" + ) + + with pytest.raises(ValueError): + c.crypto_box_open_easy(b"", b"", b"", b"") + with pytest.raises(ValueError): + c.crypto_box_open_easy( + b"", b"\x00" * c.crypto_box_NONCEBYTES, b"", b"" + ) + with pytest.raises(ValueError): + c.crypto_box_open_easy( + b"", b"\x00" * c.crypto_box_NONCEBYTES, A_pubkey, b"" + ) + + with pytest.raises(ValueError): + c.crypto_box_beforenm(b"", b"") + with pytest.raises(ValueError): + c.crypto_box_beforenm(A_pubkey, b"") + + with pytest.raises(ValueError): + c.crypto_box_easy_afternm(b"", b"", b"") + with pytest.raises(ValueError): + c.crypto_box_easy_afternm(b"", b"\x00" * c.crypto_box_NONCEBYTES, b"") + + with pytest.raises(ValueError): + c.crypto_box_open_easy_afternm(b"", b"", b"") + with pytest.raises(ValueError): + c.crypto_box_open_easy_afternm( + b"", b"\x00" * c.crypto_box_NONCEBYTES, b"" + ) + + def test_sign(): seed = b"\x00" * c.crypto_sign_SEEDBYTES pubkey, secretkey = c.crypto_sign_seed_keypair(seed)