Building a Software RNG v1.0 with Timers, LFSR, XOR Shift, and FNV Hash algorithms
Why a Software Random Number Generator?
The STM32F401 microcontroller lacks a hardware Random Number Generator (RNG), making a Software RNG necessary for generating random numbers. While hardware RNGs provide higher-quality randomness, a Software RNG can still achieve essential functionality by generating unique cryptographic keys, mimicking real-world randomness, and introducing system unpredictability.
How the Software RNG Generates Random Numbers
Extracting Hardware-based Seed
To generate the hardware seed, TIM2 is set up to Count Upwards at 16 MHz, while TIM3 counts down at 25 kHz. The counter values from both timers are then XORed together to produce a combined seed value, which is then returned.
LFSR Transformation of Seed
The LFSR operation extracts the least significant bit (LSB) of the input seed and the seed is then right-shifted by one bit. If the original LSB was set, a feedback value (0xB400) is XORed with the shifted seed. The resulting value is returned as the new LFSR output.
Performing XOR Shift on Seed
The XOR Shift algorithm performs three bitwise operations on the variable, state. First, it XORs state with itself shifted left by 13 bits, then XORs it with state shifted right by 17 bits, and finally XORs it again with state shifted left by 5 bits. These bitwise operations mix the bits of the seed, producing a new value, which is then returned.
Combining Results and Generating Hash
It generates a hash value using the FNV Hash Algorithm. It starts by initializing a constant hash value (2166136261U). The input value (val) is then XORed with this hash value. After that, the result is multiplied by the FNV prime constant (16777619). The final hashed value is returned.
Analyzing the Randomness of the Generated Values
To assess the effectiveness of the RNG library, I generated a sample of 10,000 random numbers and stored them in the W25Q64 Flash memory for analysis. Each 32-bit random number was saved on a separate page, with one entry per page, for clarity. The library exhibited remarkable performance, generating 100,000 32-bit random numbers in just 1.58 seconds, with the system clock running at 16 MHz. Below are the scatter plots of the 10,000 random numbers across three different test cases.
This scatter plot represents the random numbers generated using a single entropy source, with only TIM2 counting up at 16 MHz. The output was then XORed with a seed constant to introduce variability.
This scatter plot depicts the random numbers generated using two entropy sources: TIM2 counting up and TIM3 counting down. The counter values were XORed together and then XORed with the seed constant to introduce variability.
The previous two samples were collected consecutively without any delay, which could potentially introduce predictability in the long-term behavior of the random numbers. To address this, I introduced variability in the sampling process by adding an adaptive delay between each call to the SoftRNG_Generate() function in every iteration of the loop.
Refinements to the Existing algorithms
To enhance randomness, an additional entropy source, such as the onboard temperature sensor, could be incorporated. During hardware seed processing, we could interleave the bits from the LFSR and XOR operation outputs, apply multiple rounds of hashing, and implement a stateful RNG design that maintains a seed state between successive calls to the SoftRNG_Generate()
function.
Checkout my GitHub repository for the source code (v1.0) of this library