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. :) > 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? > +#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. > +#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? > + > +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' ? Best regards, Ilya Maximets. _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
