Ilya Maximets <[email protected]> writes: > On 10/28/26 8:14 PM, Aaron Conole wrote: >> This commit makes the ChaCha20 CSPRNG prefer using >> OpenSSL's SIMD optimized ChaCha20 on platforms which >> are compiled with such support. This represents a >> major improvement on performance, without impacting >> the underlying CSPRNG functionality. >> >> The EVP backend re-keys from the first bytes of every >> refill, just as the built-in implementation does, so >> that the fast key erasure - and thus the forward >> secrecy - of the generator is the same for both. The >> built-in ChaCha20 implementation is always built so >> that it can serve as a fallback when OpenSSL is unable >> to provide ChaCha20, for example when it only offers >> approved algorithms in FIPS mode. >> >> Assisted-by: LLM (claude, opencode, sonnet, opus, qwen3.8) > > I guess, we should consider dropping this tag as kernel did. :)
+1 :) >> Signed-off-by: Aaron Conole <[email protected]> >> --- >> v2->v3: >> * Update the the csprng to provide local ChaCha20 when >> openssl isn't available for many reasons. >> * Updated the cs_random_bytes() max comment (since the OpenSSL >> version will shrink the max a bit more). >> * Updated the rekey to provide forward key erasure and forward >> secrecy. >> >> lib/random.c | 217 ++++++++++++++++++++++++++++++++++++++++++--------- >> lib/random.h | 2 +- >> 2 files changed, 182 insertions(+), 37 deletions(-) >> >> diff --git a/lib/random.c b/lib/random.c >> index d2f28866a2..27d57530f7 100644 >> --- a/lib/random.c >> +++ b/lib/random.c >> @@ -18,15 +18,24 @@ >> #include "random.h" >> >> #include <errno.h> >> +#include <stdbool.h> >> #include <stdlib.h> >> #include <sys/time.h> >> >> +#if defined(HAVE_OPENSSL) && !defined(OPENSSL_NO_CHACHA) > > OPENSSL_NO_CHACHA is probably never defined here as we didn't include > any OpenSSL headers yet. Or am I missing something? I'll double check, and fix on next version if needed. >> +#include <openssl/evp.h> >> +#define HAVE_CHACHA20_EVP 1 > > Can we detect that at configuration time? The EVP_chacha20() function > should not be defined with OPENSSL_NO_CHACHA. Probably a better approach. >> +#endif >> + >> #include "byte-order.h" >> #include "entropy.h" >> #include "hash.h" >> #include "ovs-thread.h" >> #include "timeval.h" >> #include "util.h" >> +#include "openvswitch/vlog.h" > > Can we order this lexicographically? ACK >> + >> +VLOG_DEFINE_THIS_MODULE(random); >> >> /* This is the 32-bit PRNG recommended in G. Marsaglia, "Xorshift RNGs", >> * _Journal of Statistical Software_ 8:14 (July 2003). According to the >> paper, >> @@ -120,6 +129,39 @@ random_next(void) >> } >> >> >> +/* Cryptographic secure pseudo-random number generator. */ >> + >> +/* Refill a page worth of keystream at a time to amortize the block cost. */ >> +#define CSPRNG_BUFSZ 4096 >> + >> +#define CHACHA_KEYLEN 32 /* 256-bit key. */ >> +#define CHACHA_IVLEN 16 /* ChaCha20 IV: counter and nonce. */ >> + >> +/* Reseed from the OS entropy pool after this many bytes served, matching >> + * the arc4random policy of periodic reseeding. */ >> +#define CSPRNG_RESEED (1600 * 1024) >> + >> +#ifdef HAVE_CHACHA20_EVP >> +/* The OpenSSL EVP backend consumes an IV in addition to a key. */ >> +#define CSPRNG_SECRETS (CHACHA_KEYLEN + CHACHA_IVLEN) >> +#else >> +#define CSPRNG_SECRETS CHACHA_KEYLEN >> +#endif >> + >> +struct csprng { >> +#ifdef HAVE_CHACHA20_EVP >> + EVP_CIPHER_CTX *ctx; /* OpenSSL EVP state, once created. */ >> + bool use_evp; /* Serve from OpenSSL rather than the >> + * built-in implementation. */ >> +#endif >> + uint32_t state[16]; /* ChaCha20 state: >> + * constant|key|counter|nonce. */ >> + uint8_t buf[CSPRNG_BUFSZ]; /* Buffered keystream. */ >> + size_t pos; /* Next unused byte in 'buf'. */ >> + size_t served; /* Bytes served since last reseed. */ >> + int initialized; >> +}; >> + >> /* This is a 32-bit CSPRNG adapted from the ChaCha20 algorithm described in >> * RFC 7539 and RFC 8439, similar to arc4random(3) and the Linux kernel's >> * get_random_u32(). >> @@ -130,14 +172,6 @@ random_next(void) >> */ >> >> #define CHACHA_ROUNDS 20 >> -#define CHACHA_KEYLEN 32 /* 256-bit key. */ >> - >> -/* Refill a page worth of keystream at a time to amortize the block cost. */ >> -#define CSPRNG_BUFSZ 4096 >> - >> -/* Reseed from the OS entropy pool after this many bytes served, matching >> - * the arc4random policy of periodic reseeding. */ >> -#define CSPRNG_RESEED (1600 * 1024) >> >> #define ROTL32(v, n) (((v) << (n)) | ((v) >> (32 - (n)))) >> >> @@ -147,20 +181,13 @@ random_next(void) >> a += b; d ^= a; d = ROTL32(d, 8); \ >> c += d; b ^= c; b = ROTL32(b, 7) >> >> -struct csprng { >> - uint32_t state[16]; /* ChaCha20 state: >> - * constant|key|counter|nonce. */ >> - uint8_t buf[CSPRNG_BUFSZ]; /* Buffered keystream. */ >> - size_t pos; /* Next unused byte in 'buf'. */ >> - size_t served; /* Bytes served since last reseed. */ >> - int initialized; >> -}; >> - >> /* ChaCha20 constant: "expand 32-byte k". Must be at least 16 bytes + NUL. >> */ >> static const uint8_t chacha_sigma[] = "expand 32-byte k"; >> >> +#ifndef HAVE_CHACHA20_EVP >> /* Current random state. */ >> DEFINE_STATIC_PER_THREAD_DATA(struct csprng, cs_state, { .initialized = 0 >> }); >> +#endif >> >> /* Produces one 64-byte ChaCha20 block from 'input' (16 words) into 'out'. >> */ >> void >> @@ -211,22 +238,8 @@ csprng_rekey(struct csprng *c, const uint8_t >> key[CHACHA_KEYLEN]) >> } >> } >> >> -static void >> -csprng_seed(struct csprng *c) >> -{ >> - uint8_t key[CHACHA_KEYLEN]; >> - >> - get_entropy_or_die(key, sizeof key); >> - csprng_rekey(c, key); >> - ovs_memzero(key, sizeof key); >> - >> - c->pos = sizeof c->buf; >> - c->served = 0; >> - c->initialized = 1; >> -} >> - >> -/* Fills csprng with fresh keystream, reserving the first 32 bytes to re-key >> - * the generator (fast key erasure / forward secrecy). */ >> +/* Fills 'c' with fresh keystream, reserving the first 32 bytes to re-key >> the >> + * generator (fast key erasure / forward secrecy). */ >> static void >> csprng_refill(struct csprng *c) >> { >> @@ -246,21 +259,153 @@ csprng_refill(struct csprng *c) >> c->pos = CHACHA_KEYLEN; >> } >> >> +#ifdef HAVE_CHACHA20_EVP >> +/* This is the EVP based CSPRNG using the OpenSSL EVP framework to create a >> + * long stream of bytes, and pull those. The OpenSSL EVP manages the >> + * underlying bytes, all this does is manage the policy. It re-keys from >> the >> + * beginning of every refill, as the built-in implementation above does, so >> + * that both provide the same fast key erasure. >> + */ >> + >> +static void >> +csprng_state_destroy(void *ctx) >> +{ >> + struct csprng *csp = ctx; >> + >> + if (csp->ctx) { >> + EVP_CIPHER_CTX_free(csp->ctx); >> + csp->ctx = NULL; >> + } >> + >> + free(csp); >> +} >> + >> +static struct csprng * >> +cs_state_get(void) >> +{ >> + static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER; >> + static ovsthread_key_t cskey; >> + struct csprng *csp; >> + >> + if (ovsthread_once_start(&once)) { >> + ovsthread_key_create(&cskey, csprng_state_destroy); >> + ovsthread_once_done(&once); >> + } >> + >> + csp = ovsthread_getspecific(cskey); >> + if (!csp) { >> + csp = xzalloc(sizeof *csp); >> + csp->use_evp = true; >> + ovsthread_setspecific(cskey, csp); >> + } >> + return csp; >> +} >> + >> +/* (Re)keys the OpenSSL EVP state from 'key' (32 bytes) and 'iv' (16 >> bytes). */ >> +static bool >> +csprng_evp_rekey(struct csprng *c, const uint8_t key[CHACHA_KEYLEN], >> + const uint8_t iv[CHACHA_IVLEN]) >> +{ >> + return EVP_EncryptInit_ex(c->ctx, EVP_chacha20(), NULL, key, iv) == 1; >> +} >> + >> +/* Seeds the OpenSSL EVP state from fresh entropy, returning false if >> OpenSSL >> + * cannot provide ChaCha20. */ >> +static bool >> +csprng_evp_seed(struct csprng *c, const uint8_t key[CHACHA_KEYLEN]) >> +{ >> + uint8_t iv[CHACHA_IVLEN]; >> + bool ok; >> + >> + if (!c->ctx) { >> + c->ctx = EVP_CIPHER_CTX_new(); >> + if (!c->ctx) { >> + VLOG_WARN_ONCE("cs_random: cannot create cipher context."); >> + return false; >> + } >> + } >> + >> + get_entropy_or_die(iv, sizeof iv); >> + ok = csprng_evp_rekey(c, key, iv); >> + ovs_memzero(iv, sizeof iv); >> + >> + if (!ok) { >> + /* OpenSSL may not provide ChaCha20 at all, for example when it only >> + * offers approved algorithms in FIPS mode. Fall back to the >> built-in >> + * implementation rather than failing to provide randomness. */ >> + VLOG_WARN_ONCE("cs_random: OpenSSL ChaCha20 unavailable, using the " >> + "built-in implementation."); >> + } >> + return ok; >> +} >> + >> +/* Fills 'c' with fresh keystream from OpenSSL, reserving the first bytes to >> + * re-key the generator (fast key erasure / forward secrecy). */ >> +static void >> +csprng_evp_refill(struct csprng *c) >> +{ >> + static const uint8_t zero[CSPRNG_BUFSZ]; >> + int outl; >> + >> + if (EVP_EncryptUpdate(c->ctx, c->buf, &outl, zero, sizeof c->buf) != 1 >> || >> + outl != sizeof c->buf) { >> + VLOG_FATAL("cs_random: EVP fill failure."); >> + } >> + >> + csprng_evp_rekey(c, c->buf, c->buf + CHACHA_KEYLEN); > > What happens if this function returns 'false' ? What?! It won't :) I guess it can, so I'll handle that case in next version. > Best regards, Ilya Maximets. _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
