Describe the bug
pseudo_rand_gen in framework/py/flwr/common/secure_aggregation/secaggplus_utils.py collapses the input seed to a 32-bit integer and then uses np.random.RandomState to generate the private and pairwise masks.
Why this is a problem:
- np.random.RandomState is not a cryptographically secure pseudo-random generator
- XOR-compressing an arbitrary length secret into a 32-bit value throws away almost all entropy before the generator even starts
This weakens the privacy guarantee that the masks are supposed to provide.
A possible fix is to replace np.random.RandomState with a cryptographic PRF/XOF such as SHAKE256 or AES-CTR-DRBG, and use the full seed/key rather than compressing it to 32 bits.
Steps/Code to Reproduce
Mask generation helper used by classical SecAgg+:
from flwr.common.secure_aggregation.secaggplus_utils import pseudo_rand_gen
# This is the same path used inside secaggplus_mod.py for both
# the private mask (rd_seed) and the pairwise masks (ECDH shared_key).
mask = pseudo_rand_gen(b"a" * 32, 1024, [(10,)])
The implementation:
# framework/py/flwr/common/secure_aggregation/secaggplus_utils.py
def pseudo_rand_gen(seed, num_range, dimensions_list):
assert len(seed) & 0x3 == 0
seed32 = 0
for i in range(0, len(seed), 4):
seed32 ^= int.from_bytes(seed[i : i + 4], "little")
gen = np.random.RandomState(seed32)
Expected Results
- Mask generation should use a cryptographically secure pseudo-random function / generator
- The full entropy of the input secret should be preserved, not compressed to 32 bits
- An attacker who observes mask values should not be able to reconstruct the seed or predict other mask entries
Actual Results
- The input seed is XOR-compressed into a 32-bit integer, regardless of its original length
- np.random.RandomState is used to generate the masks. This generator is not cryptographically secure and is vulnerable to state-recovery attacks given sufficient output
- The privacy guarantee of the SecAgg+ masks is weakened
Describe the bug
pseudo_rand_gen in framework/py/flwr/common/secure_aggregation/secaggplus_utils.py collapses the input seed to a 32-bit integer and then uses np.random.RandomState to generate the private and pairwise masks.
Why this is a problem:
This weakens the privacy guarantee that the masks are supposed to provide.
A possible fix is to replace np.random.RandomState with a cryptographic PRF/XOF such as SHAKE256 or AES-CTR-DRBG, and use the full seed/key rather than compressing it to 32 bits.
Steps/Code to Reproduce
Mask generation helper used by classical SecAgg+:
The implementation:
Expected Results
Actual Results