Skip to content

Commit b660044

Browse files
authored
Add test vectors for ChaCha20 counter overflow (#9221)
* Adapt ChaCha20 test vectors to 64-bit counter * Add ChaCha20 test vectors for counter overflow These vectors test the behavior during counter overflow. Since different implementations use different counter sizes (e.g. OpenSSL uses a 64-bit counter, whereas BoringSSL uses a 32-bit counter), it's important to ensure that the behavior during counter overflow is consistent between implementations. These vectors take into account both 32-bit and 64-bit overflows.
1 parent f3a77b2 commit b660044

File tree

7 files changed

+226
-7
lines changed

7 files changed

+226
-7
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
ChaCha20 vector creation
2+
========================
3+
4+
This page documents the code that was used to generate the vectors
5+
to test the counter overflow behavior in ChaCha20 as well as code
6+
used to verify them against another implementation.
7+
8+
Creation
9+
--------
10+
11+
The following Python script was run to generate the vector files.
12+
13+
.. literalinclude:: /development/custom-vectors/chacha20/generate_chacha20_overflow.py
14+
15+
Download link: :download:`generate_chacha20_overflow.py
16+
</development/custom-vectors/chacha20/generate_chacha20_overflow.py>`
17+
18+
19+
Verification
20+
------------
21+
22+
The following Python script was used to verify the vectors. The
23+
counter overflow is handled manually to avoid relying on the same
24+
code that generated the vectors.
25+
26+
.. literalinclude:: /development/custom-vectors/chacha20/verify_chacha20_overflow.py
27+
28+
Download link: :download:`verify_chacha20_overflow.py
29+
</development/custom-vectors/chacha20/verify_chacha20_overflow.py>`
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# This file is dual licensed under the terms of the Apache License, Version
2+
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
3+
# for complete details.
4+
5+
import binascii
6+
import struct
7+
8+
from cryptography.hazmat.primitives import ciphers
9+
from cryptography.hazmat.primitives.ciphers import algorithms
10+
11+
_N_BLOCKS = [1, 1.5, 2, 2.5, 3]
12+
_INITIAL_COUNTERS = [2**32 - 1, 2**64 - 1]
13+
14+
15+
def _build_vectors():
16+
count = 0
17+
output = []
18+
key = "0" * 64
19+
nonce = "0" * 16
20+
for blocks in _N_BLOCKS:
21+
plaintext = binascii.unhexlify("0" * int(128 * blocks))
22+
for counter in _INITIAL_COUNTERS:
23+
full_nonce = struct.pack("<Q", counter) + binascii.unhexlify(nonce)
24+
cipher = ciphers.Cipher(
25+
algorithms.ChaCha20(binascii.unhexlify(key), full_nonce),
26+
None,
27+
)
28+
encryptor = cipher.encryptor()
29+
output.append(f"\nCOUNT = {count}")
30+
count += 1
31+
output.append(f"KEY = {key}")
32+
output.append(f"NONCE = {nonce}")
33+
output.append(f"INITIAL_BLOCK_COUNTER = {counter}")
34+
output.append(f"PLAINTEXT = {binascii.hexlify(plaintext)}")
35+
output.append(
36+
"CIPHERTEXT = {}".format(
37+
binascii.hexlify(encryptor.update(plaintext))
38+
)
39+
)
40+
return "\n".join(output)
41+
42+
43+
def _write_file(data, filename):
44+
with open(filename, "w") as f:
45+
f.write(data)
46+
47+
48+
if __name__ == "__main__":
49+
_write_file(_build_vectors(), "counter-overflow.txt")
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# This file is dual licensed under the terms of the Apache License, Version
2+
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
3+
# for complete details.
4+
5+
import binascii
6+
import math
7+
import struct
8+
from pathlib import Path
9+
10+
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
11+
from tests.utils import load_nist_vectors
12+
13+
BLOCK_SIZE = 64
14+
MAX_COUNTER = 2**64 - 1
15+
16+
17+
def encrypt(
18+
key: bytes, nonce: bytes, initial_block_counter: int, plaintext: bytes
19+
) -> bytes:
20+
full_nonce = struct.pack("<Q", initial_block_counter) + nonce
21+
encryptor = Cipher(
22+
algorithms.ChaCha20(key, full_nonce), mode=None
23+
).encryptor()
24+
25+
plaintext_len_blocks = math.ceil(len(plaintext) / BLOCK_SIZE)
26+
blocks_until_overflow = MAX_COUNTER - initial_block_counter + 1
27+
28+
if plaintext_len_blocks <= blocks_until_overflow:
29+
return binascii.hexlify(encryptor.update(plaintext))
30+
else:
31+
bytes_until_overflow = min(blocks_until_overflow * 64, len(plaintext))
32+
first_batch = binascii.hexlify(
33+
encryptor.update(plaintext[:bytes_until_overflow])
34+
)
35+
# We manually handle the overflow by resetting the counter to zero once
36+
# we surpass MAX_COUNTER blocks. This way we can check the vectors are
37+
# correct without relying on the same logic that generated them.
38+
full_nonce = struct.pack("<Q", 0) + nonce
39+
encryptor = Cipher(
40+
algorithms.ChaCha20(key, full_nonce), mode=None
41+
).encryptor()
42+
second_batch = binascii.hexlify(
43+
encryptor.update(plaintext[bytes_until_overflow:])
44+
)
45+
return first_batch + second_batch
46+
47+
48+
def verify_vectors(filename: Path):
49+
with open(filename) as f:
50+
vector_file = f.read().splitlines()
51+
52+
vectors = load_nist_vectors(vector_file)
53+
for vector in vectors:
54+
key = binascii.unhexlify(vector["key"])
55+
nonce = binascii.unhexlify(vector["nonce"])
56+
ibc = int(vector["initial_block_counter"])
57+
pt = binascii.unhexlify(vector["plaintext"])
58+
59+
computed_ct = encrypt(key, nonce, ibc, pt)
60+
61+
assert computed_ct == vector["ciphertext"]
62+
63+
64+
overflow_path = Path(
65+
"vectors/cryptography_vectors/ciphers/ChaCha20/counter-overflow.txt"
66+
)
67+
verify_vectors(overflow_path)

docs/development/test-vectors.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -951,7 +951,8 @@ Symmetric ciphers
951951
* CAST5 (ECB) from :rfc:`2144`.
952952
* CAST5 (CBC, CFB, OFB) generated by this project.
953953
See: :doc:`/development/custom-vectors/cast5`
954-
* ChaCha20 from :rfc:`7539`.
954+
* ChaCha20 from :rfc:`7539` and generated by this project.
955+
See: :doc:`/development/custom-vectors/chacha20`
955956
* ChaCha20Poly1305 from :rfc:`7539`, `OpenSSL's evpciph.txt`_, and the
956957
`BoringSSL ChaCha20Poly1305 tests`_.
957958
* IDEA (ECB) from the `NESSIE IDEA vectors`_ created by `NESSIE`_.
@@ -993,6 +994,7 @@ Created Vectors
993994

994995
custom-vectors/arc4
995996
custom-vectors/cast5
997+
custom-vectors/chacha20
996998
custom-vectors/idea
997999
custom-vectors/seed
9981000
custom-vectors/hkdf

tests/hazmat/primitives/test_chacha20.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@ class TestChaCha20:
2626
"vector",
2727
_load_all_params(
2828
os.path.join("ciphers", "ChaCha20"),
29-
["rfc7539.txt"],
29+
["counter-overflow.txt", "rfc7539.txt"],
3030
load_nist_vectors,
3131
),
3232
)
3333
def test_vectors(self, vector, backend):
3434
key = binascii.unhexlify(vector["key"])
3535
nonce = binascii.unhexlify(vector["nonce"])
36-
ibc = struct.pack("<i", int(vector["initial_block_counter"]))
36+
ibc = struct.pack("<Q", int(vector["initial_block_counter"]))
3737
pt = binascii.unhexlify(vector["plaintext"])
3838
encryptor = Cipher(
3939
algorithms.ChaCha20(key, ibc + nonce), None, backend
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
2+
COUNT = 0
3+
KEY = 0000000000000000000000000000000000000000000000000000000000000000
4+
NONCE = 0000000000000000
5+
INITIAL_BLOCK_COUNTER = 4294967295
6+
PLAINTEXT = 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
7+
CIPHERTEXT = ace4cd09e294d1912d4ad205d06f95d9c2f2bfcf453e8753f128765b62215f4d92c74f2f626c6a640c0b1284d839ec81f1696281dafc3e684593937023b58b1d
8+
9+
COUNT = 1
10+
KEY = 0000000000000000000000000000000000000000000000000000000000000000
11+
NONCE = 0000000000000000
12+
INITIAL_BLOCK_COUNTER = 18446744073709551615
13+
PLAINTEXT = 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
14+
CIPHERTEXT = d7918cd8620cf832532652c04c01a553092cfb32e7b3f2f5467ae9674a2e9eec17368ec8027a357c0c51e6ea747121fec45284be0f099d2b3328845607b17689
15+
16+
COUNT = 2
17+
KEY = 0000000000000000000000000000000000000000000000000000000000000000
18+
NONCE = 0000000000000000
19+
INITIAL_BLOCK_COUNTER = 4294967295
20+
PLAINTEXT = 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
21+
CIPHERTEXT = ace4cd09e294d1912d4ad205d06f95d9c2f2bfcf453e8753f128765b62215f4d92c74f2f626c6a640c0b1284d839ec81f1696281dafc3e684593937023b58b1d3db41d3aa0d329285de6f225e6e24bd59c9a17006943d5c9b680e3873bdc683a
22+
23+
COUNT = 3
24+
KEY = 0000000000000000000000000000000000000000000000000000000000000000
25+
NONCE = 0000000000000000
26+
INITIAL_BLOCK_COUNTER = 18446744073709551615
27+
PLAINTEXT = 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
28+
CIPHERTEXT = d7918cd8620cf832532652c04c01a553092cfb32e7b3f2f5467ae9674a2e9eec17368ec8027a357c0c51e6ea747121fec45284be0f099d2b3328845607b1768976b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7
29+
30+
COUNT = 4
31+
KEY = 0000000000000000000000000000000000000000000000000000000000000000
32+
NONCE = 0000000000000000
33+
INITIAL_BLOCK_COUNTER = 4294967295
34+
PLAINTEXT = 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
35+
CIPHERTEXT = ace4cd09e294d1912d4ad205d06f95d9c2f2bfcf453e8753f128765b62215f4d92c74f2f626c6a640c0b1284d839ec81f1696281dafc3e684593937023b58b1d3db41d3aa0d329285de6f225e6e24bd59c9a17006943d5c9b680e3873bdc683a5819469899989690c281cd17c96159af0682b5b903468a61f50228cf09622b5a
36+
37+
COUNT = 5
38+
KEY = 0000000000000000000000000000000000000000000000000000000000000000
39+
NONCE = 0000000000000000
40+
INITIAL_BLOCK_COUNTER = 18446744073709551615
41+
PLAINTEXT = 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
42+
CIPHERTEXT = d7918cd8620cf832532652c04c01a553092cfb32e7b3f2f5467ae9674a2e9eec17368ec8027a357c0c51e6ea747121fec45284be0f099d2b3328845607b1768976b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586
43+
44+
COUNT = 6
45+
KEY = 0000000000000000000000000000000000000000000000000000000000000000
46+
NONCE = 0000000000000000
47+
INITIAL_BLOCK_COUNTER = 4294967295
48+
PLAINTEXT = 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
49+
CIPHERTEXT = ace4cd09e294d1912d4ad205d06f95d9c2f2bfcf453e8753f128765b62215f4d92c74f2f626c6a640c0b1284d839ec81f1696281dafc3e684593937023b58b1d3db41d3aa0d329285de6f225e6e24bd59c9a17006943d5c9b680e3873bdc683a5819469899989690c281cd17c96159af0682b5b903468a61f50228cf09622b5a46f0f6efee15c8f1b198cb49d92b990867905159440cc723916dc00128269810
50+
51+
COUNT = 7
52+
KEY = 0000000000000000000000000000000000000000000000000000000000000000
53+
NONCE = 0000000000000000
54+
INITIAL_BLOCK_COUNTER = 18446744073709551615
55+
PLAINTEXT = 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
56+
CIPHERTEXT = d7918cd8620cf832532652c04c01a553092cfb32e7b3f2f5467ae9674a2e9eec17368ec8027a357c0c51e6ea747121fec45284be0f099d2b3328845607b1768976b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee65869f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32ee7aed
57+
58+
COUNT = 8
59+
KEY = 0000000000000000000000000000000000000000000000000000000000000000
60+
NONCE = 0000000000000000
61+
INITIAL_BLOCK_COUNTER = 4294967295
62+
PLAINTEXT = 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
63+
CIPHERTEXT = ace4cd09e294d1912d4ad205d06f95d9c2f2bfcf453e8753f128765b62215f4d92c74f2f626c6a640c0b1284d839ec81f1696281dafc3e684593937023b58b1d3db41d3aa0d329285de6f225e6e24bd59c9a17006943d5c9b680e3873bdc683a5819469899989690c281cd17c96159af0682b5b903468a61f50228cf09622b5a46f0f6efee15c8f1b198cb49d92b990867905159440cc723916dc0012826981039ce1766aa2542b05db3bd809ab142489d5dbfe1273e7399637b4b3213768aaa
64+
65+
COUNT = 9
66+
KEY = 0000000000000000000000000000000000000000000000000000000000000000
67+
NONCE = 0000000000000000
68+
INITIAL_BLOCK_COUNTER = 18446744073709551615
69+
PLAINTEXT = 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
70+
CIPHERTEXT = d7918cd8620cf832532652c04c01a553092cfb32e7b3f2f5467ae9674a2e9eec17368ec8027a357c0c51e6ea747121fec45284be0f099d2b3328845607b1768976b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee65869f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32ee7aed29b721769ce64e43d57133b074d839d531ed1f28510afb45ace10a1f4b794d6f
Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,25 @@
11
# The vectors are from RFC 7539 Appendix A.2. They are reformatted into NIST
2-
# form for our vector loaders.
2+
# form for our vector loaders, and adapted to use a 64/64 bit counter/nonce
3+
# split (matching our implementation), rather than the 32/96 split defined
4+
# in the RFC.
35

46
COUNT = 0
57
KEY = 0000000000000000000000000000000000000000000000000000000000000000
6-
NONCE = 000000000000000000000000
8+
NONCE = 0000000000000000
79
INITIAL_BLOCK_COUNTER = 0
810
PLAINTEXT = 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
911
CIPHERTEXT = 76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586
1012

1113
COUNT = 1
1214
KEY = 0000000000000000000000000000000000000000000000000000000000000001
13-
NONCE = 000000000000000000000002
15+
NONCE = 0000000000000002
1416
INITIAL_BLOCK_COUNTER = 1
1517
PLAINTEXT = 416e79207375626d697373696f6e20746f20746865204945544620696e74656e6465642062792074686520436f6e7472696275746f7220666f72207075626c69636174696f6e20617320616c6c206f722070617274206f6620616e204945544620496e7465726e65742d4472616674206f722052464320616e6420616e792073746174656d656e74206d6164652077697468696e2074686520636f6e74657874206f6620616e204945544620616374697669747920697320636f6e7369646572656420616e20224945544620436f6e747269627574696f6e222e20537563682073746174656d656e747320696e636c756465206f72616c2073746174656d656e747320696e20494554462073657373696f6e732c2061732077656c6c206173207772697474656e20616e6420656c656374726f6e696320636f6d6d756e69636174696f6e73206d61646520617420616e792074696d65206f7220706c6163652c207768696368206172652061646472657373656420746f
1618
CIPHERTEXT = a3fbf07df3fa2fde4f376ca23e82737041605d9f4f4f57bd8cff2c1d4b7955ec2a97948bd3722915c8f3d337f7d370050e9e96d647b7c39f56e031ca5eb6250d4042e02785ececfa4b4bb5e8ead0440e20b6e8db09d881a7c6132f420e52795042bdfa7773d8a9051447b3291ce1411c680465552aa6c405b7764d5e87bea85ad00f8449ed8f72d0d662ab052691ca66424bc86d2df80ea41f43abf937d3259dc4b2d0dfb48a6c9139ddd7f76966e928e635553ba76c5c879d7b35d49eb2e62b0871cdac638939e25e8a1e0ef9d5280fa8ca328b351c3c765989cbcf3daa8b6ccc3aaf9f3979c92b3720fc88dc95ed84a1be059c6499b9fda236e7e818b04b0bc39c1e876b193bfe5569753f88128cc08aaa9b63d1a16f80ef2554d7189c411f5869ca52c5b83fa36ff216b9c1d30062bebcfd2dc5bce0911934fda79a86f6e698ced759c3ff9b6477338f3da4f9cd8514ea9982ccafb341b2384dd902f3d1ab7ac61dd29c6f21ba5b862f3730e37cfdc4fd806c22f221
1719

1820
COUNT = 2
1921
KEY = 1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0
20-
NONCE = 000000000000000000000002
22+
NONCE = 0000000000000002
2123
INITIAL_BLOCK_COUNTER = 42
2224
PLAINTEXT = 2754776173206272696c6c69672c20616e642074686520736c6974687920746f7665730a446964206779726520616e642067696d626c6520696e2074686520776162653a0a416c6c206d696d737920776572652074686520626f726f676f7665732c0a416e6420746865206d6f6d65207261746873206f757467726162652e
2325
CIPHERTEXT = 62e6347f95ed87a45ffae7426f27a1df5fb69110044c0d73118effa95b01e5cf166d3df2d721caf9b21e5fb14c616871fd84c54f9d65b283196c7fe4f60553ebf39c6402c42234e32a356b3e764312a61a5532055716ead6962568f87d3f3f7704c6a8d1bcd1bf4d50d6154b6da731b187b58dfd728afa36757a797ac188d1

0 commit comments

Comments
 (0)