4

I am writing a service where a deterministic RNG is needed across multiple platforms that don't share a codebase (except for maybe C). The random numbers need to be exactly 128 bits long. Given a pre-negotiated truly random number, is it OK if I use AES to generate a sequence of random numbers? How it would work is I would encrypt the seed to get the first random number, encrypt the first random number to get the second, etc.

Basically:

rand[0] = truly_random_number;
rand[1] = AES(truly_random_number);
rand[2] = AES(AES(truly_random_number));
rand[n] = AES(AES(AES...AES(truly_random_number...))) //n times

One argument AES here is defined as the plaintext always being all zeroes.

The clients will share their sequence number as they communicate, so it should be possible for any of them to deterministically reconstruct the needed result.

Is this a proper use of AES? Can I use something faster for this, like SHA-256 and truncate the result? Should I just find a C implementation of some RNG and use that instead? I am leaning toward AES because the platforms I am targeting have AES accelerators, so the speed should not be much of an issue.

Ermir
  • 1,391
  • 11
  • 25
  • The PRNG is not guaranteed to be the same across all clients. To insure I have such a PRNG, I would have to find and implement a good one in C. As for AES using only one argument above, assume that the argument is always 0, or just a given constant. – Ermir Apr 30 '16 at 02:41
  • 1
    You could also do `rand[0] = AES(truly_random_number, 0); rand[1] = AES(truly_random_number, 1);` etc. – user253751 Apr 30 '16 at 04:14
  • 2
    If you want to ask whether this scheme is a secure seeded CSPRNG, then [crypto.se] is much better suited than Stack Overflow. – Artjom B. Apr 30 '16 at 09:20
  • 1
    1. NIST SP 800-90 already defines a good DRBG (prng) using AES. 2. Using something like the described repeated encryption can have short cycle problems (if rand[i] == rand[j] then one can predict rand[j+1] will equal rand[i+1]). 3. SHA256 is not faster than AES for this use. 4. Naturally, you're adversarial advantage explodes past the birthday bound, and thanks to the above item #2 this is an exploitable advantage. – Thomas M. DuBuisson May 05 '16 at 01:06
  • Assuming you're just doing some Monte-Carlo simulations (*no* security guarantees): [Mersenne twister](https://en.wikipedia.org/wiki/Mersenne_Twister) is extremely common across tons of languages, so you can almost certainly find a well-optimized implementation for your language of choice. You could do a validation step and generate *n* "random" numbers from a fixed seed(s) and compare it to the known output to make sure it doesn't change out from under you. Alternatively, repeated hashes (MD5, SHA512) may end up being faster than encryption. – Nick T Oct 16 '19 at 11:32

4 Answers4

1

Given a pre-negotiated truly random number, is it OK if I use AES to generate a sequence of random numbers?

Yes; that's exactly what, at its core, a stream cipher is.

However, it's inappropriate to be using a custom construction like your iterated-ECB:

rand[n] = AES(AES(AES...AES(truly_random_number...))) //n times

…when there already exist well-established stream cipher constructions, like Counter mode.

AES_ctr128_encrypt(all_zeroes, rand+n, 16, truly_random_number, &n, ecount_buf, 1)

(The above snippet elides some serious detail; see also the new EVP_CIPHER interface. I don't know what library you're actually using. Hand-cranking AES in C is not easy.)


Python example of using AES in Counter mode as an RNG:

from Cryptodome.Cipher import AES


def pseudorand_block(key: bytes, offset: int, *, nonce=b''):
    c = AES.new(key, AES.MODE_CTR, nonce=nonce, initial_value=offset)
    return c.encrypt(bytes(AES.block_size))


def pseudorand_blocks(key: bytes, start_offset: int=0, *, nonce=b''):
    c = AES.new(key, AES.MODE_CTR, nonce=nonce, initial_value=start_offset)
    _0block = bytes(AES.block_size)
    while True:
        yield c.encrypt(_0block)


# test value:
assert pseudorand_block(bytes.fromhex('000102030405060708090A0B0C0D0E0F'), 99) == \
       int.to_bytes(179565763088325722286336838177169761478, 16, 'little')
       
0

I'd consider doing this to be a hack at best.

AES is an encryption algorithm, not a random number generation algorithm. I wouldn't expect applying AES over and over again to produce decent randomness.

You mention you're worried about performance and want to use AES hardware. The reason AES hardware acceleration is that AES is fairly complicated. Most PRNGs, however, are not; xorshift, for example, is only a few xor and shift operations. You'd also be reliant on your target hardware having AES accelerators.

Get a decent C PRNG library from somewhere (they're not hard to find), seed it with your shared random number, and leave it at that.

Colonel Thirty Two
  • 23,953
  • 8
  • 45
  • 85
  • `I wouldn't expect applying AES over and over again to produce decent randomness.` well, AES(++k) was tested to be pretty decent RNG, IIRC. Though advice to get reasonable PRNG library is right on the money. – Severin Pappadeux May 02 '16 at 17:34
0

You do not state your security requirements. If you want a cryptographically secure solution then your question is not trivial. See A Block Cipher based Pseudo Random Number Generator Secure Against Side-Channel Key Recovery for one possible solution.

Such an RNG will be slow, due to the security requirements. As Colonel Thirty Two says, your problem may be better solved by a non-crypto RNG which will be faster and probably easier to maintain.

rossum
  • 15,344
  • 1
  • 24
  • 38
-1

You should have a read of the following, it has links to other references on PRNG's

http://c-faq.com/lib/rand.html

Don't do what you're doing unless you find something in peer reviewed literature that this would work and even then I'd treat it with caution. If you want a portable PRNG then the one given in the above link is

#define a 48271
#define m 2147483647
#define q (m / a)
#define r (m % a)

static long int seed = 1;

long int PMrand()
{
    long int hi = seed / q;
    long int lo = seed % q;
    long int test = a * lo - r * hi;
    if(test > 0)
        seed = test;
    else    seed = test + m;
    return seed;
}

Note the change to a near the bottom of the doc. You should also trust none of what I've just written ie do your homework and make sure that the above or some other PRNG works for your application.

Harry
  • 11,298
  • 1
  • 29
  • 43
  • https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator According to this article my suggestion seems to be quite common. Performance seems the be the only criticism. – Ermir Apr 30 '16 at 08:25
  • @Ermir A CPRNG is not what you're describing in your question. Random numbers and security might appear together a lot but they're two very different topics. – Harry Apr 30 '16 at 08:30