LCG(线性同余生成器)
LCG属于PRNG(伪随机数生成器)和stream cipher(流密码)的一种,是一种产生伪随机数的方法。
X
n
+
1
=
(
a
∗
X
n
+
b
)
m
o
d
m
X_{n+1}=(a*X_{n}+b)\textbf{ }mod\textbf{ }m
Xn+1=(a∗Xn+b) mod m
其中,Xn代表第n个生成的随机数,X0被称为种子值。这里还定义了三个整数:a乘数、b增量、m模数,是产生器设定的常数。
参数选择
LCG的性质与参数的选择密切相关,不同的参数可能导致不同的随机序列。一般按照如下要求选择参数:
1、m是随机序列的模数,必须一个大于0的正整数。一般是一个比较大的素数或者是2的幂,以便提供较长的周期长度。
2、a是乘数,必须是一个与m互素的正整数。
3、b是增量,也必须是一个与m互素的正整数。
常用公式
公式一:由Xn+1反推Xn
X n = ( ( X n + 1 − b ) ∗ a − 1 ) m o d m , 这 里 a − 1 是 模 逆 元 X_{n}=((X_{n+1}-b)*a^{-1})\textbf{ }mod\textbf{ }m,这里a^{-1}是模逆元 Xn=((Xn+1−b)∗a−1) mod m,这里a−1是模逆元
公式二:求a
{ X n + 1 = ( a ∗ X n + b ) m o d m X n = ( a ∗ X n − 1 ) m o d m ⇒ a = ( ( X n + 1 − X n ) ∗ ( X n − X n − 1 ) − 1 ) m o d m \left\{\begin{matrix} X_{n+1} & = &(a*X_{n}+b) &mod &m \\ X_{n} & = &(a*X_{n-1}) & mod &m \end{matrix}\right.\Rightarrow a=((X_{n+1}-X_{n})*(X_{n}-X_{n-1})^{-1}\textup{ })mod\textbf{ }m {Xn+1Xn==(a∗Xn+b)(a∗Xn−1)modmodmm⇒a=((Xn+1−Xn)∗(Xn−Xn−1)−1 )mod m
公式三:求b
b = ( X n + 1 − a ∗ X n ) m o d m b=(X_{n+1}-a*X_{n})\textbf{ }mod\textbf{ }m b=(Xn+1−a∗Xn) mod m
公式四:求m
t n = X n + 1 − X n , t n = ( a ∗ X n + b ) − ( a ∗ X n − 1 + b ) = a ( X n − X n − 1 ) = a ∗ t n − 1 m o d m . t_{n}=X_{n+1}-X_{n},t_{n}=(a*X_{n}+b)-(a*X_{n-1}+b)=a(X_{n}-X_{n-1})=a*t_{n-1}\textbf{ } mod\textbf{ }m. tn=Xn+1−Xn,tn=(a∗Xn+b)−(a∗Xn−1+b)=a(Xn−Xn−1)=a∗tn−1 mod m.
∴ t n + 1 t n − 1 − t n t n = a ∗ a ∗ t n − 1 ∗ t n − 1 − a ∗ t n − 1 ∗ a ∗ t n − 1 = 0 m o d m \therefore t_{n+1}t_{n-1}-t_{n}t_{n}=a*a*t_{n-1}*t_{n-1}-a*t_{n-1}*a*t_{n-1}=0\textup{ }mod\textup{ }m ∴tn+1tn−1−tntn=a∗a∗tn−1∗tn−1−a∗tn−1∗a∗tn−1=0 mod m
即 T n = t n + 1 t n − 1 − t n t n 是 m 的 倍 数 , 故 T n 和 T n − 1 的 最 大 公 因 数 即 为 m 即T_{n}=t_{n+1}t_{n-1}-t_{n}t_{n}是m的倍数,故T_{n}和T_{n-1}的最大公因数即为m 即Tn=tn+1tn−1−tntn是m的倍数,故Tn和Tn−1的最大公因数即为m
常见六种题型
LCG_1:a、b、m都知道,此类题相当于由Xn+1反推Xn.
from Crypto.Util.number import *
flag = b'NSSCTF{******}'
class LCG:
def __init__(self, seed, a, b, m):
self.seed = seed # 初始种子
self.a = a # 乘数
self.b = b # 增量
self.m = m # 模数
def generate(self):
self.seed = (self.a * self.seed + self.b) % self.m
return self.seed
lcg = LCG(bytes_to_long(flag), getPrime(256), getPrime(256), getPrime(256))
for i in range(getPrime(16)):
lcg.generate()
print(f'a = {lcg.a}')
print(f'b = {lcg.b}')
print(f'm = {lcg.m}')
print(lcg.generate())
'''
a = 113439939100914101419354202285461590291215238896870692949311811932229780896397
b = 72690056717043801599061138120661051737492950240498432137862769084012701248181
m = 72097313349570386649549374079845053721904511050364850556329251464748004927777
9772191239287471628073298955242262680551177666345371468122081567252276480156
'''
有一个问题就是,我们需要反推多少项呢?我们并不知道,因为迭代的次数(getPrime(16))是一个随机数。但是这并不妨碍我们求解flag。因为flag的格式(b’NSSFCT{’)我们已经知道,只需要不断反推,直至找到符合格式的flag为止 。
脚本:
import gmpy2
import libnum
a = 113439939100914101419354202285461590291215238896870692949311811932229780896397
b = 72690056717043801599061138120661051737492950240498432137862769084012701248181
m = 72097313349570386649549374079845053721904511050364850556329251464748004927777
c=9772191239287471628073298955242262680551177666345371468122081567252276480156
# c=(a*c0+b)%m
a_1=gmpy2.invert(a,m)
for i in range(2**16):
c = (c - b) * a_1 % m
#print(c)
flag=libnum.n2s(int(c))
if b'NSSCTF{' in flag:
print(flag)
break
LCG_2:不知道b,要先求出b,之后操作就和LCG_1没什么区别了
from Crypto.Util.number import *
flag = b'NSSCTF{******}'
class LCG:
def __init__(self, seed, a, b, m):
self.seed = seed # 初始种子
self.a = a # 乘数
self.b = b # 增量
self.m = m # 模数
def generate(self):
self.seed = (self.a * self.seed + self.b) % self.m
return self.seed
lcg = LCG(bytes_to_long(flag), getPrime(256), getPrime(256), getPrime(256))
for i in range(getPrime(16)):
lcg.generate()
print(f'a = {lcg.a}')
print(f'm = {lcg.m}')
print(lcg.generate())
print(lcg.generate())
'''
a = 83968440254358975953360088805517488739689448515913931281582194839594954862517
m = 77161425490597512806099499399561161959645895427463118872087051902811605680317
43959768681328408257423567932475057408934775157371406900460140947365416240650
8052043336238864355872102889254781281466728072798160448260752595038552944808
'''
脚本:
import gmpy2
import libnum
from Crypto.Util.number import isPrime
a = 83968440254358975953360088805517488739689448515913931281582194839594954862517
m = 77161425490597512806099499399561161959645895427463118872087051902811605680317
c1=43959768681328408257423567932475057408934775157371406900460140947365416240650
c2=8052043336238864355872102889254781281466728072798160448260752595038552944808
b=(c2-a*c1) % m
#print(b)
#print(gmpy2.gcd(b,m))
a_1 = gmpy2.invert(a,m)
c = c1
for i in range(2**16):
c = (c-b) * a_1 % m
flag = libnum.n2s(int(c))
if b'NSSCTF' in flag:
print(flag)
break
LCG_3:a、b都不知道,先求出a,之后操作同LCG_2
from Crypto.Util.number import *
flag = b'NSSCTF{******}'
class LCG:
def __init__(self, seed, a, b, m):
self.seed = seed # 初始种子
self.a = a # 乘数
self.b = b # 增量
self.m = m # 模数
def generate(self):
self.seed = (self.a * self.seed + self.b) % self.m
return self.seed
lcg = LCG(bytes_to_long(flag), getPrime(256), getPrime(256), getPrime(256))
for i in range(getPrime(16)):
lcg.generate()
print(f'm = {lcg.m}')
print(lcg.generate())
print(lcg.generate())
print(lcg.generate())
'''
m = 96343920769213509183566159649645883498232615147408833719260458991750774595569
10252710164251491500439276567353270040858009893278574805365710282130751735178
45921408119394697679791444870712342819994277665465694974769614615154688489325
27580830484789044454303424960338587428190874764114011948712258959481449527087
'''
脚本
import gmpy2
import libnum
m = 96343920769213509183566159649645883498232615147408833719260458991750774595569
c1 = 10252710164251491500439276567353270040858009893278574805365710282130751735178
c2 = 45921408119394697679791444870712342819994277665465694974769614615154688489325
c3 = 27580830484789044454303424960338587428190874764114011948712258959481449527087
a = (c3-c2) * gmpy2.invert(c2-c1,m) % m
# print(gmpy2.gcd(a,m))
a_1 = gmpy2.invert(a,m)
b = (c2-a*c1) % m
# print(gmpy2.gcd(b,m))
c = c1
for i in range(2**16):
c = (c-b) * a_1 % m
flag = libnum.n2s(int(c))
if b'NSSCTF{' in flag:
print(flag)
break
LCG_4:a、b、m都不知道,给出多组输出,让我们恢复初始种子。
from Crypto.Util.number import *
flag = b'NSSCTF{******}'
class LCG:
def __init__(self, seed, a, b, m):
self.seed = seed # 初始种子
self.a = a # 乘数
self.b = b # 增量
self.m = m # 模数
def generate(self):
self.seed = (self.a * self.seed + self.b) % self.m
return self.seed
lcg = LCG(bytes_to_long(flag), getPrime(256), getPrime(256), getPrime(256))
for i in range(getPrime(16)):
lcg.generate()
print(lcg.generate())
print(lcg.generate())
print(lcg.generate())
print(lcg.generate())
print(lcg.generate())
'''
47513456973995038401745402734715062697203139056061145149400619356555247755807
57250853157569177664354712595458385047274531304709190064872568447414717938749
30083421760501477670128918578491346192479634327952674530130693136467154794135
38739029019071698539301566649413274114468266283936163804522278316663267625091
42506270962409723585330663340839465445484970240895653869393419413017237427900
'''
首先,我们要先求出m,才能LCG_3的操作。
脚本:
import gmpy2
import libnum
from Crypto.Util.number import GCD, isPrime, long_to_bytes
c=[47513456973995038401745402734715062697203139056061145149400619356555247755807,
57250853157569177664354712595458385047274531304709190064872568447414717938749,
30083421760501477670128918578491346192479634327952674530130693136467154794135,
38739029019071698539301566649413274114468266283936163804522278316663267625091,
42506270962409723585330663340839465445484970240895653869393419413017237427900]
t=[]
for i in range(1,len(c)):
t.append(c[i]-c[i-1])
m = 0
for i in range(1,len(t)-1):
m = GCD(t[i+1]*t[i-1]-t[i]**2, m)
# print(isPrime(m)) False
m//=2
# print(isPrime(m))
a = (c[3]-c[2])*gmpy2.invert(c[2]-c[1],m) % m
b = (c[2]-a*c[1]) % m
# print(gmpy2.gcd(a,m))
# print(gmpy2.gcd(b,m))
a_1=gmpy2.invert(a,m)
for i in range(2**16):
c[1] = (c[1]-b) * a_1 % m
flag = long_to_bytes(c[1])
if b'NSSCTF{' in flag:
print(flag)
break
这里需要解释一下代码中为什么要进行 m//=2 这样的操作?
我们虽然得到了m的倍数,通过求解 GCD 也确实能得到 m。但是在数据不够多的情况下,我们可能得到的是 km (不信的话,你可以输出一下 isPrime(m) 发现 m 确实不是素数) ,这时就需要我们遍历一些小数,手动去除 k 。*
LCG_5:本题给出信息和LCG_4一样需要我们恢复参数。
from Crypto.Util.number import *
flag = b'NSSCTF{******}'
class LCG:
def __init__(self, seed, a, b, m):
self.seed = seed # 初始种子
self.a = a # 乘数
self.b = b # 增量
self.m = m # 模数
def generate(self):
self.seed = self.a * (self.seed - self.b) % self.m
return self.seed
lcg = LCG(bytes_to_long(flag), getPrime(256), getPrime(256), getPrime(256))
for i in range(getPrime(16)):
lcg.generate()
print(lcg.generate())
print(lcg.generate())
print(lcg.generate())
print(lcg.generate())
print(lcg.generate())
'''
57648351648792284446777383544515312078150027665462203747924668509833442797796
90378879763416486117626477831653213918315023665514305359005153448529276829825
21826576702665114807208181233864324586557058567478767825970403161758214940301
47594460970742467761038407996122637655856234121180714918606854365482948918701
11871076497267630136796123094001159466754095580273018347962555675375123133730
'''
需要注意的是,这里所给的LCG递归式是
self.seed = self.a * (self.seed - self.b) % self.m
我们只需要把 -ab 看成一个整体,这样我们就可以转化为标准式 ax+b 的形式。因为这里我们只需要恢复初始种子 m,所以代码和LCG_4没什么区别。
脚本:
import gmpy2
from Crypto.Util.number import GCD, isPrime, long_to_bytes
c=[57648351648792284446777383544515312078150027665462203747924668509833442797796,
90378879763416486117626477831653213918315023665514305359005153448529276829825,
21826576702665114807208181233864324586557058567478767825970403161758214940301,
47594460970742467761038407996122637655856234121180714918606854365482948918701,
11871076497267630136796123094001159466754095580273018347962555675375123133730]
t=[]
for i in range(1,len(c)):
t.append(c[i]-c[i-1])
m = 0
for i in range(1,len(t)-1):
m = GCD(t[i+1]*t[i-1]-t[i]**2, m)
print(isPrime(m)) # False m的倍数
print(m)
for i in range(1,100):
if isPrime(m//i):
print(i) # i是4
m//=i
break
a = (c[3]-c[2])*gmpy2.invert(c[2]-c[1],m) % m
b = (c[2]-a*c[1]) % m
# print(gmpy2.gcd(a,m))
# print(gmpy2.gcd(b,m))
a_1=gmpy2.invert(a,m)
for i in range(2**16):
c[1] = (c[1]-b) * a_1 % m
flag = long_to_bytes(c[1])
if b'NSSCTF{' in flag:
print(flag)
break
LCG_6:同样需要恢复参数
from Crypto.Util.number import *
flag = b'NSSCTF{******}'
class LCG:
def __init__(self, seed, a, b, m):
self.seed = seed # 初始种子
self.a = a # 乘数
self.b = b # 增量
self.m = m # 模数
def generate(self):
self.seed = (self.a * self.seed + self.b) % self.m
self.seed = (self.a * self.seed + self.b) % self.m
return self.seed
lcg = LCG(bytes_to_long(flag), getPrime(255), getPrime(255), getPrime(256))
for i in range(getPrime(16)):
lcg.generate()
print(lcg.generate())
print(lcg.generate())
print(lcg.generate())
print(lcg.generate())
print(lcg.generate())
这里进行了两次加密,我们得到的并不是连续的输出,而是隔位输出,比如是 X2,X4,X6,X8,X10
self.seed = (self.a * self.seed + self.b) % self.m
self.seed = (self.a * self.seed + self.b) % self.m
首先要恢复模数 m,
{
X
2
=
(
a
∗
X
1
+
b
)
m
o
d
m
X
4
=
(
a
X
3
+
b
)
m
o
d
m
⇒
(
X
4
−
X
2
)
=
a
(
X
3
−
X
1
)
m
o
d
m
\left\{\begin{matrix} X_{2} &= &(a*X_{1}+b) &mod &m \\ X_{4}& = & (aX_{3}+b) &mod &m \end{matrix}\right.\Rightarrow (X_{4}-X_{2})=a(X_{3}-X_{1})\textbf{ }mod\textbf{ }m
{X2X4==(a∗X1+b)(aX3+b)modmodmm⇒(X4−X2)=a(X3−X1) mod m
依然可以构造消去增量 b 构造等比数列,所以我们依然可以用之前的方法求 m 。
接下来就是求 a,
(
X
4
−
X
2
)
=
a
(
X
3
−
X
1
)
m
o
d
m
=
a
(
(
a
X
2
+
b
)
−
(
a
X
0
+
b
)
)
m
o
d
m
=
a
2
(
X
2
−
X
0
)
m
o
d
m
(X_{4}-X_{2})=a(X_{3}-X_{1})\textbf{ }mod\textbf{ }m=a((aX_{2}+b)-(aX_{0}+b))\textbf{ }mod\textbf{ }m=a^{2}(X_{2}-X_{0})\textbf{ }mod\textbf{ }m
(X4−X2)=a(X3−X1) mod m=a((aX2+b)−(aX0+b)) mod m=a2(X2−X0) mod m
那么,这个平方我们该如何处理呢?我们看Xenny师傅 怎么解决。
这个方法我也还是一知半解(AMM算法也不是很明白)。这里,讲一下我的做法。前面已经说过,本题进行了两轮加密,所给输出是间隔的。
X
n
+
1
=
(
a
X
n
+
b
)
m
o
d
m
=
(
a
(
a
X
n
−
1
+
b
)
+
b
)
m
o
d
m
X_{n+1}=(aX_{n}+b)\textbf{ }mod\textbf{ }m=(a(aX_{n-1}+b)+b)\textbf{ }mod\textbf{ }m
Xn+1=(aXn+b) mod m=(a(aXn−1+b)+b) mod m
可以把 a**2 看作 a,把 (a+1)b 看成 b,这样就相当于重新构造了一个LCG。
脚本:
import gmpy2
from Crypto.Util.number import GCD, isPrime, long_to_bytes
c = [25445927935559969212648839062255651208014967526951331344342413906051118248013,
81572970970116732975667604095930675262596098540738447440566868976253289440293,
6956793925625110803779114150160476498676179542815207353218944386232051429289,
88042506866508011592456777776490262927213783361334741921985316105965255450508,
5652832125321707726481846809536180176877263519327268361130605456255558285092]
t=[]
for i in range(1,len(c)):
t.append(c[i]-c[i-1])
m = 0
for i in range(1,len(t)-1):
m = GCD(t[i+1]*t[i-1]-t[i]**2, m)
# print(isPrime(m)) # true
a = (c[3]-c[2])*gmpy2.invert(c[2]-c[1],m) % m
b = (c[2]-a*c[1]) % m # 把(a+1)*b当成b就可以了
# print(gmpy2.gcd(a,m))
# print(gmpy2.gcd(b,m))
a_1=gmpy2.invert(a,m)
for i in range(2**16):
c[1] = (c[1]-b) * a_1 % m
flag = long_to_bytes(c[1])
if b'NSSCTF{' in flag:
print(flag)
break
最后附上我参考的文章: