线性同余生成器(LCG)学习记录

admin 2024年10月16日21:23:06评论122 views字数 4758阅读15分51秒阅读模式

线性同余生成器(LCG)学习记录

前言

foreword

流密码(Stream Cipher)是对称算法的一种,近年来已成为Crypto方向的常见考点。常见的题型有LCG(线性同余发生器),LFSR(线性反馈移位寄存器),以及基于非线性数组变换的流密码算法CR4。对这方面的了解很少,打算利用暑假的时间来学习一下。看了一些大佬的博客后,对流密码有了基础的认识。接下来我将简单介绍一下LCG,若有不到位的地方敬请见谅。

简介

brief

线性同余发生器(Linear congruential generator),简称LCG,是一种能产生具有不连续计算的伪随机序列的分段线性方程的算法,它代表了最古老和最知名的伪随机序列生成器算法之一,其理论相对容易理解,并且易于实现和快速,特别是在可以通过存储位截断提供模运算的计算机硬件上。在理论下,使用计算机软件生成的数列,在某个时刻一定会出现重复,这个时间或长或短,由于计算机的内存是有限,这个时刻肯定会出现。所以软件生成的数列,肯定是具有周期性(会重复出现),所以不具备该性质。由硬件生成的数列,一般则具有该性质。由硬件生成的随机数列,根据传感器收集的热量、声音的变化等事实上无法预测的和重现自然现象信息来生成的,像这样的硬件设备,称为随机数生成器。而LCG就是一个伪随机数生成器。之所以称为伪随机数生成器,是由于它是由软件生成的。生成器由循环关系定义:
其中a为乘数,b为增量,m为模数,均为设定的常数,同时需要初始值X0为种子。接下来对公式进行解读:结合图和表达式可知,就是把种子传入发生器中,乘上a再加上b然后对m取模得到第一个伪随机数,然后返回到发生器中作为seed,在进行上一步的操作得到第二个,然后再传入发生器中。举个例子:假设 a=3 b=0,m=7,seed=6
同时也可以发现,伪随机数是对m取模后的值,其范围为 0~m-1,也就是其周期。同时也证明了其不具备不可预测性。所以线性同余法得到的是弱伪随机数。基于原始公式我们可以变形出以下的攻击手段:
_

目的

公式

No.1

Xn+1反推出Xn 

Xn=(a-1 (Xn+1 - b))%m

No.2

求a

a=((Xn+2-Xn+1)(Xn+1-Xn)-1)%m

No.3

求b

b=(Xn+1 - aXn)%m

No.4

求m

tn=Xn+1-Xn,m=gcd((tn+1tn-1 - tntn) , (tntn-2 - tn-1tn-1))

具体公式推导见以下链接:

https://blog.csdn.net/superprintf/article/details/108964563

例题

from Crypto.Util.number import *f = open('flag.txt', 'r')flag = f.read()f.close()assert flag[:8] == "Dest0g3{"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.mreturn self.seed >> 16def output(self):print("a = {}nb = {}nm = {}".format(self.a, self.b, self.m))print("state1 = {}".format(self.next()))print("state2 = {}".format(self.next()))lcg = LCG()lcg.output()c = b''.join([long_to_bytes(ord(flag[i]) ^ (lcg.next() % 10))for i in range(len(flag))])print(bytes_to_long(c))'''a = 3939333498b = 3662432446m = 2271373817state1 = 17362state2 = 20624600017039001091357643174067454938198067935635401496485588306838343558125283178792619821966678282131419050878'''

分析:

from Crypto.Util.number import *f = open('flag.txt', 'r')flag = f.read()f.close()assert flag[:8] == "Dest0g3{"#读取名为 'flag.txt' 的文件,并确保文件内容的前8个字符是 "Dest0g3{"class LCG:def __init__(self):self.a = getRandomNBitInteger(32)self.b = getRandomNBitInteger(32)self.m = getPrime(32)self.seed = getRandomNBitInteger(32)#定义了一个名为LCG的类,通过调用 getRandomNBitInteger(32) 方法生成一个32位的随机整数,赋值为lcg的基本模数a,b,m,seeddef next(self):self.seed = (self.a * self.seed + self.b) % self.mreturn self.seed >> 16#根据线性同余生成器的公式计算下一个伪随机数,并返回该伪随机数的右移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()))#定义了output()方法,打印a,b,m的值,使用 format() 方法将这些值插入到相应的字符串中。调用生成器的 next() 方法,计算并打印生成器的下一个状态值 state1。使用 format() 方法将该值插入到相应的字符串中。再次调用生成器的 next() 方法,计算并打印生成器的下一个状态值 state2。同样使用format() 方法将该值插入到相应的字符串中lcg = LCG()#创建一个 LCG 类的实例对象 lcglcg.output()#调用 output() 方法输出生成器的参数值和两个连续状态值c = b''.join([long_to_bytes(ord(flag[i]) ^ (lcg.next() % 10))for i in range(len(flag))])#使用列表推导式和位运算符计算变量 c 的值:遍历字符串 flag 的每个字符,将其转换为 ASCII 码,并与生成器的下一个状态值进行位异或运算。将位异或结果通过取余运算限制在 0-9 的范围内。将每个限制后的结果转换为字节类型,并将它们合并为一个字节序print(bytes_to_long(c))#使用 bytes_to_long() 函数将字节序列 c 转换为一个长整数

可以总结为四个步骤:

 1. LCG对象的初始化:LCG类的构造函数使用了随机生成的参数a、b、m和seed来初始化LCG对象的属性。这些参数决定了随机数生成器的行为。 

2. 生成随机数:LCG类的next()方法使用线性同余公式来生成下一个随机数。每次调用next()方法都会更新seed的值,并返回右移16位后的结果。

3. 对flag进行加密:代码使用LCG对象生成一个长度与flag相同的随机数序列,然后将每个flag字符与(lcg.next() % 10)进行异或操作,并将结果转换为字节类型。 

4. 将加密后的结果转换为整数:使用 bytes_to_long() 函数将加密后的字节序列转换为一个大整数。 

综上所述,解出flag的关键是逆向计算LCG生成器的参数和状态,以及逆向计算加密过程中的异或操作和取模运算。通过得到正确的参数和状态,可以重现加密过程并还原出原始的flag。。所以我们可用一个循环来尝试不同的迭代次数(0到65535),以生成可能的种子值。在每次迭代中,代码首先将一个固定的十进制数17362(即state1)转换为二进制形式,并将其赋值给 bin1 。然后,将迭代的当前值转换为二进制形式,并通过 ljust 函数将其填充至16位,不足16位的部分用0补齐,并将结果赋值给 bin2 。接下来,代码将 bin1 和 bin2 拼接起来,组成一个新的二进制字符串,并通过 int() 函数将其转换为一个整数形式的种子值( seed )。然后,代码使用相同的线性同余公式 seed2 = (a * seed + b) % m

代码如下:

for i in range(65536):bin1 = bin(17362)[2:]bin2 = bin(i)[2:].ljust(16, '0')seed = int(bin1 + bin2, 2)seed2 = (a * seed + b) % mif seed2 >> 16 == 20624:init_seed = seedprint(seed)
然后,我们再通过使用构造函数和 __init__ 方法,接收四个参数:a、b、m和init_seed。接着可以通过逆过程调用 next 和 output 方法,打印出 LCG 对象的属性值和生成的伪随机数。对于给出的大数c,我们也要进行处理,将 c 转换为字节数组 c_string ,然后,通过循环遍历 c_string 中的每个字节 a ,并对其进行异或操作。异或操作的另一个操作数是通过调用 lcg.next() 方法生成的伪随机数取模 10 的结果。异或运算的结果 m_char 被转换为字符,并拼接到字符串 m 中,字符串m即为我们所求的flag
完整wp如下:
from Crypto.Util.number import *a = 3939333498b = 3662432446m = 2271373817init_seed = 0for i in range(65536):bin1 = bin(17362)[2:]bin2 = bin(i)[2:].ljust(16, '0')seed = int(bin1 + bin2, 2)seed2 = (a * seed + b) % mif seed2 >> 16 == 20624:init_seed = seedprint(seed)# 1137870862class LCG:def __init__(self):self.a = aself.b = bself.m = mself.seed = init_seeddef next(self):self.seed = (self.a * self.seed + self.b) % self.mreturn self.seed >> 16def output(self):print("a = {}nb = {}nm = {}".format(self.a, self.b, self.m))print("state = {}".format(self.next()))lcg = LCG()lcg.output()c =600017039001091357643174067454938198067935635401496485588306838343558125283178792619821966678282131419050878c_string=long_to_bytes(m)m = ''for a in c_string:m_char = a^(lcg.next()%10)m += chr(m_char)print(m)

END

原文始发于微信公众号(火炬木攻防实验室):线性同余生成器(LCG)学习记录

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月16日21:23:06
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   线性同余生成器(LCG)学习记录https://cn-sec.com/archives/1890340.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息