Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gteissier
GitHub Repository: gteissier/erl-matter
Path: blob/master/revert-prng.sage
271 views
1
#!/usr/bin/env sage
2
3
'''
4
Adapted from initial work performed by guillaume kaim.
5
6
Example usage:
7
8
echo "ELDUPJHMPTCVINSPFDTA" | ./revert-prng.sage
9
404289480
10
11
it works with sage 10.3
12
13
to verify the solution, you can use derive_cookie(seed), and check it effectively equals the input cookie
14
'''
15
16
import string
17
import sys
18
from scipy.stats import describe, tstd, cumfreq
19
from sage.all import QQ, ZZ, matrix, vector
20
21
22
def attack(y, n, m, a, c):
23
"""
24
Slightly modified version from https://github.com/jvdsn/crypto-attacks/blob/master/attacks/lcg/truncated_state_recovery.py
25
26
Recovers the states associated with the outputs from a truncated linear congruential generator.
27
More information: Frieze, A. et al., "Reconstructing Truncated Integer Variables Satisfying Linear Congruences"
28
:param y: the sequential output values obtained from the truncated LCG (the states truncated in n possible values)
29
:param n: the number of possible y values
30
:param m: the modulus of the LCG
31
:param a: the multiplier of the LCG
32
:param c: the increment of the LCG
33
:return: a list containing the states associated with the provided outputs
34
"""
35
# Preparing for the lattice reduction.
36
delta = c % m
37
y = vector(ZZ, y)
38
for i in range(len(y)):
39
# Shift output value to the MSBs and remove the increment.
40
y[i] = y[i] * m // n - delta
41
delta = (a * delta + c) % m
42
43
# This lattice only works for increment = 0.
44
B = matrix(ZZ, len(y), len(y))
45
B[0, 0] = m
46
for i in range(1, len(y)):
47
B[i, 0] = a ** i
48
B[i, i] = -1
49
50
B = B.LLL()
51
52
# Finding the target value to solve the equation for the states.
53
b = B * y
54
for i in range(len(b)):
55
b[i] = round(QQ(b[i]) / m) * m - b[i]
56
57
# Recovering the states
58
delta = c % m
59
x = list(B.solve_right(b))
60
for i, state in enumerate(x):
61
# Adding the MSBs and the increment back again.
62
x[i] = int(y[i] + state + delta)
63
delta = (a * delta + c) % m
64
65
return x
66
67
N = 2**36
68
69
F = IntegerModRing(N)
70
71
A = F(17059465)
72
B = F(1)
73
74
75
def split_cookie(cookie):
76
nums = []
77
for i in range(19, -1, -1):
78
assert(cookie[i] in string.ascii_uppercase)
79
nums.append(ord(cookie[i]) - ord('A'))
80
return nums
81
82
def revert_prng(cookie):
83
nums = split_cookie(cookie)
84
assert(len(nums) == 20)
85
86
x = attack(nums, 26, N, int(A), int(B))
87
seed = (x[0] - B) / A
88
89
return int(seed)
90
91
92
def derive_cookie(seed):
93
cookie = ''.join([c for c in random_cookie(20, seed)])
94
return cookie[::-1]
95
96
def random_cookie(n, seed):
97
x = seed
98
for i in range(n):
99
x = next_random(x)
100
yield chr((x*(26) // 0x1000000000) + ord('A'))
101
102
def next_random(x):
103
ret = (x*17059465+1) & 0xfffffffff
104
return ret
105
106
107
mode = 'print'
108
if len(sys.argv) == 2 and sys.argv[1] == 'stats':
109
mode = 'stats'
110
111
112
seeds = []
113
for cookie in sys.stdin.readlines():
114
cookie = cookie.rstrip('\n')
115
if len(cookie) == 0:
116
break
117
118
seed = revert_prng(cookie)
119
assert(derive_cookie(seed) == cookie)
120
121
if mode == 'print':
122
print(seed)
123
elif mode == 'stats':
124
seeds.append(seed)
125
126
if mode == 'stats':
127
desc = describe(seeds)
128
stddev = tstd(seeds)
129
print('number of cookies: %d' % desc.nobs)
130
print(' min seed: %d' % desc.minmax[0])
131
print(' max seed: %d' % desc.minmax[1])
132
print(' mean seed: %.f' % desc.mean)
133
print(' std deviation: %.f' % stddev)
134
135
136