The archived material for the Stanford University course on cryptography at coursera.org includes a problem where you have to predict the next output of a weak PRG. It can be briefly restated as follows:
$$\begin{align*} P =&\ 295075153 \\ z_n =&\ x_n \oplus y_n,\qquad \textrm{where}\ 0 \le x_0, y_0 \lt P\ \textrm{(unknown random integers)} \\ x_{n+1} =& (2x_n + 5) \mod P \\ y_{n+1} =& (3y_n + 7) \mod P \\ (z_0, z_1, ..., z_8) =& (210205973, 22795300, 58776750, \\ &\ \ 121262470, 264731963, 140842553, \\ &\ \ 242590528, 195244728, 86752752) \\ z_9 =& ??? \end{align*}$$
I wrote a small program in C to solve this by brute force, and it finds a solution in just over 2 seconds:
#include <stdio.h>
#define P 295075153L
int main() {
long seq[] = { 210205973, 22795300, 58776750, 121262470, 264731963,
140842553, 242590528, 195244728, 86752752 };
long i, x, y, x0, y0;
for (x0=0; x0<P; x0++) { /* Try every value of x_0 */
y0 = seq[0] ^ x0; /* Calculate y_0 from x_0 */
for (x=x0,y=y0,i=1; i<9; i++) {
x = (2 * x + 5) % P; /* Iterate PRG and check values */
y = (3 * y + 7) % P;
if ((x ^ y) != seq[i]) break;
}
if (i==9) {
printf ("Solution found: x0=%ld, y0=%ld\n",x0, y0);
for (x=x0,y=y0,i=1; i<10; i++) {
x = (2 * x + 5) % P;
y = (3 * y + 7) % P;
}
printf("Next value: %ld\n",x^y);
return 0;
}
}
puts("No solution found");
return 0;
}
I also tried to solve this problem in Python, but ran into two problems:
Although it does find the same solution, it takes about 3½ minutes to do so. This means it is about a hundred times slower than the C program. Surely this can't be right?
I'm also not happy with the way I'm checking for a match with the sequence values. In the C code,
i
is incremented at the end of eachfor()
loop iteration, so wheni==9
I can be sure that every value was matched successfully. But this incrementing happens somewhere else in Python, so instead I have to check fori==8
and will report a successful match even if the last comparison fails. How can I do this properly?
from itertools import count
seq = [210205973,22795300,58776750,121262470,264731963,
140842553,242590528,195244728,86752752]
P = 295075153L
for x0 in count(): # better than range(0,P), which is **extremely** slow
if x0==P:
break
y0 = seq[0] ^ x0
x, y = x0, y0
for i in range(1,9):
x = (2 * x + 5) % P
y = (3 * y + 7) % P
if (x ^ y) != seq[i]:
break
if i==8: # will be true even if seq[8] not matched :-(
break
if i==8:
print "Solution found: x0=%d, y0=%d" % (x0,y0)
x, y = x0, y0
for i in range(1,10):
x = (2 * x + 5) % P
y = (3 * y + 7) % P
print "Next value: %d" % (x ^ y)
else:
print "No solution found"
range
builds a list, so ifP
is big it's going to use tons of memory. In python2 you should usexrange
(in python3 the oldrange
is done andxrange
was renamedrange
). \$\endgroup\$P
, so I'm pretty sure we can do much better than brute-forcing. Once you find a better algorithm the difference in time probably will be much smaller for the simple fact that the solution will be found in a matter of instants in both programs. \$\endgroup\$