Linear Congruential Generator
A linear congruential generator (LCG) is an algorithm that yields a sequence of pseudo-randomized numbers. This one one of the oldest and best-known pseudorandom number generator algorithms.
Basics
The generator is defined by the recurrence relation:
\[ X_{n+1} \equiv (aX_n + c) \mod m \]where \(X\) is the sequence of pseudo-random values, and
- \(m\) is the modulus (usually a prime power)
- \(a\), \(0 < a < m\) is the multiplier
- \(c\), \(0 < c < m\) is the increment
- \(X_0\), \(0 < X_0 < m\) is the seed (or starting value)
Python implementation
Here is a simple implementation of an LCG using Python classes
| |
Recover parameters
Find the modulus
If you have access to at least \(4\) consecutive outputs of the LCG, namely \(X_1, X_2, X_3\) and \(X_4\), you can recover the modulus used to generate those numbers. But how ?
Let’s define the recurrence \(T_n = X_{n+1} - X_n\)
We have
\[ \begin{aligned} T_0 = X_1 - X_0 \equiv (aX_0 + c) - X_0 \mod m \\ T_1 = X_2 - X_1 \equiv (aX_1 + c) - X_1 \mod m \\ T_2 = X_3 - X_2 \equiv (aX_2 + c) - X_2 \mod m \\ T_3 = X_4 - X_3 \equiv (aX_3 + c) - X_3 \mod m \\ \end{aligned} \]Between consecutive elements in \(T\), we can notice this pattern:
\[ \begin{aligned} T_n &\equiv aS_n + c - S_n &\mod m \\ &\equiv a(aS_{n-1} + c) - (aS_{n-1} + c) &\mod m \\ &\equiv a^2S_{n-1} + ac + c - aS_{n-1} - c &\mod m \\ &\equiv a(S_{n-1} + c - aS_{n-1}) &\mod m \\ &\equiv aT_{n-1} &\mod m \\ \end{aligned} \]Meaning that \(T\) forms its own LCG with the same modulus and multiplier but no increment. With this new LCG, we can now easily find the modulus by just multiplying different items in \(T\) to get differences
We know
\[ \begin{aligned} T_1 &\equiv aT_0 \mod m \\ T_2 &\equiv aT_1 \mod m \\ \end{aligned} \]and
\[ \begin{aligned} T_1^2 &\equiv a^2 T_0^2 &\mod m \\ \\ T_2 T_0 &\equiv aT_1 T_0 &\mod m \\ &\equiv a(aT_0) T_0 &\mod m \\ &\equiv a^2 T_0^2 &\mod m \\ \end{aligned} \]Thus, we have
\[ \begin{aligned} &T_1^2 - T_2 T_0 \equiv 0 &\mod m \\ \Longleftrightarrow \quad &T_1^2 - T_2 T_0 = k \cdot m \\ \end{aligned} \]for some integer \(k \geqslant 1\).
Remark
Note that in this case, you only have a multiple of the modulus as the result. To recover \(m\) from there, factor it, then divide it repeatedly by its factors until you find the correct one, which is the one with the correct bit size (we suppose you have this information).
Now, in most cases, \(k \neq 1\), but if you have \(5\) outputs, you can then calculate
\[ T_2^2 - T_3 T_1 = k^{'} \cdot m \]for some other integer \(k^{'} \geqslant 1\).
You can now finally retrieve the modulus
\[ \begin{aligned} m &= gcd(T_2^2 - T_3 T_1, \ T_1^2 - T_2 T_0) \\ &= gcd(k^{'} \cdot m, \ k \cdot m) \\ \end{aligned} \]Here is how it looks using Python
| |
Recover the multiplier
Now that you know the modulus \(m\), you can recover the multiplier \(a\) with only \(3\) consecutive outputs of the LCG.
You have
\[ \begin{aligned} X_1 &\equiv aX_0 + c \mod m \\ X_2 &\equiv aX_1 + c \mod m \\ X_3 &\equiv aX_2 + c \mod m \\ \end{aligned} \]and
\[ \begin{aligned} X_2 - X_1 &\equiv (aX_1 + c) - (aX_0 + c) \equiv a(X_1 - X_0) \mod m \\ X_3 - X_2 &\equiv (aX_2 + c) - (aX_1 + c) \equiv a(X_2 - X_1) \mod m \\ \end{aligned} \]Thus, by re-arranging, you can calculate
\[ a \equiv \frac{X_3 - X_2}{X_2 - X_1} \mod m \]Not invertible ?
If \(T_1\) is not co-prime to \(m\), then \(T_1\) is not invertible mod \(m\) and you can’t compute \((X_2 - X_1)^{-1} \mod m\).
Note
This can happen especially when \(M\) is not prime.
You can still recover \(a\) the following way. First calculate
\[ g = gcd(T_2, T_1, m) \]then, because \(T_1 \equiv aT_0 \mod m\), you have
\[ \begin{aligned} \frac{T_1}{g} &\equiv a\frac{T_0}{g} &\mod \frac{m}{g} \\ \Longleftrightarrow \quad a &\equiv \frac{T_1}{g} \left(\frac{T_0}{g}\right)^{-1} &\mod \frac{m}{g} \\ \end{aligned} \]Not the correct multiplier ?
If \(a\) is too small or too big after this calculation, we can just add or subtract \(\frac{m}{g}\) to it until it properly satisfies the \(T\) LCG.
You can do all of this using Sage
| |
Calculate the increment
Finally, knowing the modulus \(m\) and the multiplier \(a\), you can easily calculate the increment \(c\) by using only two consecutive ouputs.
\[ \begin{aligned} &X_2 \equiv aX_1 + c \mod m \\ \Longleftrightarrow \quad &c \equiv X_2 - aX_1 \mod m \end{aligned} \]In Python, it is a simple as this
| |
Going backward
Reversing the LCG is trivial now that we know all the parameters. Starting from the definition of \(X_{n+1}\), we can calculate \(X_n\).
\[ \begin{aligned} X_{n+1} &\equiv (aX_n + c) &\mod m \\ \Longleftrightarrow \quad X_n &\equiv a^{-1}(X_{n+1} - c) &\mod m \\ \end{aligned} \] | |
Attacks
Truncated LCG
Parameter recovery
TODO
State recovery
TODO