From e6f46320309f02ea4b6f258eba0a50d8c00cd716 Mon Sep 17 00:00:00 2001 From: Martijn de Milliano Date: Fri, 26 Jun 2026 00:02:59 +0000 Subject: [PATCH] Random: add DRBG reseed support --- scripts/build_ffi.py | 8 ++++++++ tests/test_random.py | 16 ++++++++++++++++ wolfcrypt/random.py | 10 ++++++++++ 3 files changed, 34 insertions(+) diff --git a/scripts/build_ffi.py b/scripts/build_ffi.py index b0b4f24..5f3edba 100644 --- a/scripts/build_ffi.py +++ b/scripts/build_ffi.py @@ -378,6 +378,7 @@ def get_features(local_wolfssl, features): features["ML_DSA"] = 1 if '#define HAVE_DILITHIUM' in defines else 0 features["ML_KEM"] = 1 if '#define WOLFSSL_HAVE_MLKEM' in defines else 0 features["HKDF"] = 1 if "#define HAVE_HKDF" in defines else 0 + features["HASHDRBG"] = 1 if "#define HAVE_HASHDRBG" in defines else 0 if '#define HAVE_FIPS' in defines: if not fips: @@ -497,6 +498,7 @@ def build_ffi(local_wolfssl, features): int ML_KEM_ENABLED = {features["ML_KEM"]}; int ML_DSA_ENABLED = {features["ML_DSA"]}; int HKDF_ENABLED = {features["HKDF"]}; + int HASHDRBG_ENABLED = {features["HASHDRBG"]}; """ ffibuilder.set_source( "wolfcrypt._ffi", init_source_string, @@ -537,6 +539,7 @@ def build_ffi(local_wolfssl, features): extern int ML_KEM_ENABLED; extern int ML_DSA_ENABLED; extern int HKDF_ENABLED; + extern int HASHDRBG_ENABLED; typedef unsigned char byte; typedef unsigned int word32; @@ -551,6 +554,10 @@ def build_ffi(local_wolfssl, features): int wc_RNG_GenerateByte(WC_RNG*, byte*); int wc_FreeRng(WC_RNG*); """ + if features["HASHDRBG"]: + cdef += """ + int wc_RNG_DRBG_Reseed(WC_RNG*, const byte*, word32); + """ if features["ERROR_STRINGS"]: cdef += """ @@ -1369,6 +1376,7 @@ def main(ffibuilder): "ML_KEM": 1, "ML_DSA": 1, "HKDF": 1, + "HASHDRBG": 1, } # Ed448 requires SHAKE256, which isn't part of the Windows build, yet. diff --git a/tests/test_random.py b/tests/test_random.py index bf59f4e..6daed86 100644 --- a/tests/test_random.py +++ b/tests/test_random.py @@ -21,7 +21,9 @@ # pylint: disable=redefined-outer-name import pytest +from wolfcrypt._ffi import lib as _lib from wolfcrypt.random import Random +from wolfcrypt.exceptions import WolfCryptApiError @pytest.fixture @@ -48,3 +50,17 @@ def test_nonce_byte(rng_nonce): @pytest.mark.parametrize("length", (1, 8, 128)) def test_nonce_bytes(rng_nonce, length): assert len(rng_nonce.bytes(length)) == length + + +@pytest.mark.skipif(not _lib.HASHDRBG_ENABLED, reason="Reseeding only available with hash-DRBG") +def test_reseed(rng): + rng.reseed(b"some seed material for testing") + assert len(rng.bytes(32)) == 32 + + +@pytest.mark.skipif(not _lib.HASHDRBG_ENABLED, reason="Reseeding only available with hash-DRBG") +def test_reseed_empty(rng): + try: + rng.reseed(b"") + except WolfCryptApiError: + pass # acceptable — C rejects zero-length seed diff --git a/wolfcrypt/random.py b/wolfcrypt/random.py index 1c4da84..04fc6c5 100644 --- a/wolfcrypt/random.py +++ b/wolfcrypt/random.py @@ -77,3 +77,13 @@ class Random: raise WolfCryptApiError("RNG generate block error", ret) return _ffi.buffer(result, length)[:] + + if _lib.HASHDRBG_ENABLED: + def reseed(self, seed: __builtins__.bytes) -> None: + """ + Reseed the DRBG with the provided seed material. + """ + assert self.native_object is not None + ret = _lib.wc_RNG_DRBG_Reseed(self.native_object, seed, len(seed)) + if ret < 0: + raise WolfCryptApiError("RNG reseed error", ret)