Skip to content

Commit f68ac28

Browse files
authored
Merge pull request #84 from embhorn/gh83
Fix ChaCha20Poly1305 to be singleshot
2 parents f0e91d0 + 3ab80ac commit f68ac28

2 files changed

Lines changed: 94 additions & 118 deletions

File tree

tests/test_chacha20poly1305.py

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
# test_aesgcmstream.py
2+
# test_chacha20poly1305.py
33
#
44
# Copyright (C) 2022 wolfSSL Inc.
55
#
@@ -24,28 +24,59 @@
2424
from wolfcrypt._ffi import lib as _lib
2525

2626
if _lib.CHACHA20_POLY1305_ENABLED:
27-
from collections import namedtuple
2827
import pytest
2928
from wolfcrypt.utils import t2b
3029
from wolfcrypt.exceptions import WolfCryptError
3130
from binascii import hexlify as b2h, unhexlify as h2b
3231
from wolfcrypt.ciphers import ChaCha20Poly1305
3332

3433
def test_encrypt_decrypt():
35-
key = "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f"
36-
key = h2b(key)
37-
iv = "07000000404142434445464748" #Chnage from C test
38-
iv = h2b(iv)
39-
aad = "50515253c0c1c2c3c4c5c6c7" #Change from C test
40-
aad = h2b(aad)
41-
plaintext1 = "4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a204966204920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73637265656e20776f756c642062652069742e"
42-
plaintext1 = h2b(plaintext1)
43-
cipher1 = "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b6116"
44-
authTag = "1ae10b594f09e26a7e902ecbd0600691"
45-
chacha = ChaCha20Poly1305(key, iv, aad)
46-
generatedChipherText, generatedAuthTag = chacha.encrypt(plaintext1)
47-
assert h2b(cipher1) == generatedChipherText
48-
assert h2b(authTag) == generatedAuthTag
49-
chachadec = ChaCha20Poly1305(key, iv, aad)
50-
generatedPlaintextdec = chachadec.decrypt(generatedAuthTag, generatedChipherText)#takes in the generated authtag made by encrypt and decrypts and produces the plaintext
51-
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.")
34+
key = h2b("808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f")
35+
iv = h2b("070000004041424344454647")
36+
aad = h2b("50515253c0c1c2c3c4c5c6c7")
37+
plaintext = h2b("4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a204966204920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73637265656e20776f756c642062652069742e")
38+
expected_ciphertext = h2b("d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b6116")
39+
expected_authTag = h2b("1ae10b594f09e26a7e902ecbd0600691")
40+
41+
chacha = ChaCha20Poly1305(key)
42+
ciphertext, authTag = chacha.encrypt(aad, iv, plaintext)
43+
assert ciphertext == expected_ciphertext
44+
assert authTag == expected_authTag
45+
46+
decrypted = chacha.decrypt(aad, iv, authTag, ciphertext)
47+
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.")
48+
49+
def test_invalid_key_size():
50+
with pytest.raises(ValueError):
51+
ChaCha20Poly1305(b"tooshort")
52+
53+
def test_encrypt_invalid_iv_length():
54+
key = h2b("808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f")
55+
chacha = ChaCha20Poly1305(key)
56+
with pytest.raises(ValueError):
57+
chacha.encrypt(b"aad", b"short", b"plaintext")
58+
59+
def test_decrypt_invalid_iv_length():
60+
key = h2b("808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f")
61+
chacha = ChaCha20Poly1305(key)
62+
with pytest.raises(ValueError):
63+
chacha.decrypt(b"aad", b"short", b"\x00" * 16, b"ciphertext")
64+
65+
def test_decrypt_invalid_tag_length():
66+
key = h2b("808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f")
67+
chacha = ChaCha20Poly1305(key)
68+
with pytest.raises(ValueError):
69+
chacha.decrypt(b"aad", b"\x00" * 12, b"short", b"ciphertext")
70+
71+
def test_decrypt_bad_tag():
72+
key = h2b("808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f")
73+
iv = h2b("070000004041424344454647")
74+
aad = h2b("50515253c0c1c2c3c4c5c6c7")
75+
plaintext = b"hello world"
76+
77+
chacha = ChaCha20Poly1305(key)
78+
ciphertext, authTag = chacha.encrypt(aad, iv, plaintext)
79+
80+
bad_tag = b"\x00" * 16
81+
with pytest.raises(WolfCryptError):
82+
chacha.decrypt(aad, iv, bad_tag, ciphertext)

