Skip to content

Commit aa8cee9

Browse files
committed
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.
1 parent 0fee267 commit aa8cee9

File tree

3 files changed

+159
-0
lines changed

3 files changed

+159
-0
lines changed

src/crypto/chacha20poly1305.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <crypto/chacha20.h>
99
#include <crypto/poly1305.h>
1010
#include <span.h>
11+
#include <support/cleanse.h>
1112

1213
#include <assert.h>
1314
#include <cstdint>
@@ -99,3 +100,41 @@ bool AEADChaCha20Poly1305::Decrypt(Span<const std::byte> cipher, Span<const std:
99100
m_chacha20.Crypt(UCharCast(cipher.data()), UCharCast(plain.data()), plain.size());
100101
return true;
101102
}
103+
104+
void AEADChaCha20Poly1305::Keystream(Nonce96 nonce, Span<std::byte> keystream) noexcept
105+
{
106+
// Skip the first output block, as it's used for generating the poly1305 key.
107+
m_chacha20.Seek64(nonce, 1);
108+
m_chacha20.Keystream(UCharCast(keystream.data()), keystream.size());
109+
}
110+
111+
void FSChaCha20Poly1305::NextPacket() noexcept
112+
{
113+
if (++m_packet_counter == m_rekey_interval) {
114+
// Generate a full block of keystream, to avoid needing the ChaCha20 buffer, even though
115+
// we only need KEYLEN (32) bytes.
116+
std::byte one_block[64];
117+
m_aead.Keystream({0xFFFFFFFF, m_rekey_counter}, one_block);
118+
// Switch keys.
119+
m_aead.SetKey(Span{one_block}.first(KEYLEN));
120+
// Wipe the generated keystream (a copy remains inside m_aead, which will be cleaned up
121+
// once it cycles again, or is destroyed).
122+
memory_cleanse(one_block, sizeof(one_block));
123+
// Update counters.
124+
m_packet_counter = 0;
125+
++m_rekey_counter;
126+
}
127+
}
128+
129+
void FSChaCha20Poly1305::Encrypt(Span<const std::byte> plain, Span<const std::byte> aad, Span<std::byte> cipher) noexcept
130+
{
131+
m_aead.Encrypt(plain, aad, {m_packet_counter, m_rekey_counter}, cipher);
132+
NextPacket();
133+
}
134+
135+
bool FSChaCha20Poly1305::Decrypt(Span<const std::byte> cipher, Span<const std::byte> aad, Span<std::byte> plain) noexcept
136+
{
137+
bool ret = m_aead.Decrypt(cipher, aad, {m_packet_counter, m_rekey_counter}, plain);
138+
NextPacket();
139+
return ret;
140+
}

src/crypto/chacha20poly1305.h

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,68 @@ class AEADChaCha20Poly1305
4646
* Requires cipher.size() = plain.size() + EXPANSION.
4747
*/
4848
bool Decrypt(Span<const std::byte> cipher, Span<const std::byte> aad, Nonce96 nonce, Span<std::byte> plain) noexcept;
49+
50+
/** Get a number of keystream bytes from the underlying stream cipher.
51+
*
52+
* This is equivalent to Encrypt() with plain set to that many zero bytes, and dropping the
53+
* last EXPANSION bytes off the result.
54+
*/
55+
void Keystream(Nonce96 nonce, Span<std::byte> keystream) noexcept;
56+
};
57+
58+
/** Forward-secure wrapper around AEADChaCha20Poly1305.
59+
*
60+
* This implements an AEAD which automatically increments the nonce on every encryption or
61+
* decryption, and cycles keys after a predetermined number of encryptions or decryptions.
62+
*
63+
* See BIP324 for details.
64+
*/
65+
class FSChaCha20Poly1305
66+
{
67+
private:
68+
/** Internal AEAD. */
69+
AEADChaCha20Poly1305 m_aead;
70+
71+
/** Every how many iterations this cipher rekeys. */
72+
const uint32_t m_rekey_interval;
73+
74+
/** The number of encryptions/decryptions since the last rekey. */
75+
uint32_t m_packet_counter{0};
76+
77+
/** The number of rekeys performed so far. */
78+
uint64_t m_rekey_counter{0};
79+
80+
/** Update counters (and if necessary, key) to transition to the next message. */
81+
void NextPacket() noexcept;
82+
83+
public:
84+
/** Length of keys expected by the constructor. */
85+
static constexpr auto KEYLEN = AEADChaCha20Poly1305::KEYLEN;
86+
87+
/** Expansion when encrypting. */
88+
static constexpr auto EXPANSION = AEADChaCha20Poly1305::EXPANSION;
89+
90+
// No copy or move to protect the secret.
91+
FSChaCha20Poly1305(const FSChaCha20Poly1305&) = delete;
92+
FSChaCha20Poly1305(FSChaCha20Poly1305&&) = delete;
93+
FSChaCha20Poly1305& operator=(const FSChaCha20Poly1305&) = delete;
94+
FSChaCha20Poly1305& operator=(FSChaCha20Poly1305&&) = delete;
95+
96+
/** Construct an FSChaCha20Poly1305 cipher that rekeys every rekey_interval operations. */
97+
FSChaCha20Poly1305(Span<const std::byte> key, uint32_t rekey_interval) noexcept :
98+
m_aead(key), m_rekey_interval(rekey_interval) {}
99+
100+
/** Encrypt a message with a specified aad.
101+
*
102+
* Requires cipher.size() = plain.size() + EXPANSION.
103+
*/
104+
void Encrypt(Span<const std::byte> plain, Span<const std::byte> aad, Span<std::byte> cipher) noexcept;
105+
106+
/** Decrypt a message with a specified aad. Returns true if valid.
107+
*
108+
* Requires cipher.size() = plain.size() + EXPANSION.
109+
*/
110+
bool Decrypt(Span<const std::byte> cipher, Span<const std::byte> aad, Span<std::byte> plain) noexcept;
49111
};
50112

51113
#endif // BITCOIN_CRYPTO_CHACHA20POLY1305_H

src/test/crypto_tests.cpp

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,41 @@ static void TestChaCha20Poly1305(const std::string& plain_hex, const std::string
264264
bool ret = aead.Decrypt(cipher, aad, nonce, decipher);
265265
BOOST_CHECK(ret);
266266
BOOST_CHECK(decipher == plain);
267+
268+
std::vector<std::byte> keystream(plain.size());
269+
aead.Keystream(nonce, keystream);
270+
for (size_t i = 0; i < plain.size(); ++i) {
271+
BOOST_CHECK_EQUAL(plain[i] ^ keystream[i], cipher[i]);
272+
}
273+
}
274+
275+
static void TestFSChaCha20Poly1305(const std::string& plain_hex, const std::string& aad_hex, const std::string& key_hex, uint64_t msg_idx, const std::string& cipher_hex)
276+
{
277+
auto plain = ParseHex<std::byte>(plain_hex);
278+
auto aad = ParseHex<std::byte>(aad_hex);
279+
auto key = ParseHex<std::byte>(key_hex);
280+
auto expected_cipher = ParseHex<std::byte>(cipher_hex);
281+
std::vector<std::byte> cipher(plain.size() + FSChaCha20Poly1305::EXPANSION);
282+
283+
FSChaCha20Poly1305 enc_aead{key, 224};
284+
for (uint64_t i = 0; i < msg_idx; ++i) {
285+
std::byte dummy_tag[FSChaCha20Poly1305::EXPANSION] = {{}};
286+
enc_aead.Encrypt(Span{dummy_tag}.first(0), Span{dummy_tag}.first(0), dummy_tag);
287+
}
288+
289+
enc_aead.Encrypt(plain, aad, cipher);
290+
BOOST_CHECK(cipher == expected_cipher);
291+
292+
FSChaCha20Poly1305 dec_aead{key, 224};
293+
for (uint64_t i = 0; i < msg_idx; ++i) {
294+
std::byte dummy_tag[FSChaCha20Poly1305::EXPANSION] = {{}};
295+
dec_aead.Decrypt(dummy_tag, Span{dummy_tag}.first(0), Span{dummy_tag}.first(0));
296+
}
297+
298+
std::vector<std::byte> decipher(cipher.size() - AEADChaCha20Poly1305::EXPANSION);
299+
bool ret = dec_aead.Decrypt(cipher, aad, decipher);
300+
BOOST_CHECK(ret);
301+
BOOST_CHECK(decipher == plain);
267302
}
268303

269304
static void TestHKDF_SHA256_32(const std::string &ikm_hex, const std::string &salt_hex, const std::string &info_hex, const std::string &okm_check_hex) {
@@ -947,6 +982,29 @@ BOOST_AUTO_TEST_CASE(chacha20poly1305_testvectors)
947982
"77adda51d6730b9ad6c995658cbd49f581b2547e7c0c08fcc24ceec797461021",
948983
{0x1f90da88, 0x75dafa3ef84471a4},
949984
"aaae5bb81e8407c94b2ae86ae0c7efbe");
985+
986+
// FSChaCha20Poly1305 tests.
987+
TestFSChaCha20Poly1305("d6a4cb04ef0f7c09c1866ed29dc24d820e75b0491032a51b4c3366f9ca35c19e"
988+
"a3047ec6be9d45f9637b63e1cf9eb4c2523a5aab7b851ebeba87199db0e839cf"
989+
"0d5c25e50168306377aedbe9089fd2463ded88b83211cf51b73b150608cc7a60"
990+
"0d0f11b9a742948482e1b109d8faf15b450aa7322e892fa2208c6691e3fecf4c"
991+
"711191b14d75a72147",
992+
"786cb9b6ebf44288974cf0",
993+
"5c9e1c3951a74fba66708bf9d2c217571684556b6a6a3573bff2847d38612654",
994+
500,
995+
"9dcebbd3281ea3dd8e9a1ef7d55a97abd6743e56ebc0c190cb2c4e14160b385e"
996+
"0bf508dddf754bd02c7c208447c131ce23e47a4a14dfaf5dd8bc601323950f75"
997+
"4e05d46e9232f83fc5120fbbef6f5347a826ec79a93820718d4ec7a2b7cfaaa4"
998+
"4b21e16d726448b62f803811aff4f6d827ed78e738ce8a507b81a8ae13131192"
999+
"8039213de18a5120dc9b7370baca878f50ff254418de3da50c");
1000+
TestFSChaCha20Poly1305("8349b7a2690b63d01204800c288ff1138a1d473c832c90ea8b3fc102d0bb3adc"
1001+
"44261b247c7c3d6760bfbe979d061c305f46d94c0582ac3099f0bf249f8cb234",
1002+
"",
1003+
"3bd2093fcbcb0d034d8c569583c5425c1a53171ea299f8cc3bbf9ae3530adfce",
1004+
60000,
1005+
"30a6757ff8439b975363f166a0fa0e36722ab35936abd704297948f45083f4d4"
1006+
"99433137ce931f7fca28a0acd3bc30f57b550acbc21cbd45bbef0739d9caf30c"
1007+
"14b94829deb27f0b1923a2af704ae5d6");
9501008
}
9511009

9521010
BOOST_AUTO_TEST_CASE(hkdf_hmac_sha256_l32_tests)

0 commit comments

Comments
 (0)