3.6. Extension: Secure Random Number Generation#

In the TLS handshake, the client is responsible for generating a random symmetric key that will be used to encrypt communication between the client and server. Since predictable keys would compromise security, it is crucial that this key is generated in a way that is truly random and unpredictable.

If an attacker could predict the symmetric key, they would be able to decrypt the communication between the client and server, completely breaking the security of TLS.

3.6.1. PRNGs: Pseudorandom Number Generators#

Most random number generation uses Pseudorandom Number Generators (PRNG). These algorithms produce a sequence of numbers that appear random but are actually generated using a fixed mathematical formula.

PRNGs are deterministic, meaning that if you start with the same initial value (called a seed), they will produce the same sequence of numbers every time.

The Linear Congruential Generator (LCG)#

A simple PRNG, the Linear Congruential Generator (LCG), generates numbers using the formula:

\[X_{n+1} ​= (a X_n ​+ c) \mod m\]

where:

  • \(X_n\) is the current number,

  • \(a\), \(c\) and \(m\) are constants we choose

  • \(X_0\) is the seed (first value).

Example

Notice that running the code below generates the same values every time.

def random_number(current):
    a = 1664525
    c = 1013904223
    m = 2**32

    return (a * current + c) % m


xn = 42  # Set the seed
for i in range(3):
    xn = random_number(xn)
    print(xn)
Output
1083814273
378494188
2479403867

3.6.2. Cryptographic Random Number Generators (CSPRNGs)#

Since PRNGs are predictable, modern cryptographic systems use Cryptographically Secure Pseudorandom Number Generators (CSPRNGs), which are designed to be unpredictable.

CSPRNGs are designed with the following properties:

  1. Given the current value in a sequence, it is infeasible to predict the next value

  2. Given the current internal state (constants), it is infeasible to predict the previous values

While there are algorithms that achieve this, it is much simpler to use an an entropy source, which is randomness collected from the real world, to generate numbers that cannot be guessed.

Entropy Sources#

Consumer devices gather entropy (randomness) from a variety of unpredictable physical sources, including:

  • Keyboard and Mouse Input – The timing of keystrokes and mouse movements are highly unpredictable.

  • CPU and Hardware Events - Variations in processor timing, power consumption, and temperature fluctuations introduce randomness.

  • Disk and Network Activity – Delays in accessing a hard drive or network jitter provide non-deterministic data.

  • Dedicated Hardware Random Generators (TRNGs) – Some modern CPUs include built-in hardware random number generators, which use electrical noise to generate randomness.

Example#

In the example below, we read from the /dev/urandom file available on Linux. Reading from this file gives us cryptographically secure random numbers from an entropy source.

The example specifically reads four bytes (32 bits) from the file to create an unsigned integer.

import os

random_number = int.from_bytes(os.urandom(4))

print(random_number)