chacha20poly1305.cpp
1 // Copyright (c) 2023 The Bitcoin Core developers 2 // Distributed under the MIT software license, see the accompanying 3 // file COPYING or http://www.opensource.org/licenses/mit-license.php. 4 5 #if defined(HAVE_CONFIG_H) 6 #include <config/bitcoin-config.h> 7 #endif 8 9 #include <crypto/chacha20poly1305.h> 10 11 #include <crypto/common.h> 12 #include <crypto/chacha20.h> 13 #include <crypto/poly1305.h> 14 #include <span.h> 15 #include <support/cleanse.h> 16 17 #include <assert.h> 18 #include <cstddef> 19 20 AEADChaCha20Poly1305::AEADChaCha20Poly1305(Span<const std::byte> key) noexcept : m_chacha20(key) 21 { 22 assert(key.size() == KEYLEN); 23 } 24 25 void AEADChaCha20Poly1305::SetKey(Span<const std::byte> key) noexcept 26 { 27 assert(key.size() == KEYLEN); 28 m_chacha20.SetKey(key); 29 } 30 31 namespace { 32 33 #ifndef HAVE_TIMINGSAFE_BCMP 34 #define HAVE_TIMINGSAFE_BCMP 35 36 int timingsafe_bcmp(const unsigned char* b1, const unsigned char* b2, size_t n) noexcept 37 { 38 const unsigned char *p1 = b1, *p2 = b2; 39 int ret = 0; 40 for (; n > 0; n--) 41 ret |= *p1++ ^ *p2++; 42 return (ret != 0); 43 } 44 45 #endif 46 47 /** Compute poly1305 tag. chacha20 must be set to the right nonce, block 0. Will be at block 1 after. */ 48 void ComputeTag(ChaCha20& chacha20, Span<const std::byte> aad, Span<const std::byte> cipher, Span<std::byte> tag) noexcept 49 { 50 static const std::byte PADDING[16] = {{}}; 51 52 // Get block of keystream (use a full 64 byte buffer to avoid the need for chacha20's own buffering). 53 std::byte first_block[ChaCha20Aligned::BLOCKLEN]; 54 chacha20.Keystream(first_block); 55 56 // Use the first 32 bytes of the first keystream block as poly1305 key. 57 Poly1305 poly1305{Span{first_block}.first(Poly1305::KEYLEN)}; 58 59 // Compute tag: 60 // - Process the padded AAD with Poly1305. 61 const unsigned aad_padding_length = (16 - (aad.size() % 16)) % 16; 62 poly1305.Update(aad).Update(Span{PADDING}.first(aad_padding_length)); 63 // - Process the padded ciphertext with Poly1305. 64 const unsigned cipher_padding_length = (16 - (cipher.size() % 16)) % 16; 65 poly1305.Update(cipher).Update(Span{PADDING}.first(cipher_padding_length)); 66 // - Process the AAD and plaintext length with Poly1305. 67 std::byte length_desc[Poly1305::TAGLEN]; 68 WriteLE64(UCharCast(length_desc), aad.size()); 69 WriteLE64(UCharCast(length_desc + 8), cipher.size()); 70 poly1305.Update(length_desc); 71 72 // Output tag. 73 poly1305.Finalize(tag); 74 } 75 76 } // namespace 77 78 void AEADChaCha20Poly1305::Encrypt(Span<const std::byte> plain1, Span<const std::byte> plain2, Span<const std::byte> aad, Nonce96 nonce, Span<std::byte> cipher) noexcept 79 { 80 assert(cipher.size() == plain1.size() + plain2.size() + EXPANSION); 81 82 // Encrypt using ChaCha20 (starting at block 1). 83 m_chacha20.Seek(nonce, 1); 84 m_chacha20.Crypt(plain1, cipher.first(plain1.size())); 85 m_chacha20.Crypt(plain2, cipher.subspan(plain1.size()).first(plain2.size())); 86 87 // Seek to block 0, and compute tag using key drawn from there. 88 m_chacha20.Seek(nonce, 0); 89 ComputeTag(m_chacha20, aad, cipher.first(cipher.size() - EXPANSION), cipher.last(EXPANSION)); 90 } 91 92 bool AEADChaCha20Poly1305::Decrypt(Span<const std::byte> cipher, Span<const std::byte> aad, Nonce96 nonce, Span<std::byte> plain1, Span<std::byte> plain2) noexcept 93 { 94 assert(cipher.size() == plain1.size() + plain2.size() + EXPANSION); 95 96 // Verify tag (using key drawn from block 0). 97 m_chacha20.Seek(nonce, 0); 98 std::byte expected_tag[EXPANSION]; 99 ComputeTag(m_chacha20, aad, cipher.first(cipher.size() - EXPANSION), expected_tag); 100 if (timingsafe_bcmp(UCharCast(expected_tag), UCharCast(cipher.last(EXPANSION).data()), EXPANSION)) return false; 101 102 // Decrypt (starting at block 1). 103 m_chacha20.Crypt(cipher.first(plain1.size()), plain1); 104 m_chacha20.Crypt(cipher.subspan(plain1.size()).first(plain2.size()), plain2); 105 return true; 106 } 107 108 void AEADChaCha20Poly1305::Keystream(Nonce96 nonce, Span<std::byte> keystream) noexcept 109 { 110 // Skip the first output block, as it's used for generating the poly1305 key. 111 m_chacha20.Seek(nonce, 1); 112 m_chacha20.Keystream(keystream); 113 } 114 115 void FSChaCha20Poly1305::NextPacket() noexcept 116 { 117 if (++m_packet_counter == m_rekey_interval) { 118 // Generate a full block of keystream, to avoid needing the ChaCha20 buffer, even though 119 // we only need KEYLEN (32) bytes. 120 std::byte one_block[ChaCha20Aligned::BLOCKLEN]; 121 m_aead.Keystream({0xFFFFFFFF, m_rekey_counter}, one_block); 122 // Switch keys. 123 m_aead.SetKey(Span{one_block}.first(KEYLEN)); 124 // Wipe the generated keystream (a copy remains inside m_aead, which will be cleaned up 125 // once it cycles again, or is destroyed). 126 memory_cleanse(one_block, sizeof(one_block)); 127 // Update counters. 128 m_packet_counter = 0; 129 ++m_rekey_counter; 130 } 131 } 132 133 void FSChaCha20Poly1305::Encrypt(Span<const std::byte> plain1, Span<const std::byte> plain2, Span<const std::byte> aad, Span<std::byte> cipher) noexcept 134 { 135 m_aead.Encrypt(plain1, plain2, aad, {m_packet_counter, m_rekey_counter}, cipher); 136 NextPacket(); 137 } 138 139 bool FSChaCha20Poly1305::Decrypt(Span<const std::byte> cipher, Span<const std::byte> aad, Span<std::byte> plain1, Span<std::byte> plain2) noexcept 140 { 141 bool ret = m_aead.Decrypt(cipher, aad, {m_packet_counter, m_rekey_counter}, plain1, plain2); 142 NextPacket(); 143 return ret; 144 }