bip324_cipher.py raw

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