wolfcrypt/ciphers.py

Lines changed: 44 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -538,133 +538,78 @@ def set_iv(self, nonce, counter = 0):
538538
if _lib.CHACHA20_POLY1305_ENABLED:
539539
class ChaCha20Poly1305(object):
540540
"""
541-
ChaCha20 Poly1305
541+
ChaCha20-Poly1305 AEAD cipher.
542+
543+
One-shot encrypt/decrypt interface (non-streaming).
542544
"""
543-
block_size = 16
544-
_key_sizes = [16, 24, 32]
545-
_native_type = "ChaChaPoly_Aead *"
546-
_aad = None
545+
_key_sizes = [32]
547546
_tag_bytes = 16
548-
_mode = None
549-
_key = bytes()
550-
_IV = bytes()
551547

552-
def __init__(self, key, IV, aad, tag_bytes=16):
553-
"""
554-
tag_bytes is the number of bytes to use for the authentication tag during encryption
555-
"""
548+
def __init__(self, key):
556549
self._key = t2b(key)
557-
self._IV = t2b(IV)
558-
self._aad = t2b(aad)
559550
if len(self._key) not in self._key_sizes:
560551
raise ValueError("key must be %s in length, not %d" %
561552
(self._key_sizes, len(self._key)))
562-
self._native_object = _ffi.new(self._native_type)
563-
self._mode = None
564-
ret = _lib.wc_ChaCha20Poly1305_Init(
565-
self._native_object,
566-
_ffi.from_buffer(self._key),
567-
_ffi.from_buffer(self._IV),
568-
1
569-
)
570-
if ret < 0:
571-
raise WolfCryptError("Init error (%d)" % ret)
572553

573-
def set_aad(self, data):
574-
"""
575-
Set the additional authentication data for the stream
554+
def encrypt(self, aad, iv, plaintext):
576555
"""
577-
if self._mode is not None:
578-
raise WolfCryptError("AAD can only be set before encrypt() or decrypt() is called")
579-
self._aad = t2b(data)
580-
581-
def get_aad(self):
582-
return self._aad
556+
Encrypt plaintext data using the IV/nonce provided. The
557+
associated data (aad) is not encrypted but is included in the
558+
authentication tag.
583559
584-
def encrypt(self, inPlainText):
560+
Returns a tuple of (ciphertext, authTag).
585561
"""
586-
Add more data to the encryption stream
587-
"""
588-
inPlainText = t2b(inPlainText)
589-
if self._mode is None:
590-
self._mode = _ENCRYPTION
591-
aad = self._aad
592-
elif self._mode == _DECRYPTION:
593-
raise WolfCryptError("Class instance already in use for decryption")
594-
outGeneratedCipherText = _ffi.new("byte[%d]" % (len(inPlainText)))
595-
outGeneratedAuthTag = _ffi.new("byte[%d]" % self._tag_bytes)
562+
aad = t2b(aad)
563+
iv = t2b(iv)
564+
if len(iv) != 12:
565+
raise ValueError("iv must be 12 bytes, got %d" % len(iv))
566+
plaintext = t2b(plaintext)
567+
ciphertext = _ffi.new("byte[%d]" % len(plaintext))
568+
authTag = _ffi.new("byte[%d]" % self._tag_bytes)
596569
ret = _lib.wc_ChaCha20Poly1305_Encrypt(
597570
_ffi.from_buffer(self._key),
598-
_ffi.from_buffer(self._IV),
571+
_ffi.from_buffer(iv),
599572
_ffi.from_buffer(aad),
600573
len(aad),
601-
_ffi.from_buffer(inPlainText),
602-
len(inPlainText),
603-
outGeneratedCipherText,
604-
outGeneratedAuthTag
574+
_ffi.from_buffer(plaintext),
575+
len(plaintext),
576+
ciphertext,
577+
authTag
605578
)
606-
607579
if ret < 0:
608580
raise WolfCryptError("Encryption error (%d)" % ret)
609-
return bytes(outGeneratedCipherText), bytes(outGeneratedAuthTag)
581+
return bytes(ciphertext), bytes(authTag)
610582

