Breaking Linear Congruential Generators

One way to generate pseudorandom generator is the Linear Congruential Generator . The generator is defined by the congruential relation X n + 1 = ( a X n + c ) ( m o d m ) , X_{n+1} = (aX_n + c) \pmod m, where a , c , a, c, and m m are parameters of the generator and X 0 X_0 is called the seed.

Here is one way we could implement this:

1
2
3
4
5
def getRandom():
    x = 1 #seed
    while True:
        x = (a*x + c)%m
        yield x

However, linear congruential generators are not very secure, i.e. their outputs are fairly predictable.

Here are 8 consecutive outputs from a particular LCG:

1
720555190, 133143292, 350469176, 715002068, 822810950, 400865843, 226553034, 200183345

What is the next output from the generator?


The answer is 193907871.

This section requires Javascript.
You are seeing this because something didn't load right. We suggest you, (a) try refreshing the page, (b) enabling javascript if it is disabled on your browser and, finally, (c) loading the non-javascript version of this page . We're sorry about the hassle.

2 solutions

Patrick Corn
Dec 10, 2016

There is a nice trick to solve for m . m. Let Y n = X n X n 1 . Y_n = X_n-X_{n-1}. Then Y n ( a X n 1 + b ) ( a X n 2 + b ) a ( X n 1 X n 2 ) a Y n 1 ( m o d m ) . Y_n \equiv (aX_{n-1}+b) - (aX_{n-2}+b) \equiv a(X_{n-1}-X_{n-2}) \equiv aY_{n-1} \pmod{m}. Now let Z n = Y n Y n + 2 Y n + 1 2 . Z_n = Y_nY_{n+2}-Y_{n+1}^2. Then Z n Z_n is congruent to 0 0 mod m . m.

(Proof: each Y k Y_k is congruent to a Y k 1 , a Y_{k-1}, so we can express everything in terms of Y n : Y_n: Z n = Y n Y n + 2 Y n + 1 2 Y n ( a 2 Y n ) ( a Y n ) ( a Y n ) 0 ( m o d m ) . ) Z_n = Y_nY_{n+2} - Y_{n+1}^2 \equiv Y_n (a^2 Y_n) - (aY_n)(aY_n) \equiv 0 \pmod m.)

Since each Z n Z_n is a multiple of m , m, the idea is to generate enough values of Z n Z_n and take their gcd, and expect that the answer equals m . m. (With sufficiently many Z n , Z_n, we expect there to be no larger common factor than the one that is guaranteed, namely m . m. )

Anyway, the gcd of Z 1 , , Z 6 Z_1,\ldots,Z_6 is m = 1000000007. m=1000000007.

Given m , m, it is easy enough to solve for a a and b b ; just use a X 1 + b X 2 a X 2 + b X 3 \begin{aligned} aX_1+b &\equiv X_2 \\ aX_2+b &\equiv X_3 \end{aligned} to get two equations in the unknowns a , b , a,b, whose solutions are a X 2 X 3 X 1 X 2 , b X 2 a X 1 . a \equiv \frac{X_2-X_3}{X_1-X_2}, \quad b \equiv X_2-aX_1. (These computations are all done mod m . m. When I did them, I cheated and used that m m was prime, which let me compute 1 / d 1/d as d m 2 ( m o d m ) . d^{m-2} \pmod m. Without the cheat, I would have had to write an extended Euclidean algorithm function to compute the modular inverse of X 1 X 2 X_1-X_2 .)

This gives a = 1664525 a=1664525 and b = 13904216 , b=13904216, whence the next term is 1664525 200183345 + 13904216 193907871 ( m o d 1000000007 ) . 1664525 \cdot 200183345 + 13904216 \equiv \fbox{193907871} \pmod{1000000007}.

Nice solution. Can you elaborate a bit more on why the Z_n's have this property?

Agnishom Chattopadhyay - 4 years, 6 months ago

Log in to reply

Yes!

Btw, if m m was not a prime then it was could be possible (by choosing a bad consecutive sequence) that we can't determine m m with absolute certainty ( if I'm not wrong.)

A Former Brilliant Member - 4 years, 6 months ago

Log in to reply

I read somewhere that the gcd is m with high probability.

Agnishom Chattopadhyay - 4 years, 6 months ago

Log in to reply

@Agnishom Chattopadhyay High probability != 1

A Former Brilliant Member - 4 years, 6 months ago

Log in to reply

@A Former Brilliant Member For a physicist, it is good enough as 1!

Agnishom Chattopadhyay - 4 years, 6 months ago

I edited the solution to be more verbose about the Z n . Z_n.

Patrick Corn - 4 years, 6 months ago
Abdelhamid Saadi
Dec 19, 2016

This solution written in python 3 based on the same idea of Patrick Corn solution.

Extended Euclidean algorithm is from wikibooks

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
from fractions import gcd

Xn = [720555190, 133143292, 350469176, 715002068, 822810950, 400865843, 226553034, 200183345]

def egcd(a, b):
    if a == 0:
        return (b, 0, 1)
    else:
        g, y, x = egcd(b % a, a)
        return (g, x - (b // a) * y, y)

def modinv(a, m):
    g, x, y = egcd(a, m)
    if g != 1:
        raise Exception('modular inverse does not exist')
    else:
        return x % m

def solve(Xn):
    "Breaking Linear Congruential Generators"
    Yn = [Xn[k+1]-Xn[k] for k in range(len(Xn)-1)]

    Zn = [abs(Yn[k+2]*Yn[k] - Yn[k+1]*Yn[k+1]) for k in range(len(Yn)-2)]

    m = Zn[0]
    for x in Zn[1:]:
        m =  gcd(m, x)

    a = (modinv(Yn[0] + m, m)*Yn[1])%m
    b = (Xn[1] - a *  Xn[0])%m

    x = Xn[0]
    for i in range(8):
        if x != Xn[i]:
            raise Exception('Failed to solve')
        x = (a*x + b)%m
        return x

print(solve(Xn))

0 pending reports

×

Problem Loading...

Note Loading...

Set Loading...