From 3c6bec5a670dbf0ef1ff46b8456daef118f37273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Tro=C3=9Fbach?= Date: Fri, 11 Sep 2026 15:09:56 +0200 Subject: [PATCH] Use fast key erasure in the ChaCha20 random number generator chacha20_rng() kept its key until the next reseed, which happened only on the first call, after a fork or when the 32-bit block counter wrapped. Anyone who learned the generator state could therefore compute all output since the last reseed. - Fast key erasure: every refill generates 256 bytes of keystream; the first 32 bytes immediately replace the key, and only the remaining 224 bytes are handed out. Output that has already been returned can no longer be reconstructed from the current state. - Reseed from entropy() every 16384 refills (3.5 MiB of output), in addition to the first call and the fork detection. - Wipe handed-out bytes from the buffer. --- src/chacha20poly1305.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/chacha20poly1305.c b/src/chacha20poly1305.c index 8ad6ceeb..2b60d970 100644 --- a/src/chacha20poly1305.c +++ b/src/chacha20poly1305.c @@ -619,12 +619,15 @@ static size_t entropy(void* buf, size_t n) #endif /* - * ChaCha20 random number generator + * ChaCha20 random number generator with fast key erasure: each refill + * replaces the key with the first 32 bytes of new keystream. */ +#define CHACHA20_RNG_RESEED_INTERVAL 16384 /* refills (3.5 MiB of output) */ + SQLITE_PRIVATE void chacha20_rng(void* out, size_t n) { - static uint8_t key[32], nonce[12], buffer[64] = { 0 }; + static uint8_t key[32], nonce[12], buffer[256] = { 0 }; static uint32_t counter = 0; static size_t available = 0; #if !defined(_WIN32) && !defined(__wasm__) @@ -674,11 +677,18 @@ void chacha20_rng(void* out, size_t n) if (entropy(nonce, sizeof(nonce)) != sizeof(nonce)) abort(); } - chacha20_xor(buffer, sizeof(buffer), key, nonce, counter++); - available = sizeof(buffer); + memset(buffer, 0, sizeof(buffer)); + chacha20_xor(buffer, sizeof(buffer), key, nonce, 0); + /* The first 32 bytes become the next key */ + memcpy(key, buffer, sizeof(key)); + memset(buffer, 0, sizeof(key)); + available = sizeof(buffer) - sizeof(key); + counter = (counter + 1) % CHACHA20_RNG_RESEED_INTERVAL; } m = (available < n) ? available : n; memcpy(out, buffer + (sizeof(buffer) - available), m); + /* Wipe handed-out bytes */ + memset(buffer + (sizeof(buffer) - available), 0, m); out = (uint8_t*)out + m; available -= m; n -= m;