611-
def decrypt(self, inGeneratedAuthTag, inGeneratedCipher):
583+
def decrypt(self, aad, iv, authTag, ciphertext):
612584
"""
613-
Add more data to the decryption stream
585+
Decrypt the ciphertext using the IV/nonce and authentication tag
586+
provided. The integrity of the associated data (aad) is checked.
587+
588+
Returns the decrypted plaintext.
614589
"""
615-
inGeneratedCipher = t2b(inGeneratedCipher)
616-
inGeneratedAuthTag = t2b(inGeneratedAuthTag)
617-
if self._mode is None:
618-
self._mode = _DECRYPTION
619-
aad = self._aad
620-
elif self._mode == _ENCRYPTION:
621-
raise WolfCryptError("Class instance already in use for decryption")
622-
outPlainText = _ffi.new("byte[%d]" % (len(inGeneratedCipher)))
590+
aad = t2b(aad)
591+
iv = t2b(iv)
592+
if len(iv) != 12:
593+
raise ValueError("iv must be 12 bytes, got %d" % len(iv))
594+
authTag = t2b(authTag)
595+
if len(authTag) != self._tag_bytes:
596+
raise ValueError("authTag must be %d bytes, got %d" %
597+
(self._tag_bytes, len(authTag)))
598+
ciphertext = t2b(ciphertext)
599+
plaintext = _ffi.new("byte[%d]" % len(ciphertext))
623600
ret = _lib.wc_ChaCha20Poly1305_Decrypt(
624601
_ffi.from_buffer(self._key),
625-
_ffi.from_buffer(self._IV),
602+
_ffi.from_buffer(iv),
626603
_ffi.from_buffer(aad),
627604
len(aad),
628-
_ffi.from_buffer(inGeneratedCipher),
629-
len(inGeneratedCipher),
630-
_ffi.from_buffer(inGeneratedAuthTag),
631-
outPlainText
605+
_ffi.from_buffer(ciphertext),
606+
len(ciphertext),
607+
_ffi.from_buffer(authTag),
608+
plaintext
632609
)
633610
if ret < 0:
634611
raise WolfCryptError("Decryption error (%d)" % ret)
635-
return bytes(outPlainText)
636-
637-
def checkTag(self, authTag):
638-
"""
639-
Check the authentication tag for the stream
640-
"""
641-
authTag = t2b(authTag)
642-
ret = _lib.wc_ChaCha20Poly1305_CheckTag(authTag, len(authTag))
643-
if ret < 0:
644-
raise WolfCryptError("Decryption error (%d)" % ret)
645-
646-
def final(self, authTag=None):
647-
"""
648-
When encrypting, finalize the stream and return an authentication tag for the stream.
649-
When decrypting, verify the authentication tag for the stream.
650-
The authTag parameter is only used for decrypting.
651-
"""
652-
if self._mode is None:
653-
raise WolfCryptError("Final called with no encryption or decryption")
654-
elif self._mode == _ENCRYPTION:
655-
authTag = _ffi.new("byte[%d]" % self._tag_bytes)
656-
ret = _lib.wc_ChaCha20Poly1305_Final(self._native_type, authTag)
657-
if ret < 0:
658-
raise WolfCryptError("Encryption error (%d)" % ret)
659-
return _ffi.buffer(authTag)[:]
660-
else:
661-
if authTag is None:
662-
raise WolfCryptError("authTag parameter required")
663-
authTag = t2b(authTag)
664-
self._native_object = _ffi.new(self._native_type)
665-
ret = _lib.wc_ChaCha20Poly1305_Final(self._native_type, authTag)
666-
if ret < 0:
667-
raise WolfCryptError("Decryption error (%d)" % ret)
612+
return bytes(plaintext)
668613

669614
if _lib.DES3_ENABLED:
670615
class Des3(_Cipher):

0 commit comments

Comments
 (0)