Fix ChaCha20Poly1305 to be singleshot

pull/84/head
Eric Blankenhorn 2026-03-02 11:40:10 -06:00
parent a6779c2e30
commit 01219a4659
2 changed files with 71 additions and 120 deletions

View File

@ -1,5 +1,5 @@
# test_aesgcmstream.py
# test_chacha20poly1305.py
#
# Copyright (C) 2022 wolfSSL Inc.
#
@ -24,7 +24,6 @@
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
@ -32,20 +31,34 @@ if _lib.CHACHA20_POLY1305_ENABLED:
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("07000000404142434445464748")
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_decrypt_bad_tag():
key = h2b("808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f")
iv = h2b("07000000404142434445464748")
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)

View File

@ -538,133 +538,71 @@ if _lib.CHACHA_ENABLED:
if _lib.CHACHA20_POLY1305_ENABLED:
class ChaCha20Poly1305(object):
"""
ChaCha20 Poly1305
"""
block_size = 16
_key_sizes = [16, 24, 32]
_native_type = "ChaChaPoly_Aead *"
_aad = None
_tag_bytes = 16
_mode = None
_key = bytes()
_IV = bytes()
ChaCha20-Poly1305 AEAD cipher.
def __init__(self, key, IV, aad, tag_bytes=16):
"""
tag_bytes is the number of bytes to use for the authentication tag during encryption
"""
One-shot encrypt/decrypt interface (non-streaming).
"""
_key_sizes = [32]
_tag_bytes = 16
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):
def encrypt(self, aad, iv, plaintext):
"""
Set the additional authentication data for the stream
"""
if self._mode is not None:
raise WolfCryptError("AAD can only be set before encrypt() or decrypt() is called")
self._aad = t2b(data)
Encrypt plaintext data using the IV/nonce provided. The
associated data (aad) is not encrypted but is included in the
authentication tag.
def get_aad(self):
return self._aad
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)
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)
authTag = t2b(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):