# Is there something wrong with using a hash function as a PRG?

I need to create cryptographically secure pseudorandomness in JavaScript. However, when I googled for PRGs, all I found was very sketchy.

My idea is as follows (in pseudocode):

seed = "0x1a29fd..." // long number I always get passed (impossible to guess but used in a different context as well)
hashedAndSaltedSeed = sha256sum("seed: " + seed)
purpose = "..." // my current function's name (no spaces)
usageIndex = 1; // will increment this each time

randomness = myPrg(hashedAndSaltedSeed, purpose, usageIndex)

function myPrg(hashedAndSaltedSeed, purpose, usageIndex, numberOfBytes) {
if(numberOfBytes > 32) {
fail()
}

input = purpose + " " + usageIndex + " " + hashedAndSaltedSeed
return sha256sum(input).binary2hex().slice(0, 2*numberOfBytes).hex2binary()
}


I only need a small amount of randomness (at most 32 Byte at a time, very few times), so the speed difference won't matter. But is there anything else wrong with this approach?

The randomness I need doesn't need to be distributed perfectly randomly but it needs to be infeasible to guess the resulting randomness when only given purpose and usageIndex but not seed nor hashedAndSaltedSeed.

Edit: I'm sorry that I forgot to mention an important requirement. I'm sure it was in my question at some point as I wrote it but I seem to have deleted that part accidentally. I need to be able to reproduce the same randomness when given the same seed. That's why I can't just use something that gives me randomness but doesn't let me control the seed.

• You can use /dev/urandom as nulltask/urandom.js – kelalaka Aug 26 '19 at 20:39
• If seed is secret drawn uniformly at random from $2^{256}$ possibilities, that's fine, but your naming is a little confusing: what is the salt in hashedAndSaltedSeed? This also, of course, begs the question of where seed comes from, but if your present goal is just to have a pseudorandom function family for which it is the caller's responsibility to choose a seed uniformly at random, then that's fine. – Squeamish Ossifrage Aug 26 '19 at 21:16
• @kelalaka That won't work because I need to be able to reproduce the same randomness. (Sorry for not having mentioned this requirement in my initial question.) – UTF-8 Aug 26 '19 at 23:40
• You should make sure that the inputs are encoded uniquely. As is, distinct inputs with spaces might get confused with one another and cause the same result. – Squeamish Ossifrage Aug 27 '19 at 1:13
• If I pass in purpose="hello world" and usageIndex="foobar", I'll get the same key as if I pass in purpose="hello" and usageIndex="world foobar" even though the inputs are distinct. – Squeamish Ossifrage Aug 27 '19 at 12:29

This is fine, although you should avoid these conversions to and from hexadecimal, that are no needed and may introduce side channels.

Now, since you are using JavaScript, you can simply use the WebCrypto API. Specifically crypto.GetRandomValues, which can fill an array up to 65536 bytes.

• That's one of the libraries I came across when looking for one prior to asking. It doesn't let me control the seed, does it? (Sorry for not having mentioned this requirement in my initial question.) – UTF-8 Aug 26 '19 at 23:39
• @UTF-8 what amount of control do you need? – Natanael Aug 27 '19 at 5:54
• I need to be able to specify a seed. When I specify the same seed again, I need to get the same pseudorandomness out (all the calls will be exactly the same order and exactly the same ranges if different ranges / sizes are possible). – UTF-8 Aug 27 '19 at 9:39

If instead of using a hash function, you used a block cipher, this would be precisely using CTR mode as a PRNG. This can definitely work, but I'd recommend you read this post to see some implementation details you'd want to be careful about (for the case of using a block cipher/AES).

• This seems like a better idea than using a hash function. Thank you! – UTF-8 Aug 28 '19 at 11:53
• Actually you'll get better security with essentially no limits on the volume of data you can process if you use SHA-256, and less complexity of code to hash structured inputs like your purpose and usage index (as long as the inputs are uniquely encoded and prefix-free), and you won't invite the standard timing side channel attacks that AES invites. The performance may not be as good but that's probably not going to be your bottleneck here. – Squeamish Ossifrage Aug 28 '19 at 14:41
• @SqueamishOssifrage I can see that a random oracle would be better than an ideal cipher. But real hash functions might only produce multiples of 7 w/o violating the criteria of collision-resistant hash functions, or they might have other characteristics. Furthermore, ciphers are guaranteed to use the entire value space. – UTF-8 Aug 28 '19 at 19:08
• @UTF-8 That's why I said SHA-256 specifically, and not ‘a collision-resistant hash function’ generally. (See crypto.stackexchange.com/a/70709 for a more nuanced discussion.) Use of the entire value space is actually part of what weakens the security of, e.g., AES-CTR in contrast to ‘SHA256-CTR’ and why safe data volume limits are something you actually have to worry about for AES-CTR, unlike SHA256-CTR. – Squeamish Ossifrage Aug 28 '19 at 19:15