Cryptographic parameters are often initialised in a deterministicly with a pseudorandom number generator constructed using a known secure hash function. If the seed is public, the produced numbers are verifiably random and are used to generate a list of basis generators as in ShuffleProofs.jl
or cryptographic parameters. Alternatively, when the seed is private, the secret random numbers can be generated deterministically. This is often used in embedded devices where the seed is burned into the device, and hence, random numbers can be secured without relying on hardware. Alternatively, the deterministic random number generation can help to reduce the attack surface as security only needs to be ensured for the seed generation by the system and is used in CryptoSignatures.jl
in this way.
CryptoPRG provides deterministic pseudorandom generators in Julia for cryptographic applications. It implements pseudorandom number generators according to Verificatum verifier specification. Note that FIPS pseudorandom number specification would be in the scope of this package but is currently not in the roadmap; hence, contributions are welcome. More pseudorandom number generator implementations are welcome to be included in the package as long as they can be tested with reputable specification test vectors.
To use the Verificatum pseudorandom number generator, we can use the following demonstrative code:
using CryptoPRG.Verificatum
hasher = HashSpec("sha256")
seed = convert(Vector{UInt8}, "SEED")
prg = PRG(hasher, seed)
The pseudorandom number generator supports multiple methods. The prg(counter::UInt32)
is a low-level interface that provides a hash with a given seed and counter computed as Hash(seed|counter)
. A higher level abstraction is the byte string that can be accessed as prg[1:10]
, giving the first ten bytes. The byte string has a period of 2^257/8
bytes (although, in practice, we are limited by the byUInt32 counter). Hence, it is computationally unable to predict what follows, knowing the one-byte string or finding a seed with which the byte string had been produced.
The pseudorandom number generator prg
can be used to construct a vector of random number numbers as simple as:
rand(prg, 1:100, 10)
which will produce ten random numbers by casting the byte string into numbers of range 1:100. Note that the random numbers are made with bias as those are generated by taking modulus, in this case, from UIn8
. This can be a security risk that can lead to leaking involved secrets. A proper strategy would be skipping the number out of range or doing a rolling window in the pseudorandom byte string. Some of those strategies could be in scope to be implemented in the Verificatum module as long as they still can be used without producing verifiable random generators. Also, note that reuse of prg
produces the same random numbers; hence, reuse poses a security risk. To avoid that, PRG in practice shall be constructed via random oracle.
The Verificatum specifies random oracles to generate pseudorandom number generators. This can be used by constructing an ROPRG
instance as follows:
hasher = HashSpec("sha256")
rho = convert(Vector{UInt8}, "seed")
roprg = ROPRG(rho, hasher)
prg_a = roprg("a")
prg_b = roprg(:b)
This is a convenient abstraction for constructing multiple pseudorandom generators from a single seed rho
. This is used internally in ShuffleProofs.jl
for proof generation, where multiple random number vectors are necessary.