From c4c0d93b05e82ee03ec881243fc7cc97f94ad01e Mon Sep 17 00:00:00 2001 From: tuxxy Date: Wed, 24 Jan 2018 14:34:14 -0700 Subject: [PATCH 1/3] Switch to cryptography.io ChaCha20-Poly1305 AEAD --- umbral/dem.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/umbral/dem.py b/umbral/dem.py index 335f6c5..3d579ab 100644 --- a/umbral/dem.py +++ b/umbral/dem.py @@ -1,29 +1,34 @@ -from nacl.secret import SecretBox +import os +from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305 class UmbralDEM(object): def __init__(self, symm_key: bytes): """ Initializes an UmbralDEM object. Requires a key to perform - Salsa20-Poly1305. + ChaCha20-Poly1305. """ - if len(symm_key) != SecretBox.KEY_SIZE: + if len(symm_key) != 32: raise ValueError( "Invalid key size, must be {} bytes".format(SecretBox.KEY_SIZE) ) - self.cipher = SecretBox(symm_key) + self.cipher = ChaCha20Poly1305(symm_key) - def encrypt(self, data: bytes): + def encrypt(self, data: bytes, authenticated_data: bytes=None): """ - Encrypts data using NaCl's Salsa20-Poly1305 secret box symmetric cipher. + Encrypts data using ChaCha20-Poly1305 with optional authenticated data. """ - enc_data = self.cipher.encrypt(data) - return enc_data + nonce = os.urandom(12) + enc_data = self.cipher.encrypt(nonce, data, authenticated_data) + return nonce + enc_data - def decrypt(self, enc_data: bytes): + def decrypt(self, enc_data: bytes, authenticated_data: bytes=None): """ - Decrypts data using NaCl's Salsa20-Poly1305 secret box symmetric cipher. + Decrypts data using ChaCha20-Poly1305 and validates the provided + authenticated data. """ - plaintext = self.cipher.decrypt(enc_data) + nonce = enc_data[:12] + ciphertext = enc_data[12:] + plaintext = self.cipher.decrypt(nonce, ciphertext, authenticated_data) return plaintext From 405a93cd357fe83351820b462ea589edb6f9b60c Mon Sep 17 00:00:00 2001 From: tuxxy Date: Wed, 24 Jan 2018 14:52:49 -0700 Subject: [PATCH 2/3] Add tests for DEM and some fixes --- tests/test_dem.py | 60 +++++++++++++++++++++++++++++++++++++++++++++++ umbral/dem.py | 14 +++++++---- 2 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 tests/test_dem.py diff --git a/tests/test_dem.py b/tests/test_dem.py new file mode 100644 index 0000000..bb6156a --- /dev/null +++ b/tests/test_dem.py @@ -0,0 +1,60 @@ +import pytest +import os + +from umbral.dem import UmbralDEM +from cryptography.exceptions import InvalidTag + + +def test_encrypt_decrypt(): + key = os.urandom(32) + + dem = UmbralDEM(key) + + plaintext = b'attack at dawn' + + ciphertext0 = dem.encrypt(plaintext) + ciphertext1 = dem.encrypt(plaintext) + + assert ciphertext0 != plaintext + assert ciphertext1 != plaintext + + # Ciphertext should be different even with same plaintext. + assert ciphertext0 != ciphertext1 + + # Nonce should be different + assert ciphertext0[:12] != ciphertext1[:12] + + cleartext0 = dem.decrypt(ciphertext0) + cleartext1 = dem.decrypt(ciphertext1) + + assert cleartext0 == plaintext + assert cleartext1 == plaintext + + +def test_encrypt_decrypt_associated_data(): + key = os.urandom(32) + aad = b'launch code 0000' + + dem = UmbralDEM(key) + + plaintext = b'attack at dawn' + + ciphertext0 = dem.encrypt(plaintext, authenticated_data=aad) + ciphertext1 = dem.encrypt(plaintext, authenticated_data=aad) + + assert ciphertext0 != plaintext + assert ciphertext1 != plaintext + + assert ciphertext0 != ciphertext1 + + assert ciphertext0[:12] != ciphertext1[:12] + + cleartext0 = dem.decrypt(ciphertext0, authenticated_data=aad) + cleartext1 = dem.decrypt(ciphertext1, authenticated_data=aad) + + assert cleartext0 == plaintext + assert cleartext1 == plaintext + + # Attempt decryption with invalid associated data + with pytest.raises(InvalidTag) as err_info: + cleartext2 = dem.decrypt(ciphertext0, authenticated_data=b'wrong data') diff --git a/umbral/dem.py b/umbral/dem.py index 3d579ab..d00c5d5 100644 --- a/umbral/dem.py +++ b/umbral/dem.py @@ -2,15 +2,19 @@ import os from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305 +DEM_KEYSIZE = 32 +DEM_NONCE_SIZE = 12 + + class UmbralDEM(object): def __init__(self, symm_key: bytes): """ Initializes an UmbralDEM object. Requires a key to perform ChaCha20-Poly1305. """ - if len(symm_key) != 32: + if len(symm_key) != DEM_KEYSIZE: raise ValueError( - "Invalid key size, must be {} bytes".format(SecretBox.KEY_SIZE) + "Invalid key size, must be {} bytes".format(DEM_KEYSIZE) ) self.cipher = ChaCha20Poly1305(symm_key) @@ -19,7 +23,7 @@ class UmbralDEM(object): """ Encrypts data using ChaCha20-Poly1305 with optional authenticated data. """ - nonce = os.urandom(12) + nonce = os.urandom(DEM_NONCE_SIZE) enc_data = self.cipher.encrypt(nonce, data, authenticated_data) return nonce + enc_data @@ -28,7 +32,7 @@ class UmbralDEM(object): Decrypts data using ChaCha20-Poly1305 and validates the provided authenticated data. """ - nonce = enc_data[:12] - ciphertext = enc_data[12:] + nonce = enc_data[:DEM_NONCE_SIZE] + ciphertext = enc_data[DEM_NONCE_SIZE:] plaintext = self.cipher.decrypt(nonce, ciphertext, authenticated_data) return plaintext From 050b0b3bdd0bb60ef487148266b0600e06e6d9b8 Mon Sep 17 00:00:00 2001 From: tuxxy Date: Thu, 25 Jan 2018 17:06:17 -0700 Subject: [PATCH 3/3] Use constants in tests and add comment --- tests/test_dem.py | 6 +++--- umbral/dem.py | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/test_dem.py b/tests/test_dem.py index bb6156a..81be81a 100644 --- a/tests/test_dem.py +++ b/tests/test_dem.py @@ -1,7 +1,7 @@ import pytest import os -from umbral.dem import UmbralDEM +from umbral.dem import UmbralDEM, DEM_KEYSIZE, DEM_NONCE_SIZE from cryptography.exceptions import InvalidTag @@ -22,7 +22,7 @@ def test_encrypt_decrypt(): assert ciphertext0 != ciphertext1 # Nonce should be different - assert ciphertext0[:12] != ciphertext1[:12] + assert ciphertext0[:DEM_NONCE_SIZE] != ciphertext1[:DEM_NONCE_SIZE] cleartext0 = dem.decrypt(ciphertext0) cleartext1 = dem.decrypt(ciphertext1) @@ -47,7 +47,7 @@ def test_encrypt_decrypt_associated_data(): assert ciphertext0 != ciphertext1 - assert ciphertext0[:12] != ciphertext1[:12] + assert ciphertext0[:DEM_NONCE_SIZE] != ciphertext1[:DEM_NONCE_SIZE] cleartext0 = dem.decrypt(ciphertext0, authenticated_data=aad) cleartext1 = dem.decrypt(ciphertext1, authenticated_data=aad) diff --git a/umbral/dem.py b/umbral/dem.py index d00c5d5..b1e7432 100644 --- a/umbral/dem.py +++ b/umbral/dem.py @@ -25,6 +25,7 @@ class UmbralDEM(object): """ nonce = os.urandom(DEM_NONCE_SIZE) enc_data = self.cipher.encrypt(nonce, data, authenticated_data) + # Ciphertext will be a 12 byte nonce, the ciphertext, and a 16 byte tag. return nonce + enc_data def decrypt(self, enc_data: bytes, authenticated_data: bytes=None):