源于某一道赛题,明年可以作为招新题出
随机数
随机数分为真随机数和伪随机数,我们程序使用的基本都是伪随机数,其中伪随机又分为强伪随机数和弱伪随机数。
真随机数,通过物理实验得出,比如掷钱币、骰子、转轮、使用电子元件的噪音、核裂变等。需要满足随机性、不可预测性、不可重现性。
伪随机数,通过一定算法和种子得出。软件实现的是伪随机数。
强伪随机数,难以预测的随机数。需要满足随机性和不可预测性。
弱伪随机数,易于预测的随机数。需要满足随机性。
**只要这个随机数是由确定算法生成的,那就是伪随机。**只能通过不断算法优化,使你的随机数更接近随机。
有限状态机不能产生真正的随机数的,所以,现代计算机中,无法通过一个纯算法来生成真正的随机数,无论是哪种语言,单纯的算法生成的数字都是伪随机数,都是由可确定的函数通过一个种子,产生的伪随机数。
这也就意味着,如果知道了种子,就可以推断接下来的随机数序列的信息。这就有了可预测性。
常见种类及攻击方法:
线性同余生成器,LCG
特征:$x_{n+1}\equiv(ax_n+c) \pmod n$
经典(去年招新题):
class 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类:
protected 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)=a*t_0&\pmod n\\ t_2 & = s_3-s_2=a*(s_2-s_1)=a*t_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_0*a+b \pmod n \\ s_2\equiv s_1*a+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_0*a+b \pmod n \Longrightarrow s_1-s_0*a\equiv b \pmod n $然后b就搞出来了,一个一个地推就好啦
python&c++:梅森旋转算法
通常称为mt19937算法,MT 是 Mersenne Twister 的缩写;而 19937 则取自算法中用到的梅森素数$2^{19937}-1$,梅森素数是算法生成伪随机数的循环长度(period),而旋转则说的是算法内部对定长二进制串循环位移的过程。
特征:比特位移动后进行&运算
unsigned 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; }或者对其进行魔改(今年面试题&最近某比赛赛题):
class 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的(左移右移是一样的):
y0 = y ^ (y >> 11)假设y是32位的,那这次运算后我们就失去了后21位的信息,但前11位留下了,把这11位与y0的12-23位做异或,就得到了y的12-23位,以此类推可以11位11位地推出信息,如此这个过程就逆完了
再看带mask的:
y = y ^ ((y >> 7) & 2636928640)其实和上面是一样的,就是加了一个mask吓人
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 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)$其实和梅森回旋是一个东西
这是一个更复杂的样例,用三维的矩阵去进行迭代,同时生成三个随机数:
uint32_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西湖论剑
class 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,其脆弱性来自于异或的可逆