伪随机数生成器
源于某一道赛题,明年可以作为招新题出
随机数
随机数分为真随机数和伪随机数,我们程序使用的基本都是伪随机数,其中伪随机又分为强伪随机数和弱伪随机数。
真随机数,通过物理实验得出,比如掷钱币、骰子、转轮、使用电子元件的噪音、核裂变等。需要满足随机性、不可预测性、不可重现性。
伪随机数,通过一定算法和种子得出。软件实现的是伪随机数。
强伪随机数,难以预测的随机数。需要满足随机性和不可预测性。
弱伪随机数,易于预测的随机数。需要满足随机性。
只要这个随机数是由确定算法生成的,那就是伪随机。只能通过不断算法优化,使你的随机数更接近随机。
有限状态机不能产生真正的随机数的,所以,现代计算机中,无法通过一个纯算法来生成真正的随机数,无论是哪种语言,单纯的算法生成的数字都是伪随机数,都是由可确定的函数通过一个种子,产生的伪随机数。
这也就意味着,如果知道了种子,就可以推断接下来的随机数序列的信息。这就有了可预测性。
常见种类及攻击方法:
线性同余生成器,LCG
特征:$x_{n+1}\equiv(ax_n+c) \pmod n$
经典(去年招新题):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22class PRNG1(object):
def __init__(self, bits):
self.n = random.getrandbits(bits)
self.a = 0
self.b = 0
self.s = 0
while not (self.a < self.n and self.gcd(self.a, self.n) == 1):
self.a = random.getrandbits(bits)
while not (self.b < self.n and self.gcd(self.b, self.n) == 1):
self.b = random.getrandbits(bits)
while not (self.s < self.n and self.gcd(self.s, self.n) == 1):
self.s = random.getrandbits(bits)
#对参数的随机化
def gcd(self, a, b):
while b:
a, b = b, a % b
return a
def next(self):
self.s = (self.a * self.s + self.b) % self.n
#核心:s2 = a*s1 + b
return self.sjava的random类:
1
2
3
4
5
6
7
8
9
10protected int next(int bits) {
long oldseed, nextseed;
AtomicLong seed = this.seed;
do {
oldseed = seed.get();
nextseed = (oldseed * multiplier + addend) & mask;
} while (!seed.compareAndSet(oldseed, nextseed));
return (int)(nextseed >>> (48 - bits));
}安全性非常得差,只要知道连续几个就可以直接推出整个序列,而且序列是循环的
分析和攻击方法:
先求n,只要稍作变形就可
$$
\left
{\begin{aligned}
t_0 & = s_1-s_0&\pmod n\
t_1 & = s_2-s_1=a*(s_1-s_0)=at_0&\pmod n\
t_2 & = s_3-s_2=a(s_2-s_1)=at_1&\pmod n\
t_3 & = s_4-s_3=a(s_3-s_2)=a*t_2&\pmod n\
\end{aligned}}
\right
$$然后进一步转化:
$$
t_1^2-t_0*t_2\equiv0\pmod n
$$然后造出3个n的因数,取gcd后得到的数及其因子都是可能的n(然而很多时候就是n),要拿hint判
接下来我们干a
$$
\left
{\begin{aligned}
s_1\equiv s_0a+b \pmod n \
s_2\equiv s_1a+b \pmod n
\end{aligned}}
\right
\Longrightarrow
a\equiv(s_0-s_1)/(s_1-s_2) \pmod n
$$然后a也有了,搞b
$$
s_1\equiv s_0a+b \pmod n
\Longrightarrow
s_1-s_0a\equiv b \pmod n
$$然后b就搞出来了,一个一个地推就好啦
python&c++:梅森旋转算法
通常称为mt19937算法,MT 是 Mersenne Twister 的缩写;而 19937 则取自算法中用到的梅森素数$2^{19937}-1$,梅森素数是算法生成伪随机数的循环长度(period),而旋转则说的是算法内部对定长二进制串循环位移的过程。
特征:比特位移动后进行&运算
1
2
3
4
5
6
7
8
9
10
11
12
13
14unsigned int Rand()
{
if(!isInit)
srand((int)time(NULL));
if(index == 0)
generate();
unsigned int y = MT[index];
y = y ^ (y >> 11); //y右移11个bit
y = y ^ ((y << 7) & 2636928640); //y左移7个bit与2636928640相与,再与y进行异或
y = y ^ ((y << 15) & 4022730752); //y左移15个bit与4022730752相与,再与y进行异或
y = y ^ (y >> 18); //y右移18个bit再与y进行异或
index = (index + 1) % 624;
return y;
}或者对其进行魔改(今年面试题&最近某比赛赛题):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class func(object):
def __init__(self, keys, num=0x1145141919810aaa):
self.mask = keys #用于&运算的mask
self.seed = num #随机种子,及伪随机数的第一位
self.bits = [random.randint(-32, 32) for i in range(4)] #位移的随机
def shift(self, m, k, c):
if k < 0:
return m ^ m >> (-k) & c
return m ^ m << k & c #梅森旋转的核心步骤:位移+与
def convert(self, m):
for t in range(4):
m = self.shift(m, self.bits[t], self.mask[t])
return m #做四次,其实做多少次都可以
def getrandom(self):
self.seed = self.convert(self.seed)
return self.seed分析和攻击方法:
首先运算的优先级是位移>&>异或
那就先拆开了看,先看不带mask的(左移右移是一样的):
1
y0 = y ^ (y >> 11)
假设y是32位的,那这次运算后我们就失去了后21位的信息,但前11位留下了,把这11位与y0的12-23位做异或,就得到了y的12-23位,以此类推可以11位11位地推出信息,如此这个过程就逆完了
再看带mask的:
1
y = y ^ ((y >> 7) & 2636928640)
其实和上面是一样的,就是加了一个mask吓人
1
2
3
4
5
6
7
8
9
10
11def inverse_left_mask(res, shift, mask, bits=32):
tmp = res
for i in range(bits // shift):
tmp = res ^ tmp << shift & mask
return tmp
def inverse_right_mask(res, shift, mask, bits=32):
tmp = res
for i in range(bits // shift):
tmp = res ^ tmp >> shift & mask
return tmpxorshift RNG
原理是:$\beta$为一个n维的非零二进制向量(b1,b2,…,bn),T为$\beta$的一个线性变换,则整个循环为$\beta$,$\beta T$,$\beta T^2$…
且该伪随机数序列的循环节为$2^n-1$。
特别的,当我们把T矩阵变成$(I+L^n)$,其中L是一个主对角线全1的对角矩阵,这个T在位运算上代表的操作就是y^(y<<n),同理可知右侧
所以最经典的xorshift的变换就是$(I+L^a)(I+R^b)(I+L^c)$其实和梅森回旋是一个东西
这是一个更复杂的样例,用三维的矩阵去进行迭代,同时生成三个随机数:
1
2
3
4
5
6
7
8
9
10
11uint32_t xor128(void) {
static uint32_t x = 123456789;
static uint32_t y = 362436069;
static uint32_t z = 521288629;
static uint32_t w = 88675123;
uint32_t t;
tem = x ^ (x << 11);
x = y; y = z; z = w;
return w = w ^ (w >> 19) ^ (tem ^ (tem >> 8));
}
LFSR,和上面那个好像关系蛮大的
线性反馈移位寄存器:
相关基础概念见ctfwiki
出自2021西湖论剑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class lfsr():
def __init__(self, init, mask, length):
self.init = init
self.mask = mask
self.lengthmask = 2**length - 1
def next(self):
nextdata = (self.init << 1) & self.lengthmask
i = self.init & self.mask & self.lengthmask
output = 0
while i != 0:
output ^= (i & 1)
i = i >> 1
nextdata ^= output
self.init = nextdata
return output这是最常见的lfsr,其脆弱性来自于异或的可逆