1

I want to generate 6 random words using Wiktionary and random numbers from /dev/random. I'll get a random number with /dev/random and then use the word from that index.

I know /dev/random should be used to generate random keys, but if it's deterministic what prevents somebody from knowing the future bytes from the old ones? That would be a problem if, for example, an attacker knows one word of my random word list.

I am aware that the LNRG changed recently in 5.18. I'm using this version.

PasWei
  • 722
  • 3
  • 14
Xephobia
  • 13
  • 4

2 Answers2

4

The CSPRNG used in Linux is effectively the ChaCha stream cipher. Like all cryptographically secure PRNGs, it passes the next bit test, which says that given an arbitrary amount of output from the PRNG, an attacker cannot predict the next bit with a probability substantially greater than 0.5. This test covers all possible ways of guessing: statistical analysis of the stream, intensive analysis of the algorithm, or any other means.

ChaCha uses a permutation and combines the original input, which contains the key and nonce, with the result of the permutation to prevent inverting the operation. This approach is also used by SHA-2 and BLAKE2 (although with different inputs) to great success. We presently believe that an attacker cannot distinguish the output of ChaCha20 from random, and the best known attacks are on 7 of 20 rounds. Because the ChaCha output isn't invertible and there's no way to distinguish it from random, we don't believe there's any way to guess future outputs that's better than chance. (Technically, we believe the attacker could guess by brute force, but the 2^-256 advantage is considered to be negligible here.)

In addition, recent versions of the Linux CSPRNG also use fast key erasure. That means that once random bytes are generated, additional CSPRNG output is used to change the key used, so that if the kernel's internal state is leaked, previous outputs are not compromised.

If your question is about the seeding of the CSPRNG, that's essentially done by hashing inputs with the keyed hash function BLAKE2s to produce a key for ChaCha. These inputs are things like interrupt timings, which each contain a small amount of entropy, and when 256 bits of entropy are believed to have been collected, the key is generated and ChaCha is seeded. Because BLAKE2s in this case is believed to be a pseudorandom function, this is a secure way of distilling the entropy into a suitable key for ChaCha.

If you're interested in how ChaCha works, the Wikipedia article is actually quite good. The algorithm is really quite simple and could be implemented from memory, The code is relatively easy to follow and demonstrates the main idea. Note that even though it looks very simple, substantial cryptanalysis has gone into ensuring that it's secure, and producing an algorithm that is both secure and simple isn't easy.

bk2204
  • 7,828
  • 16
  • 15
  • For those looking for more details, the the [source code is documented](https://github.com/torvalds/linux/blob/master/drivers/char/random.c) in its comments. – A. Hersean Aug 03 '22 at 07:46
  • ChaCha "is really quite simple and could be implemented from memory"... It's a beautiful algorithm, simple to understand and to implement. I recommend reading the Wikipedia entry, it's concise and informative! – ThoriumBR Aug 03 '22 at 10:32
  • @A.Hersean There's also the wonderful [LRNG document](https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/Studies/LinuxRNG/LinuxRNG_EN.html) which is, by far, the most detailed and up-to-date analysis and description of the Linux random driver. – forest Aug 05 '22 at 00:55
  • @forest I read it before. It's sadly outdated. The Linux CSPRNG is currently being rewritten (upgraded) by Jason A. Donenfeld (the author of Wireguard), one small patch at a time. – A. Hersean Aug 05 '22 at 08:16
-4

To understand this better, let us look at a very simple, self-made RNG algortithm. It has a counter that starts at 0 and with each call to the rng, it feeds the current counter into a SHA-256 function, then takes the last 32 bits and converts them to an integer and returns them. Finally, the counter is increased by 1. As pseudo-code, it would look like this:

counter = 0

fn getRandom()
{
    return SHA-256(counter++) ^ 0xFFFFFFFF;
}

Calling this RNG would yield the following results:

 397385757
2005222810
1683478918
2919180741
2177408625

As you can see, these are rather difficult to predict. By merely looking at the output, it doesn't seem easily plausible to guess the next number. To prove my point, I will set the counter to a random 32-bit state and generate 3 more numbers.

2090723734
3074314699
 306688151

Good luck finding the next number!


This demonstration aside, a random number generator is judged by its ability to be unpredictable to an attacker, even when they are able to observe a large amount of random data generated. In an ideal RNG, an attacker has exactly a 50% chance of guessing the next bit correctly, and when it comes to /dev/random, the attacker's chance is not much better.

In practice, how random data is used by an application is often way more relevant than the RNG being used.

schroeder
  • 123,438
  • 55
  • 284
  • 319
  • 2
    Anecdotal evidence is not a proof or demonstration. – A. Hersean Aug 03 '22 at 07:40
  • 1
    "As you can see, these are rather difficult to predict." -- what? Based solely on visual pattern matching of the small sample set of output? And you are completely ignoring Kerckhoffs'? – schroeder Aug 03 '22 at 08:33
  • 2
    It's pretty trivial to find the next number: just bruteforce `counter` until you get the current output and you can determine all past and future outputs. – ThoriumBR Aug 03 '22 at 10:35
  • 4
    The next number in the sequence is **750155109**. As @ThoriumBR points out, this is not hard to crack. Here is the python script that I used to crack the counter value: https://pastebin.com/mAN3UzmF. This took less than 10 minutes to run on my ancient laptop. And, here is the script that I used to get the next value in the sequence, once the counter was cracked: https://pastebin.com/MbqXkRGg. – mti2935 Aug 03 '22 at 15:10
  • 2
    Notwithstanding, even if you make the counter space much larger (e.g. 256 bits, instead of just 32) to thwart a brute force attack - then this leads to another problem: how do you choose a random seed for the counter to start at? As you can see, this RNG needs another RNG to seed the counter, so it becomes and chicken-and-egg problem. What this illustrates is how deceptively difficult it is to build a true cryptographically secure pseudo random number generator (CSPRNG), as @schroeder alludes to in his comment. – mti2935 Aug 03 '22 at 15:21
  • 2
    Apart from all the other problems, your pseudocode uses `^` which is generally used for xor. You probably wanted to use `&`. – nobody Aug 03 '22 at 20:13