LCG入门

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=(aXn+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+1b)a1) mod ma1

公式二:求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==(aXn+b)(aXn1)modmodmma=((Xn+1Xn)(XnXn1)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+1aXn) 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+1Xn,tn=(aXn+b)(aXn1+b)=a(XnXn1)=atn1 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+1tn1tntn=aatn1tn1atn1atn1=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+1tn1tntnmTnTn1m

常见六种题型

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==(aX1+b)(aX3+b)modmodmm(X4X2)=a(X3X1) 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 (X4X2)=a(X3X1) mod m=a((aX2+b)(aX0+b)) mod m=a2(X2X0) 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(aXn1+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

最后附上我参考的文章:

ctf之lcg算法

LCG(线性同余生成器)

  • 17
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值