random模块内置伪随机数生成—MT19937算法

random模块内置伪随机数生成—MT19937算法

所谓MT19937算法也就是梅森旋转算法中的一种,是伪随机数发生算法

有关梅森旋转算法可以看:梅森旋转算法 - 维基百科,自由的百科全书 (wikipedia.org)

S i m p l y   a n a l y z e   M T 19937 Simply~analyze~MT19937 Simply analyze MT19937

Python应用MT19937算法生成范围在 [ 0 , 2 32 − 1 ] [0,2^{32}-1] [0,2321]​的均匀分布的32位整数(来自wiki)

def _int32(x):
    return int(0xFFFFFFFF & x)

class MT19937:
    def __init__(self, seed):
        self.mt = [0] * 624
        self.mt[0] = seed
        self.mti = 0
        for i in range(1, 624):
            self.mt[i] = _int32(1812433253 * (self.mt[i - 1] ^ self.mt[i - 1] >> 30) + i)


    def extract_number(self):
        if self.mti == 0:
            self.twist()
        y = self.mt[self.mti]
        y = y ^ y >> 11
        y = y ^ y << 7 & 2636928640
        y = y ^ y << 15 & 4022730752
        y = y ^ y >> 18
        self.mti = (self.mti + 1) % 624
        return _int32(y)


    def twist(self):
        for i in range(0, 624):
            y = _int32((self.mt[i] & 0x80000000) + (self.mt[(i + 1) % 624] & 0x7fffffff))
            self.mt[i] = (y >> 1) ^ self.mt[(i + 397) % 624]

            if y % 2 != 0:
                self.mt[i] = self.mt[i] ^ 0x9908b0df

可以看到代码分为三段

  • 导入seed,初始化伪随机数发生器

可以看到每次生成mt[i]是由前一项进行运算得到的,而第一项也就是mt[0] == seed

总的生成了有 624 624 624​​项整数的列表(以及其他参数)作为state

state是伪随机数发生器的重要变量,它决定了输出怎样的随机数,也就是说知道了state你可以预测出它将要输出的数

Python中的random模块就是内置的MT19937算法

举个例子验证state在伪随机数发生器中的重要性

import random
random_object = random.Random() # 生成伪随机数对象,实际上不必手动调用此函数,在直接调用random的其他生成随机数的函数时,会自动调用此函数生成对象
ori_state = random_object.getstate() # 得到该伪随机数发生器的state,也就是状态量
# 输出随机数
random_object.getrandbits(32) # 4221216727
random_object.getrandbits(32) # 1519334418
random_object2 = random.Random()
random_object2.setstate(ori_state)
# 验证随机数是否相等
random_object2.getrandbits(32) # 4221216727
random_object2.getrandbits(32) # 1519334418

观察state的组成

print(ori_state)
# (3, (2147483648,...,624), None)

中间的元组类型有 625 625 625​项,前 624 624 624​项即是之前提到的整数,而第 625 625 625​项(此时等于 624 624 624)是该state此时对应的第几个整数,也就是说,再下一次执行extract_number()函数时的state里面应该调用第几个整数,换句话说,mti = 624

其他参数似乎是默认的,在之后我们求出 624 624 624​个整数之后,需要按这个格式进行构造真正的state;为了方便叙述,之后的state都暂时指代这 624 624 624个整数

  • 进行twist()函数

注意是在extract_number()函数内可能会执行twist(),进行twist()函数需要满足的条件,也就是每 624 624 624项作为一轮直接执行extract_number()函数里后面的位运算以及抽取随机数的操作,而下一轮 624 624 624项需要进行twist()函数替换state

这样一来,就意味着每 624 624 624项的列表作为一次state,当输出完 624 624 624​个伪随机数之后,需要通过twist()函数变换state,变换完成之后再进行输出伪随机数,以此类推

设新生成的state列表为mt',而原来的state列表为mt(注意在MT19937原代码中并没有赋值新的列表,只是不断地更新原来地列表;这里这样讲只是为了方便理解)

在twist()函数中,每生成的新的一个state列表中的mt'[i],只和在原来的state中的mt[i+1]以及mt[i+397]有关(具体的相关计算在逆转部分分析)

而当i+1或者i+397大于 624 624 624时,也就是超过了一个state列表总长度时,那么mt[i+1]或者mt[i+397]实际上是mt'[(i+1) % 624]或者mt'[(i+397) % 624]

  • 继续extract_number()函数

在判断执行twist()函数与否之后,从目前的state列表中单独抽取每个数进行运算,运算结果即是输出的随机数。也就是说,state列表里的每个数都单独的进行与常量的位运算。

R e v e r s e   M T 19937 Reverse~MT19937 Reverse MT19937

分别按对应的函数进行逆转( r e v e r s e reverse reverse​​部分涉及的思路以及脚本均来自badmonkey)

关于脚本中每个函数的具体分析请见原文:浅析MT19937伪随机数生成算法 - 安全客,安全资讯平台 (anquanke.com)

R e v e r s e   e x t r a c t _ n u m b e r ( ) Reverse~extract\_number() Reverse extract_number()

假设已知624个从开始连续输出的随机数,需要预测之后每一个输出的随机数;

我们只需要逆转输出的随机数组成state列表,然后设置伪随机数发生器的state即可,然后输出需要预测的随机数

# Author:badmonkey

# right shift inverse
def inverse_right(res, shift, bits=32):
    tmp = res
    for i in range(bits // shift):
        tmp = res ^ tmp >> shift
    return tmp


# right shift with mask inverse
def inverse_right_mask(res, shift, mask, bits=32):
    tmp = res
    for i in range(bits // shift):
        tmp = res ^ tmp >> shift & mask
    return tmp

# left shift inverse
def inverse_left(res, shift, bits=32):
    tmp = res
    for i in range(bits // shift):
        tmp = res ^ tmp << shift
    return tmp


# left shift with mask inverse
def inverse_left_mask(res, shift, mask, bits=32):
    tmp = res
    for i in range(bits // shift):
        tmp = res ^ tmp << shift & mask
    return tmp

def recover(y):
    y = inverse_right(y,18)
    y = inverse_left_mask(y,15,4022730752)
    y = inverse_left_mask(y,7,2636928640)
    y = inverse_right(y,11)
    return y&0xffffffff

执行逆转时直接调用recover()函数即可;

random_number = []
state = [recover(i) for i in random_number]

R e v e r s e   t w i s t Reverse~twist Reverse twist

C a s e   1 Case~1 Case 1

假设已知该state中的 624 624 624​个整数,想要知道之前输出的随机数(而不是之后输出的随机数),那么我们需要逆转twist()得到上一个state或者更之前的state

def backtrace(cur):
    high = 0x80000000
    low = 0x7fffffff
    mask = 0x9908b0df
    state = cur
    for i in range(623,-1,-1):
        tmp = state[i]^state[(i+397)%624]
        # recover Y,tmp = Y
        if tmp & high == high:
            tmp ^= mask
            tmp <<= 1
            tmp |= 1
        else:
            tmp <<=1
        # recover highest bit
        res = tmp&high
        # recover other 31 bits,when i =0,it just use the method again it so beautiful!!!!
        tmp = state[i-1]^state[(i+396)%624]
        # recover Y,tmp = Y
        if tmp & high == high:
            tmp ^= mask
            tmp <<= 1
            tmp |= 1
        else:
            tmp <<=1
        res |= (tmp)&low
        state[i] = res    
    return state

使用方法:直接向该函数传入逆转extract_number()得到的state即可得到上一个state,然后再执行extract_number()输出对应的随机数即可

C a s e   2 Case~2 Case 2

Q u e s t i o n : Question: Question:​假设已知 1000 1000 1000​个从开始第 5 5 5​个输出的连续随机数,求前 4 4 4​个输出的随机数的大小(来自V&N 2020题目backtrace)

A n a l y s i s : Analysis: Analysis:​那么这 1000 1000 1000个随机数可以分成两段(对应不同的state生成的随机数),也就是前 620 620 620个数为第一个state生成的,后 380 380 380​​个数为第二个state生成的​;需要求第一个state生成的前四个随机数

这与之前的 C a s e   1 Case~1 Case 1不同,这里不知道一个完整state的 624 624 624个整数

仔细观察twist()函数,与之前提到的一样,第二个state列表中的mt'[i](注意在这里的前提条件下mt'[i] == mt[i+624]),只和在第一个的state中的mt[i+1]以及mt[i+397]有关

又因为此时相当于求在mt[i]中, i i i​等于 0 0 0​到 3 3 3​​​​的情况,那么我们只需要知道在已知 1000 1000 1000个的随机数中第 397 397 397 400 400 400个数以及第 624 624 624 628 628 628​个数,还有第 1 1 1个数;接下来就是相同的逆转,只是由于前提条件不同,部分参数改变了

这里与 C a s e   1 Case~1 Case 1相同,也从大到小进行逆转

def backtrace(cur):
    high = 0x80000000
    low = 0x7fffffff
    mask = 0x9908b0df
    state = cur
    for i in range(3,-1,-1):
        tmp = state[i+624]^state[i+397] # mt'[i] == mt [i+624]
        if tmp & high == high:
            tmp ^= mask
            tmp <<= 1
            tmp |= 1
        else:
            tmp <<=1
        res = tmp&high
        tmp = state[i-1+624]^state[i+396] # 这里也与Case 1不同,但是实际上是一个意思
        if tmp & high == high:
            tmp ^= mask
            tmp <<= 1
            tmp |= 1
        else:
            tmp <<=1
        res |= (tmp)&low
        state[i] = res
    return state

P e f e r e n c e Peference Peference

浅析MT19937伪随机数生成算法 - 安全客,安全资讯平台 (anquanke.com)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

M3ng@L

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值