Zzangg  

第9章生成随机性

熵的位数n:该数有2n 个?随机期望?取值,表示某种混乱程度
随机变量X的熵的常用定义如下∶
H(X):=-ΣP((X=x)log2P(X = x)

9.1真实随机

计算机的多种熵源:按键的精确时长、鼠标的精确移动轨迹、硬盘内部湍流引起的硬盘访问时间的随机波动
这些熵源是可疑的:“采集到的”数据并不随机,且易受侧信道攻击
计算机内置真实随机数生成器:仅允许操作系统访问

9.1.1使用真实随机数的问题

随机数不是随时都可以获取的。Web服务器无按键&不能提供大量随机数
真实的随机源有可能失效。在计算机噪声环境容易失效&失效无法预测
从某一具体的物理事件中能够提取多少熵难以判断。

9.1.2伪随机数

通过确定性算法从种子计算种得到
初衷是消除设计上的缺陷

9.1.3真实随机数和伪随机数生成器

真随机数只用于作伪随机数的种子,系统会更安全

真随机数的协议安全性:无条件安全

伪随机数的协议安全性:在攻击者无法攻破生成器的前提下可保证计算上安全

9.2伪随机数生成器的攻击模型

关键问题:如何获取一个随机种子并保证它在实际环境中是保密的

内部状态:生成伪伪随机数后更新状态以保证下次请求不会得到相同随机数

攻击:通过某种方式获取内部状态后,可计算出后面所有的输出和后续所有内部状态

多次使用:相同实例启动的虚拟机使用相同的状态,并从磁盘上读取相同的种子文件

方案:一个真实的随机数生成器来作为熵,在不可预测的实践内提供适量熵

攻击:频繁地提交随机数,因为两次请求间所添加的熵是有限的(环境因素?)

防御方法:使用一个熵池来存放包含熵的传入事件,收集足够多的熵混入内部状态,以保证攻击者无法猜测出熵池中存放的数据,但难以估测熵大小以确定熵池大小

9.3 Fortuna

生成器负责采用一个固定长度的种子,生成任意数量的伪随机数
累加器负责从不同的熵源收集熵再放入熵池中,并间或地给生成器重新设定种子
最后,种子文件管理器负责保证即使在计算机刚刚启动时伪随机数生成器也能生成随机数

9.4生成器

生成器负责将固定长度的内部状态转换为任意长度的输出

基本上可以认为是一个计数器模式的分组密码,CTR模式能够输出一串随机数据流。

不能同时生成太多随机数据:为保证统计随机性

攻击进展的快慢不是由攻击者的计算机决定的,而是由被攻击的计算机的性能决定的

每次请求结束后,分组密码的密钥会被重置,但是计数器不需要重置

9.4.1初始化

密钥和计数器为0表示生成器没获取种子

函数 InitializeGenerator
输出∶G 生成器状态
将密钥K 和计数器 C 设置为 0。
(K,C)⬅ (0,0)
获取状态。
G⬅(K,C)
return G

9.4.2更新种子

为保证输入字符串和现有密钥完全混合使用散列函数

函数 Reseed
输入∶ G 生成器状态,由函数修改
s 新的额外种子
使用散列函数计算新密钥。
K⬅ SHA-256(K||s)
计数器的值加1,但要保证结果不为 0,同时标记生成器已经获取种子在生成器中,C 为 16 字节的整数并且最低有效字节在前。
C⬅ C+1

9.4.3生成块

生成多个随机块,但只能被生成器调用

函数 GenerateBlocks
输入∶ G 生成器状态,由函数修改
k 生成的随机块的数量
输出∶ r 16k字节的伪随机字符串
assert C ≠0
以空字符串开始。
r←ε
将k个块进行链接。
for i =1,…,k do
r← r||E(K,C)
C⬅ C+1
od
return r

9.4.4生成随机数

最大输出长度为220 ,同时确保之前的结果信息被清除

函数 PesudoRandomData
输入∶ G 生成器状态,由函数修改
n 生成的随机数据的字节个数
输出∶ r 长度为n字节的伪随机字符串
限制输出的长度是为了减少随机输出的统计误差,同时也需要保证n非负。
assert 0 ≤n ≤ 20
计算输出。
r ←fist-n-bytes(GenerateBlocks(G,[n/16]))
更新K以避免泄露输出。
K ⬅ GenerateBlocks(G,2)
return r

更新密钥后,r无法被看出来
保密前向:只要没有保留r的副本、没有忘记清除内存单元,生成器就无法泄露r的任何信息

9.4.5生成器速度

依赖于使用的分组密码算法,平均每产生1字节的输出需要花费的时间低于 20 个时钟周期。

9.5累加器

负责从不同的嫡源中获取实随机数,并用来更新生成器的种子。

9.5.1熵源

熵源选择:
    将所有看起来不可预测的数据都用作熵源 如:按键时长和鼠标的移动
    尽可能多地使用实际可行的其他时序上的熵源 如:按键的精确时长、鼠标的移动和点击、硬盘和打印机的响应
熵源都会被嵌入到操作系统的各种硬件驱动器中,用户几乎不可能完成这项工作。
将不同熵源产生的事件链接起来,同时为了保证链接后获得的字符串唯一标识这
些事件,必须确保这个字符串是可解析的。

9.5.2熵池

安全地更新生成器种子:熵池足够大以至攻击者无法穷举出熵池中事件的所有可能值
Fortuna法:设置 32个熵池P0,P1,⋯,P31。理论上,每个熵池都包含一个(无限长的)字符串(用作散列函数输入)P0池中字符串长度足够时,就更新生成器的种子,每个种子都标记了序号1,2、3,⋯,根据种子的序号r,一个或多个池中的字符串会被用来生成种子。生成第r个种子时。如果2i是r的倍数,那么第i个熵池中的字符串就会被使用。因此,每次生成种子都会用到P0,每生成两个种子P1会被用到一次,每生成四个种子P2会被用到一次等。一旦某个熵池
参与生成新的种子,熵池中的内容就会被重置为空字符串。
若生成器的某个状态被泄露了,那么它恢复到安全状态的速度取决于熵(攻击者无法预测的那部分)流入熵池的速度
由于熵池有限,P31可能在两次更新种子之间无法收集到足够的随机性来使生成器恢复到安全状态

9.5.3实现注意事项

基于熵池的事件分发

    基本方法:累加器分发

    风险:攻击者调用累加器,通过额外重复调用影响“真实事件”(熵)

    解决方法:产生事件的同时就确定事件将要放入的熵池号

    累加器可以检查熵源是不是按正确的顺序将事件放入到熵池中。但不能通过验证时应延迟驱动,丢失熵

事件发送的运行时间

    必须将事件数据添加到指定的熵池中时,我们需要计算缓冲区中的部分散列值

    通过使CPU 总是在执行一小段的循环程序并不让它在不同的代码段之间进行切换提高性能

    扩大熵池的缓冲区,使得在计算散列值之前缓冲区中能够收集到更多的数据

    优点:减少CPU计算总时间

    确定:将事件添加到熵池所需时间变长

9.5.4初始化

函数 InitializePRNG
输出∶R 伪随机数生成器状态
将 32 个熵池中的内容都设为空字符串。
for Pi=0..31 do
Pi⬅ ε
od
将种子更新计数器设为0。
ReseedCnt ⬅ 0
初始化生成器。
G ⬅ InitializeGenerator()
获取状态。
R ⬅ (G, ReseedCnt, P0,⋯,P31)
return R

9.5.5获取随机数据

函数 RandomData
输入∶ R 伪随机数生成器状态,由函数修改
n 生成的随机数据的字节数
输出∶ r n字节的伪随机字符串
if length(P)≥MinPoolSize Alast reseed>100 ms ago then
需要更新种子。
ReseedCnt ← ReseedCnt +1
添加需要使用的所有嫡池中字符串的散列值。
s ← ε
for i ε 0,⋯,31 do
if 2^i||ReseedCnt then
s ← s||SHA-256(Pi)
Pi ← ε
fi
od
取得随机数据之后需要更新种子。
Reseed(G, s)
fi
if ReseedCnt=0 then
报错,伪随机数生成器还没有获取种子。
else
种子已经更新(如果需要的话)。让R的生成器部分来生成随机数。
return PseudoRandomData(G,m)
fi

不建议使用比 32字节更小的值:熵源能够高效稳定地提供熵,种子的更新速度也会很缓慢

9.5.6添加事件

函数 AddRandomEvent
输入∶R 伪随机数生成器状态,由函数修改
s 熵源号,取值范围0,,255
i 熵池号,取值范围0,⋯,31。每个熵源必须循环地将它产生的事件分发到所有熵池中
e 事件数据,1 ~ 32 字节的字符串
首先检查参数。
assert1≤length(e)≤320≤s≤2550≤i≤31
将事件数据添加到熵池中。
Pi←Pi||s||lengrh(e)||e

还可以让熵源仅仅把事件发送给累加器进程,再由累加器创建一个单独的线程负责所有的散列运算:设计复杂但能减轻熵源运算负担

9.6种子文件管理

目的:避免重启收集收集过长和首个状态可预测

9.6.1 写种子文件

函数 WriteSeedFile
输入∶R 伪随机数生成器状态,由函数修改
f 需要写入的文件
write(f,RandomData(R,64))

9.6.2 更新种子文件

函数 UpdateSeedFile
输入∶ R 伪随机数生成器状态,由函数修改
f 需要更新的文件
s ← read(f)
assert length(s) = 64
Reseed(G, s)
write(f, RandomData(R,64))

保证在更新种子和更新种子文件之间伪随机数生成器没有被调用过

9.6.3 读写种子文件的时间

在关闭计算机之前对种子文件进行更新。在不关机情况下,需要定时更新种子文件

9.6.4 备份和虚拟机

大多存放种子的文件系统不能避免出现重复状态

例:利用备份还原时就会出现重复状态

解决方法:修复备份系统使得它对伪随机数生成器敏感

相同问题:同一虚拟机的重启和多个虚拟机的同时开启

9.6.5文件系统更新的原子性

flush的非即时性,受控于硬件和操作系统
使用日志解决,但不能达到要求

9.6.6初次启动

由于需要等待熵更新种子,启动时间很长,无法确认是否已经收集到足够的熵来生成良好的密钥
good 方法:利用安装时的配置过程生成随机种子
best方法:利用一个外部随机源创建第一个种子文件

9.7选择随机元素

令k为满足2* ≥n的最小整数。
利用伪随机数生成器生成一个k位的随机数K,K的范围为0,⋯,2'-1。
伪随机数生成器生成的是一定数量的字节,所以可能需要丢弃最后一个字节的部分位。
如果K≥n,返回第2步。

9.8习题

9.1 研究最喜欢的三种编程语言中自带的随机数生成器。你会把它们用于密码中吗?

答:python中默认使用梅森旋转生成,C中默认使用

9.4 分析伪随机数生成器和随机数生成器各自的优缺点。

答:伪随机生成器能够快速生成伪随机数,但是随机数在足够大范围内由一定规律性

随机数生成器完全随机,但生成数据慢,而且会受软硬件采集设备精度影响

9.5 使用一个能够输出随机位流的密码学伪随机数生成器,来实现一个能够从0,1,⋯,-I}
(1≤, ≤ 2')中随机选择整数的随机数生成器。

答:使用平方取中、线性同余和梅森旋转法实现了要求的随机数算法

import matplotlib.pyplot as plt
import datetime
import math
import numpy as np
import random
import time as tm

def scatter_test():
points = b
x, y = zip(*points)
plt.figure()
plt.scatter(x, y, 1,"r",".",alpha=0.1)
plt.show()

def midsquare(x,Num,time):
a_0=str(time)[-6:]
a_1=str(int(a_0)2)
for i in range(Num):
while(len(a_1)!=len(a_0)*2):
a_1=str((x[i-1]%(i+1))%9+1)+a_1
a_2=a_1[3:9]
j=0
while(a_2[j]=='0'):
a_2=a_2+str(((x[i-1]%(i+1))%10))
j=j+1
x[i]=int(a_2)%numRange+startNum
a_0=a_2
a_1=str(int(a_2)
2)

def LCG(x,Num,time):
a_0=int(str(time)[-3:])
alpha=16807
beta=0
m= 2147483647
a=4alpha+1
c=2
beta+1
for i in range(Num):
a_1=(a*a_0+c)%m
x[i]=a_1%numRange+startNum
a_0=a_1

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

class MT19937:
def init(self, seed):
self.mt = [0] * 624
self.mt[0] = seed
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):
self.twist()
y = self.mt[0]
y = y ^ y >> 11
y = y ^ y << 7 & 2636928640
y = y ^ y << 15 & 4022730752
y = y ^ y >> 18
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 ^ self.mt[(i + 397) % 624] >> 1
if y % 2 != 0:
self.mt[i] = self.mt[i] ^ 0x9908b0df

def Mersenne(x,Num,time):
for i in range(Num):
x[i]=(MT19937(int(str(time)[-6:])+i).extract_number())%numRange+startNum

def PYTHON(x,Num,time):
for i in range(Num):
x[i] =random.randint(startNum,endNum)

if name == "main":
while(1):
startNum = int(input("请输入起始范围:"))
endNum = int(input("请输入终止范围:"))
if(endNum>startNum):
numRange = endNum -startNum + 1
break
else:
print("请重新输入")
Num = int(input("请输入随机数个数:"))
time=datetime.datetime.now()
print(time)
a=np.arange(Num)
while 1:
print("1.平方取中\n2.线性同余\n3.梅森旋转\n4.python\n0.结束")
mode = int(input("请输入随机模式:"))
start = tm.perf_counter()
if(mode == 1):
midsquare(a,Num,time)
elif (mode == 2):
LCG(a,Num,time)
elif (mode == 3):
Mersenne(a,Num,time)
elif (mode == 4):
PYTHON(a,Num,time)
elif (mode == 0):
break
else :
print("请重新输入!")
continue
end = tm.perf_counter()
b = []
for i in range(0,Num,2):
b.append([a[i],a[i+1]])
scatter_test()
c = []
count = []
sum = 0
fc = 0
print("当前生成随机数序列:")
for i in a:
sum += i
print(hex(i),end=" ")
if i not in c:
c.append(i)
count.append(1)
else:
count[c.index(i)]+=1
ave = sum/Num
countAve = Num/len(c)
for j in count:
fc+=(j-countAve)**2
c.sort()
count.sort()
print('\n{:10}'.format(str("n"))+'{:10}'.format(str("randomNum"))+'{:^10}'.format(str("Time")))
for i in range(len(c)):
print('{:10}'.format(str(i+1))+'{:10}'.format(str(c[i]))+'{:^10}'.format(str(count[i])))
print("在"+str(startNum)+"~"+str(endNum)+"内生成"+str(Num)+"个随机数")
print("标准生成随机数类数:"+str(min(Num,numRange))+" 生成随机数类数:"+str(len(c)))
print("标准平均数:"+str((startNum+endNum)/2)+" 实际平均数:"+str(ave))
print("标准中位数:"+str((startNum+endNum)/2)+" 实际中位数:"+str(c[len(c)//2]))
print("随机出现次数方差:"+str(fc/len(c)))
print("运行时间为:"+str(end-start)+"s")
q = input("输入任意字符开启下一次循环")
print("程序结束")

第10章素数

非常重要的公钥的密码都是基于素数设计的

10.1整除性与素数

素数:一个数只有1和它自身两个正因子
合数:一个整数大于1且不为素数

引理 1 如果a|b且b|c,那么a|c。

引理 2 如果n为大于1的正整数且d为n除1之外最小的因子,那么d是素数。

欧几里得定理:素数有无穷多个

证明:假设素数的个数是有限的,那么一个包含所有素数的列表也是有限的,记为p1,p2,p3,⋯p,这里k表示素数的个数。定义n∶=p1p2p3⋯pk+1,即 n为所有素数的乘积加上1。
考虑n除1之外的最小因子,我们仍用d来表示这个因子。由引理2可知,d为素数且d|n;但是在那个有限的素数列表中,没有一个素数是n的因子,因为它们都是n-1的因子,n除以列表中任何一个素数p,都会有余数1,所以d为素数且不在列表中。而列表在定义时就包含了所有的素数,这样就出现了矛盾,所以素数的个数是有限的这个假设是错误的,从而可知素数有无穷多个。□

算数基本定理:任何一个大于1的整数都可以唯一表示为有限个素数的乘积

10.2产生小素数

image-20210410224736539

算法思想:每一个合数c都有至少一个比c小的素因子,将比n小的数的倍数定为合数后,其余均为素数
值得注意的是循环和取值的范围
目的:利用小素数来获取大素数

10.3素数的模运算

方法:用p除r,去掉商,将所得的余数作为结果。so模p运算的结果在0,…,p-1中
基本规则:把模运算当作普通的整数运算,但是每一次的结果r都要对p进行取模运算
计算(amod p),就要先找到满足a=q+r且0≤r<p的整数q和r,那么 a mod p 的值就定义为 r

10.3.1加法和减法

加法相当于a+b(-p)
减法相当于a-b(+p)
可以在运算过程中的任何时候进行模运算

10.3.2乘法

ab的最大可能值为(p-1)2=p-2p+1,所以就需要执行一次长除法来找到满足 ab=qp+r且0≤r<p的(q,r),去掉q,r就是结果
可以在运算过程中的任何时候进行模运算,迭代&直接取模都可

10.3.3群和有限域

有限域:模素数p的集合
有限域性质:
    对运算中的每个数,可以加上或减去p的任何倍数而不改变运算的结果。
    所有运算的结果都在0,1,⋯,p-1范围内。
    可以在整数范围内做整个计算,只在最后一步做模运算。于是所有关于整数的代数规则(比如 a(b+c)= ab + ac)仍然适用。
群:在元素中定义了一种运算的集合,一个群由集合和运算构成
群性质:
    群中任意两个元素进行运算的结果也是群中的元素
    乘法运算中不能乘0:乘0无意义&0不能作除数
    一个有限域包含加法群和乘法群
    群可以包含子群,子群元素中运算结果仍在子群元素中

10.3.4 GCD算法

模p除法:乘法的逆运算,满足c · b=a(mod p)的数c即为a/b(mod p),且b≠0。

最大公因子GCD : gcd(a,b)是能够同时整除a和b的最大的数。

欧几里得算法思想:由于b mod a == b - s * a,so任何同时整除a和b的整数k也能够同时整除a和(b mod a),在a==0时,b即为a于b的公因子。

image-20210410004848733

最大公因数GCD与最小公倍数LCM的关系:GCD(a,b)*LCM(a,b)=ab

10.3.5扩展欧几里得算法

目的:做模p除法
算法思想:找到满足gcd(a,b)=ua+vb的两个整数u和v

image-20210410004834957

如何计算模p除法:能够在a/b(mod p)的计算中先算出gcd(b,p)=ub+vp,以得到u=gcd(b,p)/b(mod p)。而由于p为素数,所以gcd(b,p)=1,即得u=1/b(mod p)。所以au=a/b(mod p)

10.3.6模2运算

mod p的有趣特例,加法为异或XOR,乘法为AND运算,由于逆唯一(只可能为1),乘法和除法相同

image-20210411001540503

10.4大素数

获取大素数方法:选择一个随机数并检查它是否为素数

原理:素数数量多,在在整数n的附近,大约每0.7 ln n个数中有一个数为素数,不断找必能找到

image-20210410011020700

image-20210410011110458

10.4.1素性测试

Rabin-Mille目的:目的是检测一个奇数n是否为素数
方法:我们选择一个不超过n的随机值a,称为基,并检验a模n的某个性质,通过对不同的随机值
a重复进行这个测试,可以得到一个可信的最终结论
基本思想Fermat小定理:对任何素数n和所有1 ≤a<n,等式an-1mod n = 1始终成立。几乎对所有的基a都能通过Fermat 测试。

image-20210410011234154

10.4.2计算模指数

对中间数使用mod n防止数据过大

二进制算法递归规则:

■如果s =0,那么结果为1。
■如果s>0并且s为偶数,那么先使用这些规则计算y=d"modn,结果为dmodn=
178]y" mod n。
■如果s>0并且s为奇数,那么先使用这些规则计算y=d-1modn。结果为amod
n = a·y mod n。

asmod n需要多少次乘法呢?设s有k位,即2k-1≤s<2k,至多需要进行 2k 次模n乘法.对一个2000位的数进行素性测试,则s大约也是 2000位,只需要4000次乘法,在大多数桌面计算机的计算能力之内。

使用Montgomery乘法等可减少计算as时间

直接实现模指数易遭受时间攻击,参见 15.3

10.5习题

10.2 用以下两种方法分别计算13635+16060+8190+21363(mod 29101)并比较结果是否相同∶每完成一次加法就对结果取模 29101; 先计算最终的和再对结果取模 29101。

答:结果都为1046

10.3 用以下两种方法分别计算12358·1854·14303(mod 29101)并比较结果是否相同∶每完成一次乘法就对结果取模 29101; 先计算最终的积再对结果取模 29101。

答:结果都为25392

10.4 {1,3,4} 是模 7乘法群的一个子群吗?

答:不是,如3*4=12=5(mod 7),运算结果不在子群中

10.5 使用 GCD 算法来计算 91261和117035 的GCD。

答:GCD(117035,91261)=GCD(91261,25774)=GCD(25774,13939)=GCD(13939,11835)=GCD(11835,2104)=GCD(2104,1315)=GCD(1315,789)=GCD()=GCD(789,526)=GCD(526,263)=263

posted on 2023-03-23 11:21  Zzangg  阅读(49)  评论(0编辑  收藏  举报