Skip to content

Commit c91cedf

Browse files
committed
crypto: support split plaintext in ChaCha20Poly1305 Encrypt/Decrypt
1 parent af2b44c commit c91cedf

File tree

3 files changed

+115
-42
lines changed

3 files changed

+115
-42
lines changed

Diff for: src/crypto/chacha20poly1305.cpp

+15-13
Original file line numberDiff line numberDiff line change
@@ -73,31 +73,33 @@ void ComputeTag(ChaCha20& chacha20, Span<const std::byte> aad, Span<const std::b
7373

7474
} // namespace
7575

76-
void AEADChaCha20Poly1305::Encrypt(Span<const std::byte> plain, Span<const std::byte> aad, Nonce96 nonce, Span<std::byte> cipher) noexcept
76+
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
7777
{
78-
assert(cipher.size() == plain.size() + EXPANSION);
78+
assert(cipher.size() == plain1.size() + plain2.size() + EXPANSION);
7979

8080
// Encrypt using ChaCha20 (starting at block 1).
8181
m_chacha20.Seek64(nonce, 1);
82-
m_chacha20.Crypt(UCharCast(plain.data()), UCharCast(cipher.data()), plain.size());
82+
m_chacha20.Crypt(UCharCast(plain1.data()), UCharCast(cipher.data()), plain1.size());
83+
m_chacha20.Crypt(UCharCast(plain2.data()), UCharCast(cipher.data() + plain1.size()), plain2.size());
8384

8485
// Seek to block 0, and compute tag using key drawn from there.
8586
m_chacha20.Seek64(nonce, 0);
86-
ComputeTag(m_chacha20, aad, cipher.first(plain.size()), cipher.last(EXPANSION));
87+
ComputeTag(m_chacha20, aad, cipher.first(cipher.size() - EXPANSION), cipher.last(EXPANSION));
8788
}
8889

89-
bool AEADChaCha20Poly1305::Decrypt(Span<const std::byte> cipher, Span<const std::byte> aad, Nonce96 nonce, Span<std::byte> plain) noexcept
90+
bool AEADChaCha20Poly1305::Decrypt(Span<const std::byte> cipher, Span<const std::byte> aad, Nonce96 nonce, Span<std::byte> plain1, Span<std::byte> plain2) noexcept
9091
{
91-
assert(cipher.size() == plain.size() + EXPANSION);
92+
assert(cipher.size() == plain1.size() + plain2.size() + EXPANSION);
9293

9394
// Verify tag (using key drawn from block 0).
9495
m_chacha20.Seek64(nonce, 0);
9596
std::byte expected_tag[EXPANSION];
96-
ComputeTag(m_chacha20, aad, cipher.first(plain.size()), expected_tag);
97-
if (timingsafe_bcmp(UCharCast(expected_tag), UCharCast(cipher.data() + plain.size()), EXPANSION)) return false;
97+
ComputeTag(m_chacha20, aad, cipher.first(cipher.size() - EXPANSION), expected_tag);
98+
if (timingsafe_bcmp(UCharCast(expected_tag), UCharCast(cipher.data() + cipher.size() - EXPANSION), EXPANSION)) return false;
9899

99100
// Decrypt (starting at block 1).
100-
m_chacha20.Crypt(UCharCast(cipher.data()), UCharCast(plain.data()), plain.size());
101+
m_chacha20.Crypt(UCharCast(cipher.data()), UCharCast(plain1.data()), plain1.size());
102+
m_chacha20.Crypt(UCharCast(cipher.data() + plain1.size()), UCharCast(plain2.data()), plain2.size());
101103
return true;
102104
}
103105

@@ -126,15 +128,15 @@ void FSChaCha20Poly1305::NextPacket() noexcept
126128
}
127129
}
128130

129-
void FSChaCha20Poly1305::Encrypt(Span<const std::byte> plain, Span<const std::byte> aad, Span<std::byte> cipher) noexcept
131+
void FSChaCha20Poly1305::Encrypt(Span<const std::byte> plain1, Span<const std::byte> plain2, Span<const std::byte> aad, Span<std::byte> cipher) noexcept
130132
{
131-
m_aead.Encrypt(plain, aad, {m_packet_counter, m_rekey_counter}, cipher);
133+
m_aead.Encrypt(plain1, plain2, aad, {m_packet_counter, m_rekey_counter}, cipher);
132134
NextPacket();
133135
}
134136

135-
bool FSChaCha20Poly1305::Decrypt(Span<const std::byte> cipher, Span<const std::byte> aad, Span<std::byte> plain) noexcept
137+
bool FSChaCha20Poly1305::Decrypt(Span<const std::byte> cipher, Span<const std::byte> aad, Span<std::byte> plain1, Span<std::byte> plain2) noexcept
136138
{
137-
bool ret = m_aead.Decrypt(cipher, aad, {m_packet_counter, m_rekey_counter}, plain);
139+
bool ret = m_aead.Decrypt(cipher, aad, {m_packet_counter, m_rekey_counter}, plain1, plain2);
138140
NextPacket();
139141
return ret;
140142
}

Diff for: src/crypto/chacha20poly1305.h

+40-4
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,31 @@ class AEADChaCha20Poly1305
3939
*
4040
* Requires cipher.size() = plain.size() + EXPANSION.
4141
*/
42-
void Encrypt(Span<const std::byte> plain, Span<const std::byte> aad, Nonce96 nonce, Span<std::byte> cipher) noexcept;
42+
void Encrypt(Span<const std::byte> plain, Span<const std::byte> aad, Nonce96 nonce, Span<std::byte> cipher) noexcept
43+
{
44+
Encrypt(plain, {}, aad, nonce, cipher);
45+
}
46+
47+
/** Encrypt a message (given split into plain1 + plain2) with a specified 96-bit nonce and aad.
48+
*
49+
* Requires cipher.size() = plain1.size() + plain2.size() + EXPANSION.
50+
*/
51+
void Encrypt(Span<const std::byte> plain1, Span<const std::byte> plain2, Span<const std::byte> aad, Nonce96 nonce, Span<std::byte> cipher) noexcept;
4352

4453
/** Decrypt a message with a specified 96-bit nonce and aad. Returns true if valid.
4554
*
4655
* Requires cipher.size() = plain.size() + EXPANSION.
4756
*/
48-
bool Decrypt(Span<const std::byte> cipher, Span<const std::byte> aad, Nonce96 nonce, Span<std::byte> plain) noexcept;
57+
bool Decrypt(Span<const std::byte> cipher, Span<const std::byte> aad, Nonce96 nonce, Span<std::byte> plain) noexcept
58+
{
59+
return Decrypt(cipher, aad, nonce, plain, {});
60+
}
61+
62+
/** Decrypt a message with a specified 96-bit nonce and aad and split the result. Returns true if valid.
63+
*
64+
* Requires cipher.size() = plain1.size() + plain2.size() + EXPANSION.
65+
*/
66+
bool Decrypt(Span<const std::byte> cipher, Span<const std::byte> aad, Nonce96 nonce, Span<std::byte> plain1, Span<std::byte> plain2) noexcept;
4967

5068
/** Get a number of keystream bytes from the underlying stream cipher.
5169
*
@@ -101,13 +119,31 @@ class FSChaCha20Poly1305
101119
*
102120
* Requires cipher.size() = plain.size() + EXPANSION.
103121
*/
104-
void Encrypt(Span<const std::byte> plain, Span<const std::byte> aad, Span<std::byte> cipher) noexcept;
122+
void Encrypt(Span<const std::byte> plain, Span<const std::byte> aad, Span<std::byte> cipher) noexcept
123+
{
124+
Encrypt(plain, {}, aad, cipher);
125+
}
126+
127+
/** Encrypt a message (given split into plain1 + plain2) with a specified aad.
128+
*
129+
* Requires cipher.size() = plain.size() + EXPANSION.
130+
*/
131+
void Encrypt(Span<const std::byte> plain1, Span<const std::byte> plain2, Span<const std::byte> aad, Span<std::byte> cipher) noexcept;
105132

106133
/** Decrypt a message with a specified aad. Returns true if valid.
107134
*
108135
* Requires cipher.size() = plain.size() + EXPANSION.
109136
*/
110-
bool Decrypt(Span<const std::byte> cipher, Span<const std::byte> aad, Span<std::byte> plain) noexcept;
137+
bool Decrypt(Span<const std::byte> cipher, Span<const std::byte> aad, Span<std::byte> plain) noexcept
138+
{
139+
return Decrypt(cipher, aad, plain, {});
140+
}
141+
142+
/** Decrypt a message with a specified aad and split the result. Returns true if valid.
143+
*
144+
* Requires cipher.size() = plain1.size() + plain2.size() + EXPANSION.
145+
*/
146+
bool Decrypt(Span<const std::byte> cipher, Span<const std::byte> aad, Span<std::byte> plain1, Span<std::byte> plain2) noexcept;
111147
};
112148

113149
#endif // BITCOIN_CRYPTO_CHACHA20POLY1305_H

Diff for: src/test/crypto_tests.cpp

+60-25
Original file line numberDiff line numberDiff line change
@@ -255,20 +255,37 @@ static void TestChaCha20Poly1305(const std::string& plain_hex, const std::string
255255
auto key = ParseHex<std::byte>(key_hex);
256256
auto expected_cipher = ParseHex<std::byte>(cipher_hex);
257257

258-
std::vector<std::byte> cipher(plain.size() + AEADChaCha20Poly1305::EXPANSION);
259-
AEADChaCha20Poly1305 aead{key};
260-
aead.Encrypt(plain, aad, nonce, cipher);
261-
BOOST_CHECK(cipher == expected_cipher);
258+
for (int i = 0; i < 10; ++i) {
259+
// During i=0, use single-plain Encrypt/Decrypt; others use a split at prefix.
260+
size_t prefix = i ? InsecureRandRange(plain.size() + 1) : plain.size();
261+
// Encrypt.
262+
std::vector<std::byte> cipher(plain.size() + AEADChaCha20Poly1305::EXPANSION);
263+
AEADChaCha20Poly1305 aead{key};
264+
if (i == 0) {
265+
aead.Encrypt(plain, aad, nonce, cipher);
266+
} else {
267+
aead.Encrypt(Span{plain}.first(prefix), Span{plain}.subspan(prefix), aad, nonce, cipher);
268+
}
269+
BOOST_CHECK(cipher == expected_cipher);
262270

263-
std::vector<std::byte> decipher(cipher.size() - AEADChaCha20Poly1305::EXPANSION);
264-
bool ret = aead.Decrypt(cipher, aad, nonce, decipher);
265-
BOOST_CHECK(ret);
266-
BOOST_CHECK(decipher == plain);
271+
// Decrypt.
272+
std::vector<std::byte> decipher(cipher.size() - AEADChaCha20Poly1305::EXPANSION);
273+
bool ret{false};
274+
if (i == 0) {
275+
ret = aead.Decrypt(cipher, aad, nonce, decipher);
276+
} else {
277+
ret = aead.Decrypt(cipher, aad, nonce, Span{decipher}.first(prefix), Span{decipher}.subspan(prefix));
278+
}
279+
BOOST_CHECK(ret);
280+
BOOST_CHECK(decipher == plain);
281+
}
267282

283+
// Test Keystream output.
268284
std::vector<std::byte> keystream(plain.size());
285+
AEADChaCha20Poly1305 aead{key};
269286
aead.Keystream(nonce, keystream);
270287
for (size_t i = 0; i < plain.size(); ++i) {
271-
BOOST_CHECK_EQUAL(plain[i] ^ keystream[i], cipher[i]);
288+
BOOST_CHECK_EQUAL(plain[i] ^ keystream[i], expected_cipher[i]);
272289
}
273290
}
274291

@@ -280,25 +297,43 @@ static void TestFSChaCha20Poly1305(const std::string& plain_hex, const std::stri
280297
auto expected_cipher = ParseHex<std::byte>(cipher_hex);
281298
std::vector<std::byte> cipher(plain.size() + FSChaCha20Poly1305::EXPANSION);
282299

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-
}
300+
for (int it = 0; it < 10; ++it) {
301+
// During it==0 we use the single-plain Encrypt/Decrypt; others use a split at prefix.
302+
size_t prefix = it ? InsecureRandRange(plain.size() + 1) : plain.size();
288303

289-
enc_aead.Encrypt(plain, aad, cipher);
290-
BOOST_CHECK(cipher == expected_cipher);
304+
// Do msg_idx dummy encryptions to seek to the correct packet.
305+
FSChaCha20Poly1305 enc_aead{key, 224};
306+
for (uint64_t i = 0; i < msg_idx; ++i) {
307+
std::byte dummy_tag[FSChaCha20Poly1305::EXPANSION] = {{}};
308+
enc_aead.Encrypt(Span{dummy_tag}.first(0), Span{dummy_tag}.first(0), dummy_tag);
309+
}
291310

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-
}
311+
// Invoke single-plain or plain1/plain2 Encrypt.
312+
if (it == 0) {
313+
enc_aead.Encrypt(plain, aad, cipher);
314+
} else {
315+
enc_aead.Encrypt(Span{plain}.first(prefix), Span{plain}.subspan(prefix), aad, cipher);
316+
}
317+
BOOST_CHECK(cipher == expected_cipher);
297318

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);
319+
// Do msg_idx dummy decryptions to seek to the correct packet.
320+
FSChaCha20Poly1305 dec_aead{key, 224};
321+
for (uint64_t i = 0; i < msg_idx; ++i) {
322+
std::byte dummy_tag[FSChaCha20Poly1305::EXPANSION] = {{}};
323+
dec_aead.Decrypt(dummy_tag, Span{dummy_tag}.first(0), Span{dummy_tag}.first(0));
324+
}
325+
326+
// Invoke single-plain or plain1/plain2 Decrypt.
327+
std::vector<std::byte> decipher(cipher.size() - AEADChaCha20Poly1305::EXPANSION);
328+
bool ret{false};
329+
if (it == 0) {
330+
ret = dec_aead.Decrypt(cipher, aad, decipher);
331+
} else {
332+
ret = dec_aead.Decrypt(cipher, aad, Span{decipher}.first(prefix), Span{decipher}.subspan(prefix));
333+
}
334+
BOOST_CHECK(ret);
335+
BOOST_CHECK(decipher == plain);
336+
}
302337
}
303338

304339
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) {

0 commit comments

Comments
 (0)