LCG(线性同余生成器)

LCG(线性同余生成器)

伪随机数生成器(pseudo random number generator,PRNG

流密码(stream cipher)

LCG属于PRNG和stream cipher的一种。

Principle

递归公式: S n + 1 ≡ a S n + b (   m o d   m ) S_{n+1} \equiv aS_{n} + b \left(\bmod m\right) Sn+1aSn+b(modm)

g c d ( a , m ) = 1 gcd\left(a, m\right) = 1 gcd(a,m)=1 , 则周期 T = o r d m ( a ) T = ordm\left(a\right) T=ordm(a)

所以选取系数时应尽量使得a为模m的原根,以此尽量延长LCG周期。

整数的阶: a 与 n 互素,使得 a x ≡ 1 (   m o d   n ) a^{x} \equiv 1\left(\bmod n\right) ax1(modn)成立的最小正整数 x 称为a模n的阶,记为 o r d n ( a ) ordn\left(a\right) ordn(a)

原根:当 o r d n ( a ) = ϕ ( n ) ordn\left(a\right) = \phi\left(n\right) ordn(a)=ϕ(n)时,称a为模n的原根

python代码实现:

class LCG:
  a = 672257317069504227  # "乘数"
  b = 7382843889490547368  # "增量"
  m = 9223372036854775783  # "模数"
  def __init__(self, seed):
    self.state = seed  # "种子"
  def next(self):
    self.state = (self.state * self.a + self.b) % self.m
    return self.state
gen = LCG(123)  # seed = 123
print gen.next()  # 第一个生成值
print gen.next()  # 第二个生成值
print gen.next()  # 第三个生成值
# 7060145557346585242
# 3490819368718893392
# 6200546448603839134

Crack methods

unknown b

已知:a,m,s0,s1

因为 s 1 = a ∗ s 0 + b (   m o d   m ) s_{1} = a * s_{0} + b \left(\bmod m\right) s1=as0+b(modm)

所以 b = s 1 − a ∗ s 0 (   m o d   m ) b = s_{1} - a * s_{0} \left(\bmod m\right) b=s1as0(modm)

def crack_unknown_increment(states, modulus, multiplier):
    increment = (states[1] - states[0]*multiplier) % modulus
    return modulus, multiplier, increment

unknown (a, b)

已知:m,s0,s1,s2

因为

s 1 = a ∗ s 0 + b (   m o d   m ) s_{1} = a * s_{0} + b \left(\bmod m\right) s1=as0+b(modm)

s 2 = a ∗ s 1 + b (   m o d   m ) s_{2} = a * s_{1} + b \left(\bmod m\right) s2=as1+b(modm)

所以

s 2 − s 1 = a ∗ ( s 1 − s 0 ) (   m o d   m ) s_{2} - s_{1} = a * \left(s_{1} - s_{0}\right) \left(\bmod m\right) s2s1=a(s1s0)(modm)

a = ( s 2 − s 1 ) / ( s 1 − s 0 ) (   m o d   m ) a = \left(s_{2} - s_{1}\right) / \left(s_{1} - s_{0}\right) \left(\bmod m\right) a=(s2s1)/(s1s0)(modm)

def crack_unknown_multiplier(states, modulus):
    multiplier = (states[2] - states[1]) * invert(states[1] - states[0], modulus) % modulus # 注意这里求逆元
    return crack_unknown_increment(states, modulus, multiplier)

这里贴一下一个大师傅的另一种解法(现在没研究明白,等以后研究格密码再补)

求模等式方程组的通法

基于LLL,对构造的格基进行约化

img

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AeLJXI9P-1616997030766)(imgs/image-20210203152457151.png)]

A = Matrix([
    [s0  ,s1  ,1/m ,0   ,0   ],
    [1   ,1   ,0   ,1/m ,0   ],
    [-s1 ,-s2 ,0   ,0   ,1   ],
    [m   ,0   ,0   ,0   ,0   ],
    [0   ,m   ,0   ,0   ,0   ]
])
A = A.LLL()
a = None
b = None
for l in A:
    if l[0] == 0 and l[1] == 0:
        if l[-1] == 1:
            a, b = l[2] * m, l[3] * m
        elif l[-1] == -1:
            a, b = -l[2] * m, -l[3] * m
        else:
            continue
if not a or not b:
    raise ValueError("[*] No solves")
a %= m
b %= m

构造矩阵进行LLL约化是在解决模等式和CVP等问题时很实用的方法

unknown (a, b, m)

已知:s0-s6

假设我们有一组数据data = {𝑘𝑖𝑁}(N为大素数,𝑘𝑖为𝐹𝑁下的随机数),则reduce(gcd, data)很大概率即为N或N乘上一些很小的因子.(∵ 假设len(data)=n,则在𝑘𝑖视作完全随机的情况下,reduce(gcd, data)有除N外的素因子p的概率约为 1 p n \frac{1}{p^{n}} pn1,∴ 在p或n足够大的时候该方法能有效求解N)

即:如果有几个随机数分别乘以n,那么这几个数的gcd就很可能等于n。

n = 123456789
print reduce(gcd, [randint(1, 1000000)*n, randint(1, 1000000)*n, randint(1, 1000000)*n])
# 123456789

引入序列 T n = S n + 1 − S n T_{n} = S_{n+1} - S_{n} Tn=Sn+1Sn

T n + 1 = S n + 2 − S n + 1 = a ( S n + 1 − S n ) = a T n (   m o d   m ) T_{n+1} = S_{n+2} - S_{n+1} = a\left(S_{n+1} - S_{n}\right) = aT_{n}\left(\bmod m\right) Tn+1=Sn+2Sn+1=a(Sn+1Sn)=aTn(modm)

T n + 2 = S n + 3 − S n + 2 = a ( S n + 2 − S n + 1 ) = a T n + 1 = a 2 T n (   m o d   m ) T_{n+2} = S_{n+3} - S_{n+2} = a\left(S_{n+2} - S_{n+1}\right) = aT_{n+1} = a^{2}T_{n}\left(\bmod m\right) Tn+2=Sn+3Sn+2=a(Sn+2Sn+1)=aTn+1=a2Tn(modm)

∴ T n T n + 2 − ( T n + 1 ) 2 = 0 (   m o d   m ) \therefore T_{n}T_{n+2} - \left(T_{n+1}\right)^{2} = 0\left(\bmod m\right) TnTn+2(Tn+1)2=0(modm)

因此只要我们能拿到连续𝑛(𝑛≥6)组数据,即可对module进行有效攻击. 后续即转为上一个情形的LCG攻击.

也就是说我们可以利用n组s,生成几个mod m为0(m的倍数)的一组data(通过 T n T n + 2 − ( T n + 1 ) 2 T_{n}T_{n+2} - \left(T_{n+1}\right)^{2} TnTn+2(Tn+1)2求得),就可以求得它们的最大公因数(reduce(gcd, data)),也就是模数m,得到了m,就可以进而转化为unknown(a, b)。

def crack_unknown_modulus(states):
    diffs = [s1 - s0 for s0, s1 in zip(states, states[1:])]
    zeroes = [t2*t0 - t1*t1 for t0, t1, t2 in zip(diffs, diffs[1:], diffs[2:])]
    modulus = abs(reduce(gcd, zeroes))
    return crack_unknown_multiplier(states, modulus)

Many Time Pad Attack(多次一密攻击)

https://lazzzaro.github.io/2020/11/07/crypto-%E6%B5%81%E5%AF%86%E7%A0%81/#%E5%A4%9A%E6%AC%A1%E4%B8%80%E5%AF%86%E6%94%BB%E5%87%BB%EF%BC%88Many-Time-Pad-Attack%EF%BC%89

Truncated LCG(given a, b, m)

https://0xdktb.top/2020/03/27/Summary-of-Crypto-in-CTF-PRNG/

examples

[NPUCTF2020]babyLCG

又要跟格结合。。学完格再来补

[NCTF2019]LCG

class LCG(object):
    def __init__(self, seed):
        self.N = getPrime(256)
        self.a = randrange(self.N)
        self.b = randrange(self.N)
        self.seed = seed % self.N
        self.state = self.seed

    def next(self):
        self.state = (self.a * self.state + self.b) % self.N
        return self.state
challenge1
def challenge1():
    print '[++++++++++++++++] Generating challenge 1 [++++++++++++++++]'
    init_seed = getrandbits(256)
    lcg = LCG(init_seed)
    print '[+] init_seed = getrandbits(256)'
    print '[+] lcg = LCG(init_seed)'
    print '[+] lcg.N = ' + str(lcg.N)
    print '[+] lcg.a = ' + str(lcg.a)
    print '[+] lcg.b = ' + str(lcg.b)
    print '[+] lcg.next() = ' + str(lcg.next())
    # print lcg.seed
    guess = int(raw_input("[-] lcg.seed = "))
    if guess != lcg.seed:
        print '[!] Sorry, you are wrong, exit...'
        exit(0)
    print '[++++++++++++++++] Challenge 1 completed [++++++++++++++++]\n\n'
from gmpy2 import *
N = 58752504807475554471851705640525123198743088433770689920509844260675922894303
a = 42193965358850167681766742821143818559444699348244818042292306553454049730799
b = 42429115791925198614889893041072535667400342792227251328952232401985843207872
s1 = 123623408317446005887944641919451936767286763339264317753249629629642353726
print (s1 - b)*invert(a, N)%N
# 3089764217562322037307689879824939943247311529771999590125371401998628371556
challenge2
def challenge2():
    print '[++++++++++++++++] Generating challenge 2 [++++++++++++++++]'
    init_seed = getrandbits(256)
    lcg = LCG(init_seed)
    print '[+] init_seed = getrandbits(256)'
    print '[+] lcg = LCG(init_seed)'
    print '[+] lcg.N = ' + str(lcg.N)
    print '[+] lcg.a = ' + str(lcg.a)
    print '[+] lcg.next() = ' + str(lcg.next())
    print '[+] lcg.next() = ' + str(lcg.next())
    # print lcg.seed
    guess = int(raw_input("[-] lcg.seed = "))
    if guess != lcg.seed:
        print '[!] Sorry, you are wrong, exit...'
        exit(0)
    print '[++++++++++++++++] Challenge 2 completed [++++++++++++++++]\n'
N = 101470213826837298141028377779692401427985568344670829326638899536127536313103
a = 86370044869286000119047733119317698391807712572419053385405009900702446856846
s1 = 27339319018189698721036925245943861167317224772565620839961798799373592916981
s2 = 45263309243420058081365422177433427100509767860087066494963084621382820012390
b = (s2 - s1 * a)%N
print (s1 - b)*invert(a, N)%N
# 33765136920494984413601288497824917256683713134500996060688539794373845183755
challenge3
def challenge3():
    print '[++++++++++++++++] Generating challenge 3 [++++++++++++++++]'
    init_seed = getrandbits(256)
    lcg = LCG(init_seed)
    print '[+] init_seed = getrandbits(256)'
    print '[+] lcg = LCG(init_seed)'
    print '[+] lcg.N = ' + str(lcg.N)
    print '[+] lcg.next() = ' + str(lcg.next())
    print '[+] lcg.next() = ' + str(lcg.next())
    print '[+] lcg.next() = ' + str(lcg.next())
    # print lcg.seed
    guess = int(raw_input("[-] lcg.seed = "))
    if guess != lcg.seed:
        print '[!] Sorry, you are wrong, exit...'
        exit(0)
    print '[++++++++++++++++] Challenge 3 completed [++++++++++++++++]\n'
from gmpy2 import *
N = 86766439951265287525487539006776786932804476484278575487912531572552286991509
s1 = 63587912967838086183322581621576636358608467591605562266383548088573935033444
s2 = 28390902812821650275945306214310346484832726105204083622359403128874833124588
s3 = 30101273684101790863644319735020937206505092134221511353128745353203553781415
a = (s3 - s2) * invert(s2 - s1, N) % N
b = (s2 - s1 * a) % N
s0 = (s1 - b) * invert(a, N) % N
print s0
# 3481108678780346291643455023551848157248371628861397673129497751455844069120
challenge4
def challenge4():
    print '[++++++++++++++++] Generating challenge 4 [++++++++++++++++]'
    init_seed = getrandbits(256)
    lcg = LCG(init_seed)
    print '[+] init_seed = getrandbits(256)'
    print '[+] lcg = LCG(init_seed)'
    for _ in range(6):
        print '[+] lcg.next() = ' + str(lcg.next())
    # print lcg.seed
    guess = int(raw_input("[-] lcg.seed = "))
    if guess != lcg.seed:
        print '[!] Sorry, you are wrong, exit...'
        exit(0)
    print '[++++++++++++++++] challenge 4 completed [++++++++++++++++]\n'
    print '[+] Good job! Here is your flag: ' + flag
from gmpy2 import *
from primefac import *
s = [91998030559539294118763300984087645302586520269401699283197802967845643768875, 80069740776294650362142884296348197761629802041548573589982752426687778180055, 55063243971314211579722221731224042285420160417657223615910086478363530969645, 102301473242334107121311900448732568466210701241317168575997729679274391909345, 50041444132184927123261592271067474333230884356724219423508831265553492817229, 50661436852907184517648938922437143528821344611212575011889385677863365840831]
diffs = [s1 - s0 for s1, s0 in zip(s, s[1:])]
zeros = [t2*t0-t1*t1 for t0, t1, t2 in zip(diffs, diffs[1:], diffs[2:])]
N = abs(reduce(gcd, zeros))
factors = factorint(N)
while not isprime(N): # 注意这里N刚开始有可能不是素数导致后面无法求出逆元
    for prime, order in factors.items():
        if prime.bit_length() > 128:
            continue
        N = N / prime**order
a = (s[2] - s[1]) * invert(s[1] - s[0], N) % N
b = (s[1] - s[0] * a) % N
seed = (s[0] - b) * invert(a, N) % N
print seed

[BJDCTF3rd]easyLCG

from Crypto.Util.number import*
from secret import flag


class LCG:
    def __init__(self):
        self.a = getRandomNBitInteger(32)
        self.b = getRandomNBitInteger(32)
        self.m = getPrime(32)
        self.seed = getRandomNBitInteger(32)

    def next(self):
        self.seed = (self.a*self.seed+self.b) % self.m
        return self.seed >> 16

    def output(self):
        print("a = {}\nb = {}\nm = {}".format(self.a, self.b, self.m))
        print("state1 = {}".format(self.next()))
        print("state2 = {}".format(self.next()))


class DH:
    def __init__(self):
        self.lcg = LCG()
        self.lcg.output()
        self.g = getRandomNBitInteger(128)
        self.m = getPrime(256)
        self.A, self.a = self.gen_AB()
        self.B, self.b = self.gen_AB()
        self.key = pow(self.A, self.b, self.m)

    def gen_AB(self):
        x = ''
        for _ in range(64):
            x += '1' if self.lcg.next() % 2 else '0'
        return pow(self.g, int(x, 2), self.m), int(x, 2)


DH = DH()
flag = bytes_to_long(flag)
print("g = {}\nA = {}\nB = {}\nM = {}".format(DH.g, DH.A, DH.B, DH.m))
print("Cipher = {}".format(flag ^ DH.key))

'''
a = 3844066521
b = 3316005024
m = 2249804527
state1 = 16269
state2 = 4249
g = 183096451267674849541594370111199688704
A = 102248652770540219619953045171664636108622486775480799200725530949685509093530
B = 74913924633988481450801262607456437193056607965094613549273335198280176291445
M = 102752586316294557951738800745394456033378966059875498971396396583576430992701
Cipher = 13040004482819935755130996285494678592830702618071750116744173145400949521388647864913527703
'''

爆破低位的那16位,满足条件的有四个

from libnum import *
a = 3844066521
b = 3316005024
m = 2249804527
state1 = 16269
state2 = 4249
g = 183096451267674849541594370111199688704
A = 102248652770540219619953045171664636108622486775480799200725530949685509093530
B = 74913924633988481450801262607456437193056607965094613549273335198280176291445
M = 102752586316294557951738800745394456033378966059875498971396396583576430992701
Cipher = 13040004482819935755130996285494678592830702618071750116744173145400949521388647864913527703
for i in range(2**16):
    s1 = (state1 << 16) + i
    if ((s1 * a + b) % m) >> 16 == state2:
        s2 = (s1 * a + b) % m
        x = ''
        for _ in range(64):
            s2 = (s2 * a + b) % m
            x += '1' if (s2>>16)% 2 else '0'
        x = int(x, 2)
        key = pow(B, x, M)
        print n2s(Cipher ^ key)
# flag{4dfe14e0c6c21ffcf5a3b4f0ed1911f6}
  • 6
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值