Skip to content

Commit

Permalink
Merge pull request #8 from rubidium42/incremental-authenticated-encry…
Browse files Browse the repository at this point in the history
…ption

Add support for incremental authenticated encryption
  • Loading branch information
mliberty1 authored May 8, 2024
2 parents 4773ad9 + 26ad9c2 commit f62e459
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 0 deletions.
52 changes: 52 additions & 0 deletions c_monocypher.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,58 @@ def unlock(key, nonce, mac, message, associated_data=None):
return plain_text


cdef class IncrementalAuthenticatedEncryption:
cdef crypto_aead_ctx _ctx

"""Instantiate the incremental authenticated encryption handler.
:param key: The 32-byte shared session key.
:param nonce: The 24-byte number, used only once with any given session key.
"""
def __init__(self, key, nonce):
if len(key) != 32:
raise ValueError(f'Invalid key length {len(key)} != 32')

if len(nonce) != 24:
raise ValueError(f'Invalid nonce length {len(key)} != 24')

crypto_aead_init_x(&self._ctx, key, nonce)

def lock(self, message, associated_data=None):
"""Perform authenticated encryption.
:param message: The secret message to encrypt.
:param associated_data: The additional data to authenticate which
is NOT encrypted.
:return: the tuple of (MAC, ciphertext). MAC is the 16-byte message
authentication code. ciphertext is the encrypted message.
"""
mac = bytes(16)
crypto_text = bytes(len(message))
associated_data = b'' if associated_data is None else associated_data
crypto_aead_write(&self._ctx, crypto_text, mac, associated_data, len(associated_data), message, len(message))
return mac, crypto_text

def unlock(self, mac, message, associated_data=None):
"""Perform authenticated decryption.
:param mac: The 16-byte message authentication code produced by :func:`lock`.
:param message: The ciphertext encrypted message to decrypt produced by :func:`lock`.
:param associated_data: The additional data to authenticate which
is NOT encrypted.
:return: The secret message or None on authentication failure.
"""
if len(mac) != 16:
raise ValueError(f'Invalid mac length {len(mac)} != 16')

plain_text = bytearray(len(message))
associated_data = b'' if associated_data is None else associated_data
rv = crypto_aead_read(&self._ctx, plain_text, mac, associated_data, len(associated_data), message, len(message))
if 0 != rv:
return None
return plain_text


def chacha20(key, nonce, message):
"""Encrypt/Decrypt a message with ChaCha20.
Expand Down
25 changes: 25 additions & 0 deletions test/test_monocypher.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,31 @@ def test_symmetric_aead(self):
msg2 = monocypher.unlock(key, nonce, mac, c, associated_data=aead)
self.assertEqual(msg, msg2)

def test_IncrementalAuthenticatedEncryption(self):
random = np.random.RandomState(seed=1)
for i in range(10):
message_length = random.randint(1, 4096)
aead_length = random.randint(1, 128)
key = bytes(random.randint(0, 256, 32, dtype=np.uint8))
nonce = bytes(random.randint(0, 256, 24, dtype=np.uint8))
aead = bytes(random.randint(0, 256, aead_length, dtype=np.uint8))
msg = bytes(random.randint(0, 256, message_length, dtype=np.uint8))

sender = monocypher.IncrementalAuthenticatedEncryption(key, nonce)
receiver = monocypher.IncrementalAuthenticatedEncryption(key, nonce)

mac, c = sender.lock(msg, associated_data=aead)
msg2 = receiver.unlock(mac, c, associated_data=aead)
self.assertEqual(msg, msg2)

# A second encryption works, but creates a different cipher text
mac2, c2 = sender.lock(msg, associated_data=aead)
self.assertNotEqual(c, c2)
self.assertNotEqual(mac, mac2)

msg2 = receiver.unlock(mac2, c2, associated_data=aead)
self.assertEqual(msg, msg2)

def test_sign(self):
random = np.random.RandomState(seed=1)
for i in range(10):
Expand Down

0 comments on commit f62e459

Please sign in to comment.