diff options
author | Pieter Wuille <pieter@wuille.net> | 2023-06-29 14:55:16 -0400 |
---|---|---|
committer | Pieter Wuille <pieter@wuille.net> | 2023-07-26 16:55:05 -0400 |
commit | aa8cee93342ee857931afec9af3ff5dbd8ce7749 (patch) | |
tree | 3bc9c334f70cc1f6dbc02bd06785ccf2e4d3fc2c /src/crypto | |
parent | 0fee267792eb8cbdd48ad78f1712420b5d8d905b (diff) | |
download | bitcoin-aa8cee93342ee857931afec9af3ff5dbd8ce7749.tar.xz |
crypto: add FSChaCha20Poly1305, rekeying wrapper around ChaCha20Poly1305
This adds the FSChaCha20Poly1305 AEAD as specified in BIP324, a wrapper
around the ChaCha20Poly1305 AEAD (as specified in RFC8439 section 2.8) which
automatically rekeys every N messages, and automatically increments the nonce
every message.
Diffstat (limited to 'src/crypto')
-rw-r--r-- | src/crypto/chacha20poly1305.cpp | 39 | ||||
-rw-r--r-- | src/crypto/chacha20poly1305.h | 62 |
2 files changed, 101 insertions, 0 deletions
diff --git a/src/crypto/chacha20poly1305.cpp b/src/crypto/chacha20poly1305.cpp index 5f27857213..c3f8fe9e64 100644 --- a/src/crypto/chacha20poly1305.cpp +++ b/src/crypto/chacha20poly1305.cpp @@ -8,6 +8,7 @@ #include <crypto/chacha20.h> #include <crypto/poly1305.h> #include <span.h> +#include <support/cleanse.h> #include <assert.h> #include <cstdint> @@ -99,3 +100,41 @@ bool AEADChaCha20Poly1305::Decrypt(Span<const std::byte> cipher, Span<const std: m_chacha20.Crypt(UCharCast(cipher.data()), UCharCast(plain.data()), plain.size()); return true; } + +void AEADChaCha20Poly1305::Keystream(Nonce96 nonce, Span<std::byte> keystream) noexcept +{ + // Skip the first output block, as it's used for generating the poly1305 key. + m_chacha20.Seek64(nonce, 1); + m_chacha20.Keystream(UCharCast(keystream.data()), keystream.size()); +} + +void FSChaCha20Poly1305::NextPacket() noexcept +{ + if (++m_packet_counter == m_rekey_interval) { + // Generate a full block of keystream, to avoid needing the ChaCha20 buffer, even though + // we only need KEYLEN (32) bytes. + std::byte one_block[64]; + m_aead.Keystream({0xFFFFFFFF, m_rekey_counter}, one_block); + // Switch keys. + m_aead.SetKey(Span{one_block}.first(KEYLEN)); + // Wipe the generated keystream (a copy remains inside m_aead, which will be cleaned up + // once it cycles again, or is destroyed). + memory_cleanse(one_block, sizeof(one_block)); + // Update counters. + m_packet_counter = 0; + ++m_rekey_counter; + } +} + +void FSChaCha20Poly1305::Encrypt(Span<const std::byte> plain, Span<const std::byte> aad, Span<std::byte> cipher) noexcept +{ + m_aead.Encrypt(plain, aad, {m_packet_counter, m_rekey_counter}, cipher); + NextPacket(); +} + +bool FSChaCha20Poly1305::Decrypt(Span<const std::byte> cipher, Span<const std::byte> aad, Span<std::byte> plain) noexcept +{ + bool ret = m_aead.Decrypt(cipher, aad, {m_packet_counter, m_rekey_counter}, plain); + NextPacket(); + return ret; +} diff --git a/src/crypto/chacha20poly1305.h b/src/crypto/chacha20poly1305.h index 0007ee63a9..c4ec978df8 100644 --- a/src/crypto/chacha20poly1305.h +++ b/src/crypto/chacha20poly1305.h @@ -46,6 +46,68 @@ public: * Requires cipher.size() = plain.size() + EXPANSION. */ bool Decrypt(Span<const std::byte> cipher, Span<const std::byte> aad, Nonce96 nonce, Span<std::byte> plain) noexcept; + + /** Get a number of keystream bytes from the underlying stream cipher. + * + * This is equivalent to Encrypt() with plain set to that many zero bytes, and dropping the + * last EXPANSION bytes off the result. + */ + void Keystream(Nonce96 nonce, Span<std::byte> keystream) noexcept; +}; + +/** Forward-secure wrapper around AEADChaCha20Poly1305. + * + * This implements an AEAD which automatically increments the nonce on every encryption or + * decryption, and cycles keys after a predetermined number of encryptions or decryptions. + * + * See BIP324 for details. + */ +class FSChaCha20Poly1305 +{ +private: + /** Internal AEAD. */ + AEADChaCha20Poly1305 m_aead; + + /** Every how many iterations this cipher rekeys. */ + const uint32_t m_rekey_interval; + + /** The number of encryptions/decryptions since the last rekey. */ + uint32_t m_packet_counter{0}; + + /** The number of rekeys performed so far. */ + uint64_t m_rekey_counter{0}; + + /** Update counters (and if necessary, key) to transition to the next message. */ + void NextPacket() noexcept; + +public: + /** Length of keys expected by the constructor. */ + static constexpr auto KEYLEN = AEADChaCha20Poly1305::KEYLEN; + + /** Expansion when encrypting. */ + static constexpr auto EXPANSION = AEADChaCha20Poly1305::EXPANSION; + + // No copy or move to protect the secret. + FSChaCha20Poly1305(const FSChaCha20Poly1305&) = delete; + FSChaCha20Poly1305(FSChaCha20Poly1305&&) = delete; + FSChaCha20Poly1305& operator=(const FSChaCha20Poly1305&) = delete; + FSChaCha20Poly1305& operator=(FSChaCha20Poly1305&&) = delete; + + /** Construct an FSChaCha20Poly1305 cipher that rekeys every rekey_interval operations. */ + FSChaCha20Poly1305(Span<const std::byte> key, uint32_t rekey_interval) noexcept : + m_aead(key), m_rekey_interval(rekey_interval) {} + + /** Encrypt a message with a specified aad. + * + * Requires cipher.size() = plain.size() + EXPANSION. + */ + void Encrypt(Span<const std::byte> plain, Span<const std::byte> aad, Span<std::byte> cipher) noexcept; + + /** Decrypt a message with a specified aad. Returns true if valid. + * + * Requires cipher.size() = plain.size() + EXPANSION. + */ + bool Decrypt(Span<const std::byte> cipher, Span<const std::byte> aad, Span<std::byte> plain) noexcept; }; #endif // BITCOIN_CRYPTO_CHACHA20POLY1305_H |