Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 50 additions & 19 deletions tests/test_chacha20poly1305.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

# test_aesgcmstream.py
# test_chacha20poly1305.py
#
# Copyright (C) 2022 wolfSSL Inc.
#
Expand All @@ -24,28 +24,59 @@
from wolfcrypt._ffi import lib as _lib

if _lib.CHACHA20_POLY1305_ENABLED:
from collections import namedtuple
import pytest
from wolfcrypt.utils import t2b
from wolfcrypt.exceptions import WolfCryptError
from binascii import hexlify as b2h, unhexlify as h2b
from wolfcrypt.ciphers import ChaCha20Poly1305

def test_encrypt_decrypt():
key = "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f"
key = h2b(key)
iv = "07000000404142434445464748" #Chnage from C test
iv = h2b(iv)
aad = "50515253c0c1c2c3c4c5c6c7" #Change from C test
aad = h2b(aad)
plaintext1 = "4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a204966204920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73637265656e20776f756c642062652069742e"
plaintext1 = h2b(plaintext1)
cipher1 = "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b6116"
authTag = "1ae10b594f09e26a7e902ecbd0600691"
chacha = ChaCha20Poly1305(key, iv, aad)
generatedChipherText, generatedAuthTag = chacha.encrypt(plaintext1)
assert h2b(cipher1) == generatedChipherText
assert h2b(authTag) == generatedAuthTag
chachadec = ChaCha20Poly1305(key, iv, aad)
generatedPlaintextdec = chachadec.decrypt(generatedAuthTag, generatedChipherText)#takes in the generated authtag made by encrypt and decrypts and produces the plaintext
assert generatedPlaintextdec == t2b("Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.")
key = h2b("808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f")
iv = h2b("070000004041424344454647")
aad = h2b("50515253c0c1c2c3c4c5c6c7")
plaintext = h2b("4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a204966204920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73637265656e20776f756c642062652069742e")
expected_ciphertext = h2b("d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b6116")
expected_authTag = h2b("1ae10b594f09e26a7e902ecbd0600691")

chacha = ChaCha20Poly1305(key)
ciphertext, authTag = chacha.encrypt(aad, iv, plaintext)
assert ciphertext == expected_ciphertext
assert authTag == expected_authTag

decrypted = chacha.decrypt(aad, iv, authTag, ciphertext)
assert decrypted == t2b("Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.")

def test_invalid_key_size():
with pytest.raises(ValueError):
ChaCha20Poly1305(b"tooshort")

def test_encrypt_invalid_iv_length():
key = h2b("808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f")
chacha = ChaCha20Poly1305(key)
with pytest.raises(ValueError):
chacha.encrypt(b"aad", b"short", b"plaintext")

def test_decrypt_invalid_iv_length():
key = h2b("808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f")
chacha = ChaCha20Poly1305(key)
with pytest.raises(ValueError):
chacha.decrypt(b"aad", b"short", b"\x00" * 16, b"ciphertext")

def test_decrypt_invalid_tag_length():
key = h2b("808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f")
chacha = ChaCha20Poly1305(key)
with pytest.raises(ValueError):
chacha.decrypt(b"aad", b"\x00" * 12, b"short", b"ciphertext")

def test_decrypt_bad_tag():
key = h2b("808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f")
iv = h2b("070000004041424344454647")
aad = h2b("50515253c0c1c2c3c4c5c6c7")
plaintext = b"hello world"

chacha = ChaCha20Poly1305(key)
ciphertext, authTag = chacha.encrypt(aad, iv, plaintext)

bad_tag = b"\x00" * 16
with pytest.raises(WolfCryptError):
chacha.decrypt(aad, iv, bad_tag, ciphertext)
143 changes: 44 additions & 99 deletions wolfcrypt/ciphers.py
Original file line number Diff line number Diff line change
Expand Up @@ -538,133 +538,78 @@ def set_iv(self, nonce, counter = 0):
if _lib.CHACHA20_POLY1305_ENABLED:
class ChaCha20Poly1305(object):
"""
ChaCha20 Poly1305
ChaCha20-Poly1305 AEAD cipher.

One-shot encrypt/decrypt interface (non-streaming).
"""
block_size = 16
_key_sizes = [16, 24, 32]
_native_type = "ChaChaPoly_Aead *"
_aad = None
_key_sizes = [32]
_tag_bytes = 16
_mode = None
_key = bytes()
_IV = bytes()

def __init__(self, key, IV, aad, tag_bytes=16):
"""
tag_bytes is the number of bytes to use for the authentication tag during encryption
"""
def __init__(self, key):
self._key = t2b(key)
self._IV = t2b(IV)
self._aad = t2b(aad)
if len(self._key) not in self._key_sizes:
raise ValueError("key must be %s in length, not %d" %
(self._key_sizes, len(self._key)))
self._native_object = _ffi.new(self._native_type)
self._mode = None
ret = _lib.wc_ChaCha20Poly1305_Init(
self._native_object,
_ffi.from_buffer(self._key),
_ffi.from_buffer(self._IV),
1
)
if ret < 0:
raise WolfCryptError("Init error (%d)" % ret)

def set_aad(self, data):
"""
Set the additional authentication data for the stream
def encrypt(self, aad, iv, plaintext):
"""
if self._mode is not None:
raise WolfCryptError("AAD can only be set before encrypt() or decrypt() is called")
self._aad = t2b(data)

def get_aad(self):
return self._aad
Encrypt plaintext data using the IV/nonce provided. The
associated data (aad) is not encrypted but is included in the
authentication tag.

def encrypt(self, inPlainText):
Returns a tuple of (ciphertext, authTag).
"""
Add more data to the encryption stream
"""
inPlainText = t2b(inPlainText)
if self._mode is None:
self._mode = _ENCRYPTION
aad = self._aad
elif self._mode == _DECRYPTION:
raise WolfCryptError("Class instance already in use for decryption")
outGeneratedCipherText = _ffi.new("byte[%d]" % (len(inPlainText)))
outGeneratedAuthTag = _ffi.new("byte[%d]" % self._tag_bytes)
aad = t2b(aad)
iv = t2b(iv)
if len(iv) != 12:
raise ValueError("iv must be 12 bytes, got %d" % len(iv))
plaintext = t2b(plaintext)
ciphertext = _ffi.new("byte[%d]" % len(plaintext))
authTag = _ffi.new("byte[%d]" % self._tag_bytes)
ret = _lib.wc_ChaCha20Poly1305_Encrypt(
_ffi.from_buffer(self._key),
_ffi.from_buffer(self._IV),
_ffi.from_buffer(iv),
_ffi.from_buffer(aad),
len(aad),
_ffi.from_buffer(inPlainText),
len(inPlainText),
outGeneratedCipherText,
outGeneratedAuthTag
_ffi.from_buffer(plaintext),
len(plaintext),
ciphertext,
authTag
)

if ret < 0:
raise WolfCryptError("Encryption error (%d)" % ret)
return bytes(outGeneratedCipherText), bytes(outGeneratedAuthTag)
return bytes(ciphertext), bytes(authTag)

def decrypt(self, inGeneratedAuthTag, inGeneratedCipher):
def decrypt(self, aad, iv, authTag, ciphertext):
"""
Add more data to the decryption stream
Decrypt the ciphertext using the IV/nonce and authentication tag
provided. The integrity of the associated data (aad) is checked.

Returns the decrypted plaintext.
"""
inGeneratedCipher = t2b(inGeneratedCipher)
inGeneratedAuthTag = t2b(inGeneratedAuthTag)
if self._mode is None:
self._mode = _DECRYPTION
aad = self._aad
elif self._mode == _ENCRYPTION:
raise WolfCryptError("Class instance already in use for decryption")
outPlainText = _ffi.new("byte[%d]" % (len(inGeneratedCipher)))
aad = t2b(aad)
iv = t2b(iv)
if len(iv) != 12:
raise ValueError("iv must be 12 bytes, got %d" % len(iv))
authTag = t2b(authTag)
if len(authTag) != self._tag_bytes:
raise ValueError("authTag must be %d bytes, got %d" %
(self._tag_bytes, len(authTag)))
ciphertext = t2b(ciphertext)
plaintext = _ffi.new("byte[%d]" % len(ciphertext))
ret = _lib.wc_ChaCha20Poly1305_Decrypt(
_ffi.from_buffer(self._key),
_ffi.from_buffer(self._IV),
_ffi.from_buffer(iv),
_ffi.from_buffer(aad),
len(aad),
_ffi.from_buffer(inGeneratedCipher),
len(inGeneratedCipher),
_ffi.from_buffer(inGeneratedAuthTag),
outPlainText
_ffi.from_buffer(ciphertext),
len(ciphertext),
_ffi.from_buffer(authTag),
plaintext
)
if ret < 0:
raise WolfCryptError("Decryption error (%d)" % ret)
return bytes(outPlainText)

def checkTag(self, authTag):
"""
Check the authentication tag for the stream
"""
authTag = t2b(authTag)
ret = _lib.wc_ChaCha20Poly1305_CheckTag(authTag, len(authTag))
if ret < 0:
raise WolfCryptError("Decryption error (%d)" % ret)

def final(self, authTag=None):
"""
When encrypting, finalize the stream and return an authentication tag for the stream.
When decrypting, verify the authentication tag for the stream.
The authTag parameter is only used for decrypting.
"""
if self._mode is None:
raise WolfCryptError("Final called with no encryption or decryption")
elif self._mode == _ENCRYPTION:
authTag = _ffi.new("byte[%d]" % self._tag_bytes)
ret = _lib.wc_ChaCha20Poly1305_Final(self._native_type, authTag)
if ret < 0:
raise WolfCryptError("Encryption error (%d)" % ret)
return _ffi.buffer(authTag)[:]
else:
if authTag is None:
raise WolfCryptError("authTag parameter required")
authTag = t2b(authTag)
self._native_object = _ffi.new(self._native_type)
ret = _lib.wc_ChaCha20Poly1305_Final(self._native_type, authTag)
if ret < 0:
raise WolfCryptError("Decryption error (%d)" % ret)
return bytes(plaintext)

if _lib.DES3_ENABLED:
class Des3(_Cipher):
Expand Down