Skip to content

Commit 12ff724

Browse files
sipadhruv
andcommitted
Make unrestricted ChaCha20 cipher not waste keystream bytes
Co-authored-by: dhruv <[email protected]>
1 parent 6babf40 commit 12ff724

File tree

4 files changed

+60
-9
lines changed

4 files changed

+60
-9
lines changed

src/crypto/chacha20.cpp

+23-6
Original file line numberDiff line numberDiff line change
@@ -297,22 +297,39 @@ inline void ChaCha20Aligned::Crypt64(const unsigned char* m, unsigned char* c, s
297297
void ChaCha20::Keystream(unsigned char* c, size_t bytes)
298298
{
299299
if (!bytes) return;
300+
if (m_bufleft) {
301+
unsigned reuse = std::min<size_t>(m_bufleft, bytes);
302+
memcpy(c, m_buffer + 64 - m_bufleft, reuse);
303+
m_bufleft -= reuse;
304+
bytes -= reuse;
305+
c += reuse;
306+
}
300307
if (bytes >= 64) {
301308
size_t blocks = bytes / 64;
302309
m_aligned.Keystream64(c, blocks);
303310
c += blocks * 64;
304311
bytes -= blocks * 64;
305312
}
306313
if (bytes) {
307-
unsigned char buffer[64];
308-
m_aligned.Keystream64(buffer, 1);
309-
memcpy(c, buffer, bytes);
314+
m_aligned.Keystream64(m_buffer, 1);
315+
memcpy(c, m_buffer, bytes);
316+
m_bufleft = 64 - bytes;
310317
}
311318
}
312319

313320
void ChaCha20::Crypt(const unsigned char* m, unsigned char* c, size_t bytes)
314321
{
315322
if (!bytes) return;
323+
if (m_bufleft) {
324+
unsigned reuse = std::min<size_t>(m_bufleft, bytes);
325+
for (unsigned i = 0; i < reuse; i++) {
326+
c[i] = m[i] ^ m_buffer[64 - m_bufleft + i];
327+
}
328+
m_bufleft -= reuse;
329+
bytes -= reuse;
330+
c += reuse;
331+
m += reuse;
332+
}
316333
if (bytes >= 64) {
317334
size_t blocks = bytes / 64;
318335
m_aligned.Crypt64(m, c, blocks);
@@ -321,10 +338,10 @@ void ChaCha20::Crypt(const unsigned char* m, unsigned char* c, size_t bytes)
321338
bytes -= blocks * 64;
322339
}
323340
if (bytes) {
324-
unsigned char buffer[64];
325-
m_aligned.Keystream64(buffer, 1);
341+
m_aligned.Keystream64(m_buffer, 1);
326342
for (unsigned i = 0; i < bytes; i++) {
327-
c[i] = m[i] ^ buffer[i];
343+
c[i] = m[i] ^ m_buffer[i];
328344
}
345+
m_bufleft = 64 - bytes;
329346
}
330347
}

src/crypto/chacha20.h

+13-3
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,13 @@ class ChaCha20Aligned
4141
void Crypt64(const unsigned char* input, unsigned char* output, size_t blocks);
4242
};
4343

44-
/** Unrestricted ChaCha20 cipher. Seeks forward to a multiple of 64 bytes after every operation. */
44+
/** Unrestricted ChaCha20 cipher. */
4545
class ChaCha20
4646
{
4747
private:
4848
ChaCha20Aligned m_aligned;
49+
unsigned char m_buffer[64] = {0};
50+
unsigned m_bufleft{0};
4951

5052
public:
5153
ChaCha20() = default;
@@ -54,13 +56,21 @@ class ChaCha20
5456
ChaCha20(const unsigned char* key, size_t keylen) : m_aligned(key, keylen) {}
5557

5658
/** set key with flexible keylength (16 or 32 bytes; 32 recommended). */
57-
void SetKey(const unsigned char* key, size_t keylen) { m_aligned.SetKey(key, keylen); }
59+
void SetKey(const unsigned char* key, size_t keylen)
60+
{
61+
m_aligned.SetKey(key, keylen);
62+
m_bufleft = 0;
63+
}
5864

5965
/** set the 64-bit nonce. */
6066
void SetIV(uint64_t iv) { m_aligned.SetIV(iv); }
6167

6268
/** set the 64bit block counter (pos seeks to byte position 64*pos). */
63-
void Seek64(uint64_t pos) { m_aligned.Seek64(pos); }
69+
void Seek64(uint64_t pos)
70+
{
71+
m_aligned.Seek64(pos);
72+
m_bufleft = 0;
73+
}
6474

6575
/** outputs the keystream of size <bytes> into <c> */
6676
void Keystream(unsigned char* c, size_t bytes);

src/test/crypto_tests.cpp

+18
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,24 @@ BOOST_AUTO_TEST_CASE(chacha20_testvector)
500500
"fab78c9");
501501
}
502502

503+
BOOST_AUTO_TEST_CASE(chacha20_midblock)
504+
{
505+
auto key = ParseHex("0000000000000000000000000000000000000000000000000000000000000000");
506+
ChaCha20 c20{key.data(), 32};
507+
// get one block of keystream
508+
unsigned char block[64];
509+
c20.Keystream(block, CHACHA20_ROUND_OUTPUT);
510+
unsigned char b1[5], b2[7], b3[52];
511+
c20 = ChaCha20{key.data(), 32};
512+
c20.Keystream(b1, 5);
513+
c20.Keystream(b2, 7);
514+
c20.Keystream(b3, 52);
515+
516+
BOOST_CHECK_EQUAL(0, memcmp(b1, block, 5));
517+
BOOST_CHECK_EQUAL(0, memcmp(b2, block + 5, 7));
518+
BOOST_CHECK_EQUAL(0, memcmp(b3, block + 12, 52));
519+
}
520+
503521
BOOST_AUTO_TEST_CASE(poly1305_testvector)
504522
{
505523
// RFC 7539, section 2.5.2.

src/test/fuzz/crypto_diff_fuzz_chacha20.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -310,20 +310,26 @@ FUZZ_TARGET(crypto_diff_fuzz_chacha20)
310310
},
311311
[&] {
312312
uint32_t integralInRange = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096);
313+
// DJB's version seeks forward to a multiple of 64 bytes after every operation. Correct for that.
314+
uint64_t pos = ctx.input[12] + (((uint64_t)ctx.input[13]) << 32) + ((integralInRange + 63) >> 6);
313315
std::vector<uint8_t> output(integralInRange);
314316
chacha20.Keystream(output.data(), output.size());
315317
std::vector<uint8_t> djb_output(integralInRange);
316318
ECRYPT_keystream_bytes(&ctx, djb_output.data(), djb_output.size());
317319
assert(output == djb_output);
320+
chacha20.Seek64(pos);
318321
},
319322
[&] {
320323
uint32_t integralInRange = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096);
324+
// DJB's version seeks forward to a multiple of 64 bytes after every operation. Correct for that.
325+
uint64_t pos = ctx.input[12] + (((uint64_t)ctx.input[13]) << 32) + ((integralInRange + 63) >> 6);
321326
std::vector<uint8_t> output(integralInRange);
322327
const std::vector<uint8_t> input = ConsumeFixedLengthByteVector(fuzzed_data_provider, output.size());
323328
chacha20.Crypt(input.data(), output.data(), input.size());
324329
std::vector<uint8_t> djb_output(integralInRange);
325330
ECRYPT_encrypt_bytes(&ctx, input.data(), djb_output.data(), input.size());
326331
assert(output == djb_output);
332+
chacha20.Seek64(pos);
327333
});
328334
}
329335
}

0 commit comments

Comments
 (0)