Merge pull request #130 from mjdemilliano/add-hashdrbg-reseed

Random: add DRBG reseed support
pull/137/head
David Garske 2026-07-08 12:28:45 -07:00 committed by GitHub
commit 905682a036
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 55 additions and 0 deletions

View File

@ -391,6 +391,9 @@ def get_features(local_wolfssl, features):
features["ML_DSA_NO_CTX"] = 1 if have_mldsa_no_context_support 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
# Unlike the other fatures, HASHDRBG is enabled by default in random.h, unless WC_NO_HASHDRBG or
# CUSTOM_RAND_GENERATE_BLOCK is defined.
features["HASHDRBG"] = 0 if ("#define WC_NO_HASHDRBG" in defines or "#define CUSTOM_RAND_GENERATE_BLOCK" in defines) else 1
if '#define HAVE_FIPS' in defines:
if not fips:
@ -511,6 +514,7 @@ def build_ffi(local_wolfssl, features):
int ML_DSA_ENABLED = {features["ML_DSA"]};
int ML_DSA_NO_CTX_ENABLED = {features["ML_DSA_NO_CTX"]};
int HKDF_ENABLED = {features["HKDF"]};
int HASHDRBG_ENABLED = {features["HASHDRBG"]};
"""
ffibuilder.set_source( "wolfcrypt._ffi", init_source_string,
@ -553,6 +557,7 @@ def build_ffi(local_wolfssl, features):
extern int ML_DSA_ENABLED;
extern int ML_DSA_NO_CTX_ENABLED;
extern int HKDF_ENABLED;
extern int HASHDRBG_ENABLED;
typedef unsigned char byte;
typedef unsigned int word32;
@ -567,6 +572,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 += """
@ -1390,6 +1399,7 @@ def main(ffibuilder):
"ML_DSA": 1,
"ML_DSA_NO_CTX": 0,
"HKDF": 1,
"HASHDRBG": 1,
}
# Ed448 requires SHAKE256, which isn't part of the Windows build, yet.

View File

@ -21,6 +21,7 @@
# pylint: disable=redefined-outer-name
import pytest
from wolfcrypt._ffi import lib as _lib
from wolfcrypt.random import Random
@ -38,13 +39,44 @@ def test_bytes(rng):
assert len(rng.bytes(8)) == 8
assert len(rng.bytes(128)) == 128
@pytest.fixture
def rng_nonce():
return Random(b"abcdefghijklmnopqrstuv")
def test_nonce_byte(rng_nonce):
assert len(rng_nonce.byte()) == 1
@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")
@pytest.mark.parametrize("seed_size", [0, 1, 32, 1000])
def test_reseed_sizes(rng, seed_size):
"""
Test that reseeding the random number generator works, for various seed sizes.
"""
# Create seed of required length.
seed = bytes(x % 256 for x in range(seed_size))
assert len(seed) == seed_size
rng.reseed(seed)
# Pull some bytes from the random number generator to test that it still works.
rng.bytes(32)
@pytest.mark.skipif(not _lib.HASHDRBG_ENABLED, reason="Reseeding only available with hash-DRBG")
def test_reseed_multiple(rng):
"""
Test that consecutive reseeding of the random number generator works.
"""
for _ in range(10):
# Create seed of typical size. Testing with various seed sizes done in `test_reseed_sizes`.
seed = bytes(x % 256 for x in range(32))
rng.reseed(seed)
# Pull some bytes from the random number generator to test that it still works.
rng.bytes(100)

View File

@ -6,6 +6,7 @@
#endif
#define WOLFCRYPT_ONLY
#define HAVE_HASHDRBG
#define WOLFSSL_AESGCM_STREAM
#define HAVE_AESGCM
#define GCM_TABLE_4BIT

View File

@ -53,6 +53,7 @@ SHA256_ENABLED: int
SHA384_ENABLED: int
SHA512_ENABLED: int
WC_RNG_SEED_CB_ENABLED: int
HASHDRBG_ENABLED: int
# Error codes
WC_FAILURE: int
@ -331,6 +332,7 @@ def wc_GetErrorString(error: int) -> FFI.CData: ...
def wc_InitRngNonce_ex(rng: RNG, nonce: bytes, nonce_size: int, heap: FFI.CData, device_id: int) -> int: ...
def wc_RNG_GenerateByte(rng: RNG, buffer: FFI.CData) -> int: ...
def wc_RNG_GenerateBlock(rng: RNG, buffer: FFI.CData, len: int) -> int: ...
def wc_RNG_DRBG_Reseed(rng: RNG, seed: bytes, seed_size: int) -> int: ...
def wc_FreeRng(rng: RNG) -> None: ...
DerBufferPtr: TypeAlias = FFI.CData

View File

@ -82,3 +82,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: # pragma: no cover
raise WolfCryptApiError("RNG reseed error", ret)