攻击线性同余生成器(LCG)

0x00 前言

目前我们在编程中经常会使用随机数,但是其中会不会存在什么问题呢?要知道CPU计算中的各种状态都是确定的,在其中的随机数不是凭空产生的,所以这种随机数真的随机吗?目前生成随机数的方式主要分为以下几种:

  • 硬件随机数生成器
  • 利用现有硬件,从非预期方式产生随机数(比如利用音频的产生、硬盘寻址时间等)
  • 伪随机数
  • 量子技术

PS: RDRAND指令产生的随机数目前存在争议,在此不做详细讨论。有兴趣可以参考 RdRand

虽然选择很多,但是目前还是主要采用伪随机数的方式来应对实际开发中需要的场景。用于产生这些看起来随机但实际是由确定性算法生成数字的机制被称为”伪随机数发生器”,简称为PRNG。

PRNG的中心是确定的,如果攻击者知道其内部的完整状态,则可以对未来的值和过去的值进行预测。如果PRNG被用于加密密钥、生成证书等场景,就会出现安全问题。

接下来我将详细讲解对线性同余发生器的攻击。

0x01 线性同余生成器(LCG)

1.线性同余方法

线性同余方法(LCG)是个产生伪随机数的方法。

它是根据递归公式:

其中A,B,M是产生器设定的常数。

LCG的周期最大为 M,但大部分情况都会少于M。要令LCG达到最大周期,应符合以下条件:

  1. B,M互质;
  2. M的所有质因数都能整除A-1;
  3. 若M是4的倍数,A-1也是;
  4. A,B,N[0]都比M小;
  5. A,B是正整数。

2.Python代码实现

由上面的原理我们可以看到,其中最重要的是定义了三个整数,乘数A、增量B和模数M,因此我们在此用简单的几行Python代码实现一下:

123456789101112131415161718
class prng_lcg:    m = 672257317069504227  # "乘数"    c = 7382843889490547368  # "增量"    n = 9223372036854775783  # "模数"

    def __init__(self, seed):        self.state = seed  # the "seed"

    def next(self):        self.state = (self.state * self.m + self.c) % self.n        return self.state

def test():    gen = prng_lcg(123)  # seed = 123    print gen.next()  # 第一个生成值    print gen.next()  # 第二个生成值    print gen.next()  # 第三个生成值

3.LCG的优缺点

LCG目前是分流行,得益于其在数学表达实现上十分优雅、非常容易理解并且容易设计实现、计算速度可以非常快。但是它也存在一些缺点,比如它在加密安全性方面十分弱。接下来将从以下几种情况对其进行攻击。

0x02 攻击LCG

1. 对于A、B、M以及N0已知的情况

假设我们观察到有一个LCG系统产生了以下三组连续的值,并且我们知道内部的参数如下:

1234
# 三组连续的值s0 = 2300417199649672133s1 = 2071270403368304644s2 = 5907618127072939765
1234
# 内部的参数m = 672257317069504227   # the "multiplier"c = 7382843889490547368  # the "increment"n = 9223372036854775783  # the "modulus"

在已知了这些参数之后我们可以很快的推算出未来的数值或者之前的某个数值,所以还是存在安全问题的。

123456789101112131415161718192021222324
In [1]: m = 672257317069504227

In [2]: c = 7382843889490547368

In [3]: n = 9223372036854775783

In [4]: s0 = 2300417199649672133

In [5]: s1 = (s0*m + c) % n

In [6]: s2 = (s1*m + c) % n

In [7]: s3 = (s2*m + c) % n

In [8]: s4 = (s3*m + c) % n

In [9]: s1Out[9]: 2071270403368304644L

In [10]: s2Out[10]: 5907618127072939765L

In [11]: s3Out[11]: 5457707446309988294L

2.增量未知

我们不清楚增量,但是我们知道以下信息:

123
m = 81853448938945944c = # unknownn = 9223372036854775783
123
# 初值和第一个计算值s0 = 4501678582054734753s1 = 4371244338968431602

我们稍稍改写下公式就可以将目标c计算出来

123
s1 = s0*m + c   (mod n)

c  = s1 - s0*m  (mod n)

此种类型Python攻击代码如下所示:

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

print crack_unknown_increment([4501678582054734753, 4371244338968431602], 9223372036854775783, 81853448938945944)

3.增量和乘数都未知

我们虽然不知道增量和乘数但是我们知道以下数值

1大专栏  攻击线性同余生成器(LCG)ne">23
m = # unknownc = # unknownn = 9223372036854775783
1234
# LCG生成的初值和后面生成的两个值s0 = 6473702802409947663s1 = 6562621845583276653s2 = 4483807506768649573

解决办法很简单,想想怎么解线性方程组就好了

123456
s_1 = s0*m + c  (mod n)s_2 = s1*m + c  (mod n)

s_2 - s_1 = s1*m - s0*m  (mod n)s_2 - s_1 = m*(s1 - s0)  (mod n)m = (s_2 - s_1)/(s_1 - s_0)  (mod n)

此种类型Python攻击代码如下所示:

12345
def crack_unknown_multiplier(states, modulus):    multiplier = (states[2] - states[1]) * modinv(states[1] - states[0], modulus) % modulus    return crack_unknown_increment(states, modulus, multiplier)

print crack_unknown_multiplier([6473702802409947663, 6562621845583276653, 4483807506768649573], 9223372036854775783)

这个算法中应用到了求模,所以我们就需要逆推。详情参考: Recursive algorithm

1234567891011
def egcd(a, b):    if a == 0:        return (b, 0, 1)    else:        g, x, y = egcd(b % a, a)        return (g, y - (b // a) * x, x)

def modinv(b, n):    g, x, _ = egcd(b, n)    if g == 1:        return x % n

4.增量,乘数和模数均未知

现在内部状态基本是都不知道了,但是我们知道初值和随后LCG产生的连续的几个值。

123
m = # unknownc = # unknownn = # unknown
1234567
s0 = 2818206783446335158s1 = 3026581076925130250s2 = 136214319011561377s3 = 359019108775045580s4 = 2386075359657550866s5 = 1705259547463444505s6 = 2102452637059633432

这次用线性方程式不好解决的了,因为对于每一个方程,我们是不知道前一个模数,因此我们将形成的每个方程都会引入新的未知量:

123
s1 = s0*m + c  (mod n)s2 = s1*m + c  (mod n)s3 = s2*m + c  (mod n)
123
s1 - (s0*m + c) = k_1 * ns2 - (s1*m + c) = k_2 * ns3 - (s2*m + c) = k_3 * n

这就相当于六个未知数和三个方程。所以线性方程组是不可能行得通的了,但是数论里面有一条很有用:如果有几个随机数分别乘以n,那么这几个数的欧几里德算法(gcd)就很可能等于n。

1234
In [944]: n = 123456789

In [945]: reduce(gcd, [randint(1, 1000000)*n, randint(1, 1000000)*n, randint(1, 1000000)*n])Out[945]: 123456789

某些取模运算是会等于0的

1
X = 0 (mod n)

然后,根据定义,这相当于:

1
X = k*n

所以这种X != 0但是X = 0 (mod n)的情况就很有趣。我们只需要取几个这样的值进行gcd运算,我们就可以解出n的值。这种是在模数未知的情况下十分常用的方法。

我们在此引入一个序列 – T(n) = S(n+1) - S(n):

1234
t0 = s1 - s0t1 = s2 - s1 = (s1*m + c) - (s0*m + c) = m*(s1 - s0) = m*t0 (mod n)t2 = s3 - s2 = (s2*m + c) - (s1*m + c) = m*(s2 - s1) = m*t1 (mod n)t3 = s4 - s3 = (s3*m + c) - (s2*m + c) = m*(s3 - s2) = m*t2 (mod n)

之后我们就可以得到我们想要的效果了:

1
t2*t0 - t1*t1 = (m*m*t0 * t0) - (m*t0 * m*t0) = 0 (mod n)

然后我们就可以生成几个这样模是0的值,进而利用我们上文讲述的技巧,此种类型Python攻击代码如下所示:

12345678
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)

print crack_unknown_modulus([2818206783446335158, 3026581076925130250,    136214319011561377, 359019108775045580, 2386075359657550866, 1705259547463444505])

0x03 总结

此处我们简述了对LCG的攻击方式,这种方式刚在P.W.N CTF中出现过,具体的题目以及解答可以参考我的下一篇文章–《P.W.N. CTF》中的LCG and the X题目解析。

0x04 参考

Cryptographically secure pseudorandom number generator

Lenstra–Lenstra–Lovász lattice basis reduction algorithm

Cracking RNGs: Linear Congruential Generators

Algorithm Implementation/Mathematics/Extended Euclidean algorithm

线性同余方法

原文地址:https://www.cnblogs.com/wangziqiang123/p/11696986.html

时间: 2024-10-13 23:50:57

攻击线性同余生成器(LCG)的相关文章

年会抽奖程序的一些总结

起源 2019年年会的到来,当然免不了激动人心的抽奖环节啦,那直接延用上一年的抽奖程序吧,然而Boss希望今年的抽奖程序能够能让所有人都参与进来,一起来抢有限奖品,先到先得,而不是站在那里盯着屏幕. OK,程序内容大概是这样子,每个人在手机浏览器打开抽奖程序界面,系统会随机给个数字,谁戳屏幕上的圆形最快最准,就能参与抽奖活动,有多少奖品就有多少场battle(仅限手机浏览器打开 & 每人仅限获得一个奖品) 重要的是,年会之前得把程序公布出来,让其他同事想办法作弊,硬件作弊和软件作弊都允许,我们主

不使用Math.random实现随机数。

var rand = (function(){ var today = new Date(); var seed = today.getTime(); function rnd(){ seed = ( seed * 9301 + 49297 ) % 233280; return seed / ( 233280.0 ); }; return function rand(number){ return Math.ceil(rnd(seed) * number); }; })(); console.l

JS随机数种子

1 试着想一下,如果在某一个场景,我们做一个游戏,用户玩到一半的时候退出了,这样 用户下次进来可以选择继续上一次的进度继续玩,那么现在问题来了:用户玩 的进度以及用户的积分等简单的描述数据,我们都可以记录下来,但是游戏里绘制的障碍物.飞行物以及很多装饰类的小玩意儿,他们甚至是每次用户点开始随机输 出的,要把画布上所有的东西以及它们的大小,位置等都记录下来,实在是没必要. 于是种子随机数就闪亮登场了,我们如果在画布上元素随机绘制的时候,有一个种子值,页面上所有元素的位置.大小等都是根据这个种子来算

进击的雨燕--------------协议

详情转自:http://wiki.jikexueyuan.com/project/swift/chapter2/07_Closures.html 协议定义了一个蓝图,规定了用来实现某一特定工作或者功能所必需的方法和属性.类,结构体或枚举类型都可以遵循协议,并提供具体实现来完成协议定义的方法和功能.任意能够满足协议要求的类型被称为遵循(conform)这个协议. 除了遵循协议的类型必须实现那些指定的规定以外,还可以对协议进行扩展,实现一些特殊的规定或者一些附加的功能,使得遵循的类型能够收益. 协议

Java语言第四讲

1.纯随机数发生器 Xn+1=(aXn + c)mod m Modulus=2^31-1=int.MaxValue Multiplier=7^5=16807 C=0 当显示过2^31-2个数之后,才可能重复. 动手动脑: 编写一个方法,使用以上算法生成指定数目(比如1000个)的随机整数. package 纯随机数; public class Suiji { private static final int N = 200; private static final int LEFT = 40;

Swift2.1 语法指南——协议

原档: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-ID267 参考:http://wiki.jikexueyuan.com/project/swift/chapter2/22_Protocols.html 1.协议 协议定

Scala学习(七)练习

控制结构和函数 1. 编写示例程序,展示为什么 package com.horstmann.impatient 不同于 package com package horstmann package impatient 描述: 区别是后者的上层包也可见,而串写的只有当前包范围可见 程序代码: b.scala package com{   package horstmann{     object A{       def hi=println("I am A")     }     pac

Ch07 包和引入 - 练习

1. 编写示例程序,展示为什么  package com.horstmann.impatient 不同于 package com package horstmann package impatient 回答: 分别使用 package 的效果如下: package com { class T1() {} package hostmann { class T2(t: T1) {} package impatient { class T3(t1: T1, t2: T2) {} } } } 子包里的类

Swift学习笔记-协议(Protocols)

  1.0 翻译:geek5nan 校对:dabing1022 2.0 翻译:futantan 校对:小铁匠Linus 定稿:shanksyang 本页包含内容: 协议的语法(Protocol Syntax) 对属性的规定(Property Requirements) 对方法的规定(Method Requirements) 对Mutating方法的规定(Mutating Method Requirements) 对构造器的规定(Initializer Requirements) 协议类型(Pro