crypto-babyLCG(NPUCTF2020)

近期研究格密码过程中遇到的一个很好的题目,记录一下。
题目源码:

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

class LCG:
    def __init__(self, bit_length):
        m = getPrime(bit_length)
        a = getRandomRange(2, m)
        b = getRandomRange(2, m)
        seed = getRandomRange(2, m)
        self._key = {'a':a, 'b':b, 'm':m}
        self._state = seed
        
    def next(self):
        self._state = (self._key['a'] * self._state + self._key['b']) % self._key['m']
        return self._state
    
    def export_key(self):
        return self._key


def gen_lcg():
    rand_iter = LCG(128)
    key = rand_iter.export_key()
    f = open("key", "w")
    f.write(str(key))
    return rand_iter


def leak_data(rand_iter):
    f = open("old", "w")
    for i in range(20):
        f.write(str(rand_iter.next() >> 64) + "\n")
    return rand_iter


def encrypt(rand_iter):
    f = open("ct", "wb")
    key = rand_iter.next() >> 64
    key = (key << 64) + (rand_iter.next() >> 64)
    key = long_to_bytes(key).ljust(16, b'\x00')
    iv = long_to_bytes(rand_iter.next()).ljust(16, b'\x00')
    cipher = AES.new(key, AES.MODE_CBC, iv)
    pt = flag + (16 - len(flag) % 16) * chr(16 - len(flag) % 16)
    ct = cipher.encrypt(pt.encode())
    f.write(ct)


def main():
    rand_iter = gen_lcg()
    rand_iter = leak_data(rand_iter)
    encrypt(rand_iter)


if __name__ == "__main__":
    main()
    

输出内容:

#key
{'b': 153582801876235638173762045261195852087, 'a': 107763262682494809191803026213015101802, 'm': 226649634126248141841388712969771891297}
#old
7800489346663478448
11267068470666042741
5820429484185778982
6151953690371151688
548598048162918265
1586400863715808041
7464677042285115264
4702115170280353188
5123967912274624410
8517471683845309964
2106353633794059980
11042210261466318284
4280340333946566776
6859855443227901284
3149000387344084971
7055653494757088867
5378774397517873605
8265548624197463024
2898083382910841577
4927088585601943730

#ct十六进制显示
00000000h: E0 E0 70 2C B7 D6 FC 19 AC E7 29 BE FC A4 0A F5 ; ààp,·Öü.¬ç)¾ü¤.õ
00000010h: 01 23 F7 A6 ED E1 F3 AE 4B 3A 2A CD 7B 1D D8 37 ; .#÷¦íáó®K:*Í{.Ø7
00000020h: 14 73 10 58 86 AF D1 57 73 44 1F A0 6C 65 6A D6 ; .s.X.¯ÑWsD..lejÖ

大概分析一下逻辑

  • 首先调用genlcg方法生成了一个LCG加密类,参数m为128bit随机素数,a, b, seed均为随机整数,2<a,b,seed<m,key文件中给出了a, b, m参数值,可以认为是公钥
  • 然后调用leakdata方法,生成20个位数小于128bit随机数,生成算法为 s k + 1 = a s k + b m o d    m s_{k+1}=as_{k}+b \mod m sk+1=ask+bmodmold文件中给出每个随机数低位64bit被隐去
  • 再生成三个关键的LCG随机数,用AES加密flag的变形,key由前两个随机数组成,分别取第一个随机数高位和第二个随机数的低位拼起来,iv由第三个随机数组成

显然如果要解密出flag明文那必须要反推出最初的seed参数,将后面产生的随机数全部复原。

因为前20个随机数的高位是已知的,接下来思路是针对这个式子做变形
s k + 1 = a s k + b m o d    m s_{k+1}=as_{k}+b \mod m sk+1=ask+bmodm
将s拆分为高位h和低位l变为
h k + 1 + l k + 1 = a ( h k + l k ) + b m o d    m h_{k+1}+l_{k+1}=a(h_{k}+l_{k})+b \mod m hk+1+lk+1=a(hk+lk)+bmodm
⇒ l k + 1 = a l k + ( a h k + b − h k + 1 ) m o d    m \Rightarrow l_{k+1}=al_{k}+(ah_{k}+b-h_{k+1}) \mod m lk+1=alk+(ahk+bhk+1)modm
由于h均为已知,那么现在找到低位l递推的关系式,又因为
l 2 = a l 1 + ( a h 1 + b − h 2 ) m o d    m l_{2}=al_{1}+(ah_{1}+b-h_{2}) \mod m l2=al1+(ah1+bh2)modm
l 3 = a l 2 + ( a h 2 + b − h 3 ) m o d    m l_{3}=al_{2}+(ah_{2}+b-h_{3}) \mod m l3=al2+(ah2+bh3)modm
⇒ l 3 = a 2 l 1 + a ( a h 1 + b − h 2 ) + ( a h 2 + b − h 3 ) m o d    m \Rightarrow l_{3}=a^2l_{1}+a(ah_{1}+b-h_{2})+(ah_{2}+b-h_{3}) \mod m l3=a2l1+a(ah1+bh2)+(ah2+bh3)modm
则可以推出所有低位l与l1的关系式,简写为
l k + 1 = A k l 1 + B k m o d    m l_{k+1}=A_{k}l_{1}+B_{k} \mod m lk+1=Akl1+Bkmodm
⇒ l k + 1 = A k l 1 + B k + z k m \Rightarrow l_{k+1}=A_{k}l_{1}+B_{k} + z_km lk+1=Akl1+Bk+zkm

其中A,B均可通过a,b,h推出。现在将以上关系式写成矩阵形式。
构造lattice,最后一项取264的原因是为了保持向量各个数值bit位保持一致(关于格密码基础概念推荐这篇Lattice学习笔记
M = [ m m ⋱ m A 1 A 2 ⋯ A 19 1 B 1 A 3 ⋯ B 19 0 2 64 ] M= \begin{bmatrix} m \\ & m \\ & & \ddots \\ & & & m \\ A_1 & A_2 & \cdots & A_{19} & 1 \\ B_1 & A_3 & \cdots & B_{19} & 0 & 2^{64} \\ \end{bmatrix} M=mA1B1mA2A3mA19B1910264

[ z 1 z 2 ⋯ z 19 l 1 1 ] M = [ l 2 l 3 ⋯ l 20 l 1 2 64 ] \begin{bmatrix} z_1 & z_2 & \cdots & z_{19} & l_1 & 1\end{bmatrix} M =\begin{bmatrix} l_2 & l_3 & \cdots & l_{20} & l_1 & 2^{64}\end{bmatrix} [z1z2z19l11]M=[l2l3l20l1264]
于是对M使用LLL算法求出随机数低位,sage exp:

#sage 9.4
a=107763262682494809191803026213015101802
b=153582801876235638173762045261195852087
m=226649634126248141841388712969771891297
h = [0,7800489346663478448,11267068470666042741,5820429484185778982,6151953690371151688,548598048162918265,1586400863715808041,7464677042285115264,4702115170280353188,5123967912274624410,8517471683845309964,2106353633794059980,11042210261466318284,4280340333946566776,6859855443227901284,3149000387344084971,7055653494757088867,5378774397517873605,8265548624197463024,2898083382910841577,4927088585601943730]
for i in range(len(h)):
    h[i] <<= 64
A = [1]
B = [0]
for i in range(1, len(h)-1):
    A.append(a*A[i-1] % m)
    B.append((a*B[i-1]+a*h[i]+b-h[i+1]) % m)
A = A[1:]
B = B[1:]

M = matrix(ZZ, 21, 21)
for i in range(19):
    M[i, i] = m
    M[19, i] = A[i]
    M[20, i] = B[i]
    M[i, 19] = M[i, 20] = 0
M[19, 19] =  1
M[20, 20] = 2^64
M[19, 20]= 0

ll = M.LLL()[0]
l1 = ll[-2]
h1 = h[1]
s1 = l1+h1
#for s1=a*seed+b%m
seed = ((s1-b)*inverse_mod(a,m))%m
print(seed)

#73991202721494681711496408225724067994

得到seed之后就比较简单了,修改一下加密逻辑及得到解密后的结果:

from Crypto.Util.number import *
from Crypto.Cipher import AES

class LCG:
    def __init__(self, bit_length):
        b = 153582801876235638173762045261195852087
        a = 107763262682494809191803026213015101802
        m = 226649634126248141841388712969771891297
        seed = 73991202721494681711496408225724067994
        self._key = {'a':a, 'b':b, 'm':m}
        self._state = seed

    def next(self):
        self._state = (self._key['a'] * self._state + self._key['b']) % self._key['m']
        return self._state

    def export_key(self):
        return self._key


def gen_lcg():
    rand_iter = LCG(128)
    key = rand_iter.export_key()
    f = open("key", "w")
    f.write(str(key))
    return rand_iter


def leak_data(rand_iter):
    f = open("old", "w")
    for i in range(20):
        f.write(str(rand_iter.next() >> 64) + "n")
    return rand_iter

def decrypt(rand_iter):
    #from ct
    flag=b'\xe0\xe0p,\xb7\xd6\xfc\x19\xac\xe7)\xbe\xfc\xa4\n\xf5\x01#\xf7\xa6\xed\xe1\xf3\xaeK:*\xcd{\x1d\xd87\x14s\x10X\x86\xaf\xd1WsD\x1f\xa0lej\xd6'
    key = rand_iter.next() >> 64
    key = (key << 64) + (rand_iter.next() >> 64)
    key = long_to_bytes(key).ljust(16, b'\x00')
    iv = long_to_bytes(rand_iter.next()).ljust(16, b'\x00')
    cipher = AES.new(key, AES.MODE_CBC, iv)
    ct = cipher.decrypt(flag)
    print(ct)


def main():
    rand_iter = gen_lcg()
    rand_iter = leak_data(rand_iter)
    decrypt(rand_iter)


if __name__ == "__main__":
    main()
  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值