|
| 1 | +#!/usr/bin/env python3 |
| 2 | +# Copyright (c) 2022 The Bitcoin Core developers |
| 3 | +# Distributed under the MIT software license, see the accompanying |
| 4 | +# file COPYING or http://www.opensource.org/licenses/mit-license.php. |
| 5 | + |
| 6 | +"""Test-only implementation of ChaCha20 Poly1305 AEAD Construction in RFC 8439 and FSChaCha20Poly1305 for BIP 324 |
| 7 | +
|
| 8 | +It is designed for ease of understanding, not performance. |
| 9 | +
|
| 10 | +WARNING: This code is slow and trivially vulnerable to side channel attacks. Do not use for |
| 11 | +anything but tests. |
| 12 | +""" |
| 13 | + |
| 14 | +import unittest |
| 15 | + |
| 16 | +from .chacha20 import chacha20_block, REKEY_INTERVAL |
| 17 | +from .poly1305 import Poly1305 |
| 18 | + |
| 19 | + |
| 20 | +def pad16(x): |
| 21 | + if len(x) % 16 == 0: |
| 22 | + return b'' |
| 23 | + return b'\x00' * (16 - (len(x) % 16)) |
| 24 | + |
| 25 | + |
| 26 | +def aead_chacha20_poly1305_encrypt(key, nonce, aad, plaintext): |
| 27 | + """Encrypt a plaintext using ChaCha20Poly1305.""" |
| 28 | + ret = bytearray() |
| 29 | + msg_len = len(plaintext) |
| 30 | + for i in range((msg_len + 63) // 64): |
| 31 | + now = min(64, msg_len - 64 * i) |
| 32 | + keystream = chacha20_block(key, nonce, i + 1) |
| 33 | + for j in range(now): |
| 34 | + ret.append(plaintext[j + 64 * i] ^ keystream[j]) |
| 35 | + poly1305 = Poly1305(chacha20_block(key, nonce, 0)[:32]) |
| 36 | + mac_data = aad + pad16(aad) |
| 37 | + mac_data += ret + pad16(ret) |
| 38 | + mac_data += len(aad).to_bytes(8, 'little') + msg_len.to_bytes(8, 'little') |
| 39 | + ret += poly1305.tag(mac_data) |
| 40 | + return bytes(ret) |
| 41 | + |
| 42 | + |
| 43 | +def aead_chacha20_poly1305_decrypt(key, nonce, aad, ciphertext): |
| 44 | + """Decrypt a ChaCha20Poly1305 ciphertext.""" |
| 45 | + if len(ciphertext) < 16: |
| 46 | + return None |
| 47 | + msg_len = len(ciphertext) - 16 |
| 48 | + poly1305 = Poly1305(chacha20_block(key, nonce, 0)[:32]) |
| 49 | + mac_data = aad + pad16(aad) |
| 50 | + mac_data += ciphertext[:-16] + pad16(ciphertext[:-16]) |
| 51 | + mac_data += len(aad).to_bytes(8, 'little') + msg_len.to_bytes(8, 'little') |
| 52 | + if ciphertext[-16:] != poly1305.tag(mac_data): |
| 53 | + return None |
| 54 | + ret = bytearray() |
| 55 | + for i in range((msg_len + 63) // 64): |
| 56 | + now = min(64, msg_len - 64 * i) |
| 57 | + keystream = chacha20_block(key, nonce, i + 1) |
| 58 | + for j in range(now): |
| 59 | + ret.append(ciphertext[j + 64 * i] ^ keystream[j]) |
| 60 | + return bytes(ret) |
| 61 | + |
| 62 | + |
| 63 | +class FSChaCha20Poly1305: |
| 64 | + """Rekeying wrapper AEAD around ChaCha20Poly1305.""" |
| 65 | + def __init__(self, initial_key): |
| 66 | + self._key = initial_key |
| 67 | + self._packet_counter = 0 |
| 68 | + |
| 69 | + def _crypt(self, aad, text, is_decrypt): |
| 70 | + nonce = ((self._packet_counter % REKEY_INTERVAL).to_bytes(4, 'little') + |
| 71 | + (self._packet_counter // REKEY_INTERVAL).to_bytes(8, 'little')) |
| 72 | + if is_decrypt: |
| 73 | + ret = aead_chacha20_poly1305_decrypt(self._key, nonce, aad, text) |
| 74 | + else: |
| 75 | + ret = aead_chacha20_poly1305_encrypt(self._key, nonce, aad, text) |
| 76 | + if (self._packet_counter + 1) % REKEY_INTERVAL == 0: |
| 77 | + rekey_nonce = b"\xFF\xFF\xFF\xFF" + nonce[4:] |
| 78 | + self._key = aead_chacha20_poly1305_encrypt(self._key, rekey_nonce, b"", b"\x00" * 32)[:32] |
| 79 | + self._packet_counter += 1 |
| 80 | + return ret |
| 81 | + |
| 82 | + def decrypt(self, aad, ciphertext): |
| 83 | + return self._crypt(aad, ciphertext, True) |
| 84 | + |
| 85 | + def encrypt(self, aad, plaintext): |
| 86 | + return self._crypt(aad, plaintext, False) |
| 87 | + |
| 88 | + |
| 89 | +# Test vectors from RFC8439 consisting of plaintext, aad, 32 byte key, 12 byte nonce and ciphertext |
| 90 | +AEAD_TESTS = [ |
| 91 | + # RFC 8439 Example from section 2.8.2 |
| 92 | + ["4c616469657320616e642047656e746c656d656e206f662074686520636c6173" |
| 93 | + "73206f66202739393a204966204920636f756c64206f6666657220796f75206f" |
| 94 | + "6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73" |
| 95 | + "637265656e20776f756c642062652069742e", |
| 96 | + "50515253c0c1c2c3c4c5c6c7", |
| 97 | + "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f", |
| 98 | + [7, 0x4746454443424140], |
| 99 | + "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d6" |
| 100 | + "3dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b36" |
| 101 | + "92ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc" |
| 102 | + "3ff4def08e4b7a9de576d26586cec64b61161ae10b594f09e26a7e902ecbd060" |
| 103 | + "0691"], |
| 104 | + # RFC 8439 Test vector A.5 |
| 105 | + ["496e7465726e65742d4472616674732061726520647261667420646f63756d65" |
| 106 | + "6e74732076616c696420666f722061206d6178696d756d206f6620736978206d" |
| 107 | + "6f6e74687320616e64206d617920626520757064617465642c207265706c6163" |
| 108 | + "65642c206f72206f62736f6c65746564206279206f7468657220646f63756d65" |
| 109 | + "6e747320617420616e792074696d652e20497420697320696e617070726f7072" |
| 110 | + "6961746520746f2075736520496e7465726e65742d4472616674732061732072" |
| 111 | + "65666572656e6365206d6174657269616c206f7220746f206369746520746865" |
| 112 | + "6d206f74686572207468616e206173202fe2809c776f726b20696e2070726f67" |
| 113 | + "726573732e2fe2809d", |
| 114 | + "f33388860000000000004e91", |
| 115 | + "1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0", |
| 116 | + [0, 0x0807060504030201], |
| 117 | + "64a0861575861af460f062c79be643bd5e805cfd345cf389f108670ac76c8cb2" |
| 118 | + "4c6cfc18755d43eea09ee94e382d26b0bdb7b73c321b0100d4f03b7f355894cf" |
| 119 | + "332f830e710b97ce98c8a84abd0b948114ad176e008d33bd60f982b1ff37c855" |
| 120 | + "9797a06ef4f0ef61c186324e2b3506383606907b6a7c02b0f9f6157b53c867e4" |
| 121 | + "b9166c767b804d46a59b5216cde7a4e99040c5a40433225ee282a1b0a06c523e" |
| 122 | + "af4534d7f83fa1155b0047718cbc546a0d072b04b3564eea1b422273f548271a" |
| 123 | + "0bb2316053fa76991955ebd63159434ecebb4e466dae5a1073a6727627097a10" |
| 124 | + "49e617d91d361094fa68f0ff77987130305beaba2eda04df997b714d6c6f2c29" |
| 125 | + "a6ad5cb4022b02709beead9d67890cbb22392336fea1851f38"], |
| 126 | + # Test vectors exercising aad and plaintext which are multiples of 16 bytes. |
| 127 | + ["8d2d6a8befd9716fab35819eaac83b33269afb9f1a00fddf66095a6c0cd91951" |
| 128 | + "a6b7ad3db580be0674c3f0b55f618e34", |
| 129 | + "", |
| 130 | + "72ddc73f07101282bbbcf853b9012a9f9695fc5d36b303a97fd0845d0314e0c3", |
| 131 | + [0x3432b75f, 0xb3585537eb7f4024], |
| 132 | + "f760b8224fb2a317b1b07875092606131232a5b86ae142df5df1c846a7f6341a" |
| 133 | + "f2564483dd77f836be45e6230808ffe402a6f0a3e8be074b3d1f4ea8a7b09451"], |
| 134 | + ["", |
| 135 | + "36970d8a704c065de16250c18033de5a400520ac1b5842b24551e5823a3314f3" |
| 136 | + "946285171e04a81ebfbe3566e312e74ab80e94c7dd2ff4e10de0098a58d0f503", |
| 137 | + "77adda51d6730b9ad6c995658cbd49f581b2547e7c0c08fcc24ceec797461021", |
| 138 | + [0x1f90da88, 0x75dafa3ef84471a4], |
| 139 | + "aaae5bb81e8407c94b2ae86ae0c7efbe"], |
| 140 | +] |
| 141 | + |
| 142 | +FSAEAD_TESTS = [ |
| 143 | + ["d6a4cb04ef0f7c09c1866ed29dc24d820e75b0491032a51b4c3366f9ca35c19e" |
| 144 | + "a3047ec6be9d45f9637b63e1cf9eb4c2523a5aab7b851ebeba87199db0e839cf" |
| 145 | + "0d5c25e50168306377aedbe9089fd2463ded88b83211cf51b73b150608cc7a60" |
| 146 | + "0d0f11b9a742948482e1b109d8faf15b450aa7322e892fa2208c6691e3fecf4c" |
| 147 | + "711191b14d75a72147", |
| 148 | + "786cb9b6ebf44288974cf0", |
| 149 | + "5c9e1c3951a74fba66708bf9d2c217571684556b6a6a3573bff2847d38612654", |
| 150 | + 500, |
| 151 | + "9dcebbd3281ea3dd8e9a1ef7d55a97abd6743e56ebc0c190cb2c4e14160b385e" |
| 152 | + "0bf508dddf754bd02c7c208447c131ce23e47a4a14dfaf5dd8bc601323950f75" |
| 153 | + "4e05d46e9232f83fc5120fbbef6f5347a826ec79a93820718d4ec7a2b7cfaaa4" |
| 154 | + "4b21e16d726448b62f803811aff4f6d827ed78e738ce8a507b81a8ae13131192" |
| 155 | + "8039213de18a5120dc9b7370baca878f50ff254418de3da50c"], |
| 156 | + ["8349b7a2690b63d01204800c288ff1138a1d473c832c90ea8b3fc102d0bb3adc" |
| 157 | + "44261b247c7c3d6760bfbe979d061c305f46d94c0582ac3099f0bf249f8cb234", |
| 158 | + "", |
| 159 | + "3bd2093fcbcb0d034d8c569583c5425c1a53171ea299f8cc3bbf9ae3530adfce", |
| 160 | + 60000, |
| 161 | + "30a6757ff8439b975363f166a0fa0e36722ab35936abd704297948f45083f4d4" |
| 162 | + "99433137ce931f7fca28a0acd3bc30f57b550acbc21cbd45bbef0739d9caf30c" |
| 163 | + "14b94829deb27f0b1923a2af704ae5d6"], |
| 164 | +] |
| 165 | + |
| 166 | + |
| 167 | +class TestFrameworkAEAD(unittest.TestCase): |
| 168 | + def test_aead(self): |
| 169 | + """ChaCha20Poly1305 AEAD test vectors.""" |
| 170 | + for test_vector in AEAD_TESTS: |
| 171 | + hex_plain, hex_aad, hex_key, hex_nonce, hex_cipher = test_vector |
| 172 | + plain = bytes.fromhex(hex_plain) |
| 173 | + aad = bytes.fromhex(hex_aad) |
| 174 | + key = bytes.fromhex(hex_key) |
| 175 | + nonce = hex_nonce[0].to_bytes(4, 'little') + hex_nonce[1].to_bytes(8, 'little') |
| 176 | + |
| 177 | + ciphertext = aead_chacha20_poly1305_encrypt(key, nonce, aad, plain) |
| 178 | + self.assertEqual(hex_cipher, ciphertext.hex()) |
| 179 | + plaintext = aead_chacha20_poly1305_decrypt(key, nonce, aad, ciphertext) |
| 180 | + self.assertEqual(plain, plaintext) |
| 181 | + |
| 182 | + def test_fschacha20poly1305aead(self): |
| 183 | + "FSChaCha20Poly1305 AEAD test vectors." |
| 184 | + for test_vector in FSAEAD_TESTS: |
| 185 | + hex_plain, hex_aad, hex_key, msg_idx, hex_cipher = test_vector |
| 186 | + plain = bytes.fromhex(hex_plain) |
| 187 | + aad = bytes.fromhex(hex_aad) |
| 188 | + key = bytes.fromhex(hex_key) |
| 189 | + |
| 190 | + enc_aead = FSChaCha20Poly1305(key) |
| 191 | + dec_aead = FSChaCha20Poly1305(key) |
| 192 | + |
| 193 | + for _ in range(msg_idx): |
| 194 | + enc_aead.encrypt(b"", b"") |
| 195 | + ciphertext = enc_aead.encrypt(aad, plain) |
| 196 | + self.assertEqual(hex_cipher, ciphertext.hex()) |
| 197 | + |
| 198 | + for _ in range(msg_idx): |
| 199 | + dec_aead.decrypt(b"", bytes(16)) |
| 200 | + plaintext = dec_aead.decrypt(aad, ciphertext) |
| 201 | + self.assertEqual(plain, plaintext) |
0 commit comments