From 786dd946137963549ae67275cd1cf8ebf89c0bcf Mon Sep 17 00:00:00 2001 From: Robert de Vries Date: Sat, 11 Apr 2026 18:16:19 +0200 Subject: [PATCH] Fix mutable arguments passed as default arguments. Function defaults are evaluated once, when the function is defined. The same mutable object is then shared across all calls to the function. If the object is modified, those modifications will persist across calls, which can lead to unexpected behavior. --- tests/test_ciphers.py | 6 ++++-- wolfcrypt/ciphers.py | 40 ++++++++++++++++++++++++++++++---------- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/tests/test_ciphers.py b/tests/test_ciphers.py index 2cbd483..391d752 100644 --- a/tests/test_ciphers.py +++ b/tests/test_ciphers.py @@ -25,6 +25,7 @@ import pytest from wolfcrypt._ffi import lib as _lib from wolfcrypt.ciphers import MODE_CTR, MODE_ECB, MODE_CBC, WolfCryptError +from wolfcrypt.random import Random from wolfcrypt.utils import t2b, h2b import os @@ -613,11 +614,12 @@ def test_ecc_sign_verify_raw(ecc_private, ecc_public): def test_ecc_make_shared_secret(): - a = EccPrivate.make_key(32) + rng = Random() + a = EccPrivate.make_key(32, rng=rng) a_pub = EccPublic() a_pub.import_x963(a.export_x963()) - b = EccPrivate.make_key(32) + b = EccPrivate.make_key(32, rng=rng) b_pub = EccPublic() b_pub.import_x963(b.export_x963()) diff --git a/wolfcrypt/ciphers.py b/wolfcrypt/ciphers.py index 8fa304b..5f44a35 100644 --- a/wolfcrypt/ciphers.py +++ b/wolfcrypt/ciphers.py @@ -824,10 +824,12 @@ def verify_pss(self, plaintext, signature): class RsaPrivate(RsaPublic): if _lib.KEYGEN_ENABLED: @classmethod - def make_key(cls, size, rng=Random(), hash_type=None): + def make_key(cls, size, rng=None, hash_type=None): """ Generates a new key pair of desired length **size**. """ + if rng is None: + rng = Random() rsa = cls(hash_type=hash_type) ret = _lib.wc_MakeRsaKey(rsa.native_object, size, 65537, @@ -1183,10 +1185,12 @@ def verify_raw(self, R, S, data): class EccPrivate(EccPublic): @classmethod - def make_key(cls, size, rng=Random()): + def make_key(cls, size, rng=None): """ Generates a new key pair of desired length **size**. """ + if rng is None: + rng = Random() ecc = cls() ret = _lib.wc_ecc_make_key(rng.native_object, size, @@ -1289,12 +1293,14 @@ def shared_secret(self, peer): return _ffi.buffer(shared_secret, secret_size[0])[:] - def sign(self, plaintext, rng=Random()): + def sign(self, plaintext, rng=None): """ Signs **plaintext**, using the private key data in the object. Returns the signature. """ + if rng is None: + rng = Random() plaintext = t2b(plaintext) signature = _ffi.new("byte[%d]" % self.max_signature_size) @@ -1312,12 +1318,14 @@ def sign(self, plaintext, rng=Random()): return _ffi.buffer(signature, signature_size[0])[:] if _lib.MPAPI_ENABLED: - def sign_raw(self, plaintext, rng=Random()): + def sign_raw(self, plaintext, rng=None): """ Signs **plaintext**, using the private key data in the object. Returns the signature in its two raw components r, s """ + if rng is None: + rng = Random() plaintext = t2b(plaintext) R = _ffi.new("mp_int[1]") S = _ffi.new("mp_int[1]") @@ -1449,10 +1457,12 @@ def __init__(self, key=None, pub=None): self.decode_key(key,pub) @classmethod - def make_key(cls, size, rng=Random()): + def make_key(cls, size, rng=None): """ Generates a new key pair of desired length **size**. """ + if rng is None: + rng = Random() ed25519 = cls() ret = _lib.wc_ed25519_make_key(rng.native_object, size, @@ -1645,10 +1655,12 @@ def __init__(self, key=None, pub=None): self.decode_key(key,pub) @classmethod - def make_key(cls, size, rng=Random()): + def make_key(cls, size, rng=None): """ Generates a new key pair of desired length **size**. """ + if rng is None: + rng = Random() ed448 = cls() ret = _lib.wc_ed448_make_key(rng.native_object, size, @@ -1862,13 +1874,15 @@ def decode_key(self, pub_key): if ret < 0: # pragma: no cover raise WolfCryptError("wc_KyberKey_DecodePublicKey() error (%d)" % ret) - def encapsulate(self, rng=Random()): + def encapsulate(self, rng=None): """ :param rng: random number generator for an encupsulation :type rng: Random :return: tuple of a shared secret (first element) and the cipher text (second element) :rtype: tuple[bytes, bytes] """ + if rng is None: + rng = Random() ct_size = self.ct_size ss_size = self.ss_size ct = _ffi.new(f"unsigned char[{ct_size}]") @@ -1906,7 +1920,7 @@ def encapsulate_with_random(self, rand): class MlKemPrivate(_MlKemBase): @classmethod - def make_key(cls, mlkem_type, rng=Random()): + def make_key(cls, mlkem_type, rng=None): """ :param mlkem_type: ML-KEM type :type mlkem_type: MlKemType @@ -1915,6 +1929,8 @@ def make_key(cls, mlkem_type, rng=Random()): :return: `MlKemPrivate` object :rtype: MlKemPrivate """ + if rng is None: + rng = Random() mlkem_priv = cls(mlkem_type) ret = _lib.wc_KyberKey_MakeKey(mlkem_priv.native_object, rng.native_object) @@ -2150,7 +2166,7 @@ def verify(self, signature, message): class MlDsaPrivate(_MlDsaBase): @classmethod - def make_key(cls, mldsa_type, rng=Random()): + def make_key(cls, mldsa_type, rng=None): """ :param mldsa_type: ML-DSA type :type mldsa_type: MlDsaType @@ -2159,6 +2175,8 @@ def make_key(cls, mldsa_type, rng=Random()): :return: `MlDsaPrivate` object :rtype: MlDsaPrivate """ + if rng is None: + rng = Random() mldsa_priv = cls(mldsa_type) ret = _lib.wc_dilithium_make_key( mldsa_priv.native_object, rng.native_object @@ -2243,7 +2261,7 @@ def decode_key(self, priv_key, pub_key=None): if pub_key is not None: self._decode_pub_key(pub_key) - def sign(self, message, rng=Random()): + def sign(self, message, rng=None): """ :param message: message to be signed :type message: bytes or str @@ -2252,6 +2270,8 @@ def sign(self, message, rng=Random()): :return: signature :rtype: bytes """ + if rng is None: + rng = Random() msg_bytestype = t2b(message) in_size = self.sig_size signature = _ffi.new(f"byte[{in_size}]")