diff --git a/libnacl/__init__.py b/libnacl/__init__.py index 4a9fe73..0f930c9 100644 --- a/libnacl/__init__.py +++ b/libnacl/__init__.py @@ -162,8 +162,20 @@ class CryptError(Exception): crypto_verify_16_BYTES = nacl.crypto_verify_16_bytes() crypto_verify_32_BYTES = nacl.crypto_verify_32_bytes() crypto_verify_64_BYTES = nacl.crypto_verify_64_bytes() - # pylint: enable=C0103 + + randombytes_SEEDBYTES = nacl.randombytes_seedbytes() + crypto_kdf_PRIMITIVE = nacl.crypto_kdf_primitive() + crypto_kdf_BYTES_MIN = nacl.crypto_kdf_bytes_min() + crypto_kdf_BYTES_MAX = nacl.crypto_kdf_bytes_max() + crypto_kdf_CONTEXTBYTES = nacl.crypto_kdf_contextbytes() + crypto_kdf_KEYBYTES = nacl.crypto_kdf_keybytes() + crypto_kx_PUBLICKEYBYTES = nacl.crypto_kx_publickeybytes() + crypto_kx_SECRETKEYBYTES = nacl.crypto_kx_secretkeybytes() + crypto_kx_SEEDBYTES = nacl.crypto_kx_seedbytes() + crypto_kx_SESSIONKEYBYTES = nacl.crypto_kx_sessionkeybytes() + crypto_kx_PRIMITIVE = nacl.crypto_kx_primitive() + # pylint: enable=C0103 # Pubkey defs @@ -632,6 +644,37 @@ def crypto_secretbox_open(ctxt, nonce, key): raise ValueError('Failed to decrypt message') return msg.raw[crypto_secretbox_ZEROBYTES:] +# Authenticated Symmetric Encryption improved version + + +def crypto_secretbox_easy(cmessage, nonce, key): + if len(key) != crypto_secretbox_KEYBYTES: + raise ValueError('Invalid key') + + if len(nonce) != crypto_secretbox_NONCEBYTES: + raise ValueError('Invalid nonce') + + + ctxt = ctypes.create_string_buffer(crypto_secretbox_MACBYTES + len(cmessage)) + ret = nacl.crypto_secretbox_easy(ctxt, cmessage, ctypes.c_ulonglong(len(cmessage)), nonce, key) + if ret: + raise ValueError('Failed to encrypt message') + return ctxt.raw[0:] + +def crypto_secretbox_open_easy(ctxt, nonce, key): + + if len(key) != crypto_secretbox_KEYBYTES: + raise ValueError('Invalid key') + + if len(nonce) != crypto_secretbox_NONCEBYTES: + raise ValueError('Invalid nonce') + + msg = ctypes.create_string_buffer(len(ctxt)) + ret = nacl.crypto_secretbox_open_easy(msg, ctxt, ctypes.c_ulonglong(len(ctxt)), nonce, key) + if ret: + raise ValueError('Failed to decrypt message') + return msg.raw[0:len(ctxt) - crypto_secretbox_MACBYTES] + # Authenticated Symmetric Encryption with Additional Data @@ -1057,6 +1100,20 @@ def randombytes_buf(size): nacl.randombytes_buf(buf, size) return buf.raw +def randombytes_buf_deterministic(size, seed): + ''' + Returns a string of random byles of the given size for a given seed. + For a given seed, this function will always output the same sequence. + Size can be up to 2^70 (256 GB). + ''' + + if len(seed) != randombytes_SEEDBYTES: + raise ValueError('Invalid key seed') + + size = int(size) + buf = ctypes.create_string_buffer(size) + nacl.randombytes_buf_deterministic(buf, size, seed) + return buf.raw def randombytes_close(): ''' @@ -1089,6 +1146,72 @@ def randombytes_uniform(upper_bound): ''' return nacl.randombytes_uniform(upper_bound) +# Key derivation API + +def crypto_kdf_keygen(): + ''' + Returns a string of random bytes to generate a master key + ''' + size = crypto_kdf_KEYBYTES + buf = ctypes.create_string_buffer(size) + nacl.crypto_kdf_keygen(buf) + return buf.raw + +def crypto_kdf_derive_from_key(subkey_size, subkey_id, context, master_key): + ''' + Returns a subkey generated from a master key for a given subkey_id. + For a given subkey_id, the subkey will always be the same string. + ''' + size = int(subkey_size) + buf = ctypes.create_string_buffer(size) + nacl.crypto_kdf_derive_from_key(buf, subkey_size, subkey_id, context, master_key) + return buf.raw + +# Key Exchange API + +def crypto_kx_keypair(): + ''' + Generate and return a new keypair + ''' + pk = ctypes.create_string_buffer(crypto_kx_PUBLICKEYBYTES) + sk = ctypes.create_string_buffer(crypto_kx_SECRETKEYBYTES) + nacl.crypto_kx_keypair(pk, sk) + return pk.raw, sk.raw + +def crypto_kx_seed_keypair(seed): + ''' + Generate and return a keypair from a key seed + ''' + if len(seed) != crypto_kx_SEEDBYTES: + raise ValueError('Invalid key seed') + pk = ctypes.create_string_buffer(crypto_kx_PUBLICKEYBYTES) + sk = ctypes.create_string_buffer(crypto_kx_SECRETKEYBYTES) + nacl.crypto_kx_seed_keypair(pk, sk, seed) + return pk.raw, sk.raw + +def crypto_kx_client_session_keys(client_pk, client_sk, server_pk): + ''' + Computes a pair of shared keys (rx and tx) using the client's public key client_pk, + the client's secret key client_sk and the server's public key server_pk. + Status returns 0 on success, or -1 if the server's public key is not acceptable. + ''' + rx = ctypes.create_string_buffer(crypto_kx_SESSIONKEYBYTES) + tx = ctypes.create_string_buffer(crypto_kx_SESSIONKEYBYTES) + status = nacl.crypto_kx_client_session_keys(rx, tx, client_pk, client_sk, server_pk) + return rx.raw, tx.raw, status + +def crypto_kx_server_session_keys(server_pk, server_sk, client_pk): + ''' + Computes a pair of shared keys (rx and tx) using the server's public key server_pk, + the server's secret key server_sk and the client's public key client_pk. + Status returns 0 on success, or -1 if the client's public key is not acceptable. + ''' + rx = ctypes.create_string_buffer(crypto_kx_SESSIONKEYBYTES) + tx = ctypes.create_string_buffer(crypto_kx_SESSIONKEYBYTES) + status = nacl.crypto_kx_server_session_keys(rx, tx, server_pk, server_sk, client_pk) + return rx, tx, status + + # Utility functions @@ -1141,3 +1264,5 @@ def crypto_sign_ed25519_sk_to_curve25519(ed25519_sk): if ret: raise CryptError('Failed to generate Curve25519 secret key') return curve25519_sk.raw + + diff --git a/libnacl/secret_easy.py b/libnacl/secret_easy.py new file mode 100644 index 0000000..15dea1b --- /dev/null +++ b/libnacl/secret_easy.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +''' +Utilities to make secret box easy encryption simple +''' +# Import libnacl +import libnacl +import libnacl.utils +import libnacl.base + + +class SecretBoxEasy(libnacl.base.BaseKey): + ''' + Manage symetric encryption using the salsa20 algorithm + ''' + def __init__(self, key=None): + if key is None: + key = libnacl.utils.salsa_key() + if len(key) != libnacl.crypto_secretbox_KEYBYTES: + raise ValueError('Invalid key') + self.sk = key + + def encrypt(self, msg, nonce=None, pack_nonce=True): + ''' + Encrypt the given message. If a nonce is not given it will be + generated via the rand_nonce function + ''' + if nonce is None: + nonce = libnacl.utils.rand_nonce() + if len(nonce) != libnacl.crypto_secretbox_NONCEBYTES: + raise ValueError('Invalid nonce size') + ctxt = libnacl.crypto_secretbox_easy(msg, nonce, self.sk) + if pack_nonce: + return nonce + ctxt + else: + return nonce, ctxt + + def decrypt(self, ctxt, nonce=None): + ''' + Decrypt the given message, if no nonce is given the nonce will be + extracted from the message + ''' + if nonce is None: + nonce = ctxt[:libnacl.crypto_secretbox_NONCEBYTES] + ctxt = ctxt[libnacl.crypto_secretbox_NONCEBYTES:] + if len(nonce) != libnacl.crypto_secretbox_NONCEBYTES: + raise ValueError('Invalid nonce') + return libnacl.crypto_secretbox_open_easy(ctxt, nonce, self.sk) diff --git a/tests/unit/test_raw_auth_sym_easy.py b/tests/unit/test_raw_auth_sym_easy.py new file mode 100644 index 0000000..945e6b0 --- /dev/null +++ b/tests/unit/test_raw_auth_sym_easy.py @@ -0,0 +1,20 @@ +# Import nacl libs +import libnacl +import libnacl.utils + +# Import python libs +import unittest + + +class TestSecretBox(unittest.TestCase): + ''' + Test sign functions + ''' + def test_secret_box_easy(self): + msg = b'Are you suggesting coconuts migrate?' + sk1 = libnacl.utils.salsa_key() + nonce1 = libnacl.utils.rand_nonce() + enc_msg = libnacl.crypto_secretbox_easy(msg, nonce1, sk1) + self.assertNotEqual(msg, enc_msg) + clear_msg = libnacl.crypto_secretbox_open_easy(enc_msg, nonce1, sk1) + self.assertEqual(msg, clear_msg) diff --git a/tests/unit/test_raw_random.py b/tests/unit/test_raw_random.py index b695986..8821731 100644 --- a/tests/unit/test_raw_random.py +++ b/tests/unit/test_raw_random.py @@ -27,3 +27,78 @@ def test_randombytes(self): self.assertEqual(256, len(freq)) self.assertTrue(all(freq.values())) + + def test_randombytes_buf_deterministic(self): + + seed = libnacl.randombytes_buf(32) + seed2 = libnacl.randombytes_buf(32) + data = libnacl.randombytes_buf_deterministic(32, seed) + data2 = libnacl.randombytes_buf_deterministic(32, seed) + data3 = libnacl.randombytes_buf_deterministic(32, seed2) + + self.assertEqual(32, len(data)) + self.assertEqual(32, len(data)) + self.assertEqual(32, len(data)) + self.assertEqual(data, data2) + self.assertNotEqual(data, data3) + + def test_crypto_kdf_keygen(self): + + master_key = libnacl.crypto_kdf_keygen() + + freq = {x: 1 for x in master_key} + + self.assertEqual(32, len(master_key)) + self.assertTrue(all(freq.values())) + + def test_crypto_kdf_derive_from_key(self): + + master_key = libnacl.crypto_kdf_keygen() + subkey = libnacl.crypto_kdf_derive_from_key(16, 1, "Examples", master_key) + subkey2 = libnacl.crypto_kdf_derive_from_key(16, 1, "Examples", master_key) + subkey3 = libnacl.crypto_kdf_derive_from_key(16, 2, "Examples", master_key) + + self.assertEqual(16, len(subkey)) + self.assertEqual(16, len(subkey2)) + self.assertEqual(16, len(subkey3)) + self.assertEqual(subkey, subkey2) + self.assertNotEqual(subkey, subkey3) + + def test_crypto_kx_keypair(self): + pk, sk = libnacl.crypto_kx_keypair() + self.assertEqual(32, len(pk)) + self.assertEqual(32, len(sk)) + + def test_crypto_kx_seed_keypair(self): + seed = libnacl.randombytes_buf(32) + seed2 = libnacl.randombytes_buf(32) + pk, sk = libnacl.crypto_kx_seed_keypair(seed) + pk2, sk2 = libnacl.crypto_kx_seed_keypair(seed) + pk3, sk3 = libnacl.crypto_kx_seed_keypair(seed2) + + self.assertEqual(pk, pk2) + self.assertNotEqual(pk, pk3) + self.assertEqual(sk, sk2) + self.assertNotEqual(sk, sk3) + + def test_crypto_kx_client_session_keys(self): + client_pk, client_sk = libnacl.crypto_kx_keypair() + server_pk, server_sk = libnacl.crypto_kx_keypair() + rx, tx, status = libnacl.crypto_kx_client_session_keys(client_pk, client_sk, server_pk) + rx2, tx2, status = libnacl.crypto_kx_client_session_keys(client_pk, client_sk, server_pk) + + self.assertEqual(32, len(rx)) + self.assertEqual(32, len(tx)) + self.assertEqual(rx, rx2) + self.assertEqual(tx, tx2) + + def test_crypto_kx_server_session_keys(self): + client_pk, client_sk = libnacl.crypto_kx_keypair() + server_pk, server_sk = libnacl.crypto_kx_keypair() + rx, tx, status = libnacl.crypto_kx_server_session_keys(client_pk, client_sk, server_pk) + rx2, tx2, status = libnacl.crypto_kx_server_session_keys(client_pk, client_sk, server_pk) + + self.assertEqual(32, len(rx)) + self.assertEqual(32, len(tx)) + self.assertEqual(rx, rx2) + self.assertEqual(tx, tx2) diff --git a/tests/unit/test_raw_secret_easy.py b/tests/unit/test_raw_secret_easy.py new file mode 100644 index 0000000..77e0966 --- /dev/null +++ b/tests/unit/test_raw_secret_easy.py @@ -0,0 +1,33 @@ +# Import libnacl libs +import libnacl +import libnacl.utils + +# Import python libs +import unittest + + +class TestSecret(unittest.TestCase): + """ + Test secret functions + """ + def test_secretbox_easy(self): + msg = b'Are you suggesting coconuts migrate?' + + nonce = libnacl.utils.rand_nonce() + key = libnacl.utils.salsa_key() + + c = libnacl.crypto_secretbox_easy(msg, nonce, key) + m = libnacl.crypto_secretbox_open_easy(c, nonce, key) + self.assertEqual(msg, m) + + with self.assertRaises(ValueError): + libnacl.crypto_secretbox_easy(msg, b'too_short', key) + + with self.assertRaises(ValueError): + libnacl.crypto_secretbox_easy(msg, nonce, b'too_short') + + with self.assertRaises(ValueError): + libnacl.crypto_secretbox_open_easy(c, b'too_short', key) + + with self.assertRaises(ValueError): + libnacl.crypto_secretbox_open_easy(c, nonce, b'too_short') diff --git a/tests/unit/test_secret_easy.py b/tests/unit/test_secret_easy.py new file mode 100644 index 0000000..f77fdfc --- /dev/null +++ b/tests/unit/test_secret_easy.py @@ -0,0 +1,22 @@ +# Import libnacl libs +import libnacl.secret_easy +# Import python libs +import unittest + +class TestSecretEasy(unittest.TestCase): + ''' + ''' + def test_secret(self): + msg = b'But then of course African swallows are not migratory.' + box = libnacl.secret_easy.SecretBoxEasy() + ctxt = box.encrypt(msg) + self.assertNotEqual(msg, ctxt) + box2 = libnacl.secret_easy.SecretBoxEasy(box.sk) + clear1 = box.decrypt(ctxt) + self.assertEqual(msg, clear1) + clear2 = box2.decrypt(ctxt) + self.assertEqual(clear1, clear2) + ctxt2 = box2.encrypt(msg) + clear3 = box.decrypt(ctxt2) + self.assertEqual(clear3, msg) +