第9,10章笔记,问题与思考

知识点归纳

|第九章|生成随机性

  为了生成密钥信息,我们需要随机数生成器(RNG)。生成良好的随机性是许多密码操作中非常重要的一部分,同时也是一项具有挑战性的工作。
  衡量随机性的度量称为熵。以下是一个比较粗略的解释:如对一个完全随机的32位的字而言,它的嫡是32位;如果这个32位的字只有4个可取值,并且每个值都是25%的概率被选中,那么它的熵是2位。对一个随机数而言,知道的信息越多,它的熵越小。

  • 9.1 真实随机

  典型的计算机具有多个熵源,例如按键的精确时长和鼠标的精确移动轨迹,甚至已经有研究打算利用硬盘内部湍流引起的硬盘访问时间的随机波动来作为熵源。但是这些熵源在一定程度上都是值得怀疑的,因为在一些环境中,攻击者可以影响或者去测量这些随机熵源。


  • 9.1.1使用真实随机数的问题

真实的随机数除了难以获取之外,在实际应用中还存在其他问题。
1、随机数不是随时都可以获取的。
2、第二个问题就是真实的随机源有可能失效,比如说物理随机数生成器。一种失效的情况就是生成器的输出结果可以通过某种方式来预测。
3、第三个问题是如何判断从某一具体的物理事件中能够提取多少嫡。除非你为随机数生成器设计了专门的硬件,否则就很难计算嫡的值。

  • 9.1.2 伪随机数

      伪随机数作为使用真实随机数的一种替代品,实际上并不是真正随机的。伪随机数的生成―般都是通过确定性算法,从种子计算得到。如果种子泄露,那么伪随机数就可以预测了。
    

  忘记你的编程库中一些常见的随机函数,因为它们几乎肯定不是严格的密码学上的伪随机数生成器,除非给出了明确的密码强度,否则我们都不要使用库中提供的伪随机数生成器。


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

  实际上,我们只使用真实随机数做一件 事情:作为伪随机数生成器的种子。这种做法解决了使用真实随机数的一些问题。一且伪随机数生成器获得了种子,随机数可以随时获取。同时我们可以不断获得新的真实随机数作为新的种子,这样即使种子泄露,生成器的输出也不可能被完全预测。


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

  对伪随机数生成器的攻击有很多形式。一种直接的攻击方法就是攻击者尝试从生成器的输出重构内部状态,这是一种典型的密码学攻击方法,使用密码技术也相对来说比较容易应对。
  如果同一个伪随机数生成器状态被多次使用也会有问题。比如说,如果两个或更多的虚拟机从同样的状态启动,并从磁盘上读取相同的种子文件时,这个伪随机生成器状态就被多次使用了。


  • 9.3 Fortuna

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

  • 9.4生成器

  生成器负责将固定长度的内部状态转换为任意长度的输出。我们使用类似AES的分组密码作为生成器,也可以使用AES ( Rijndael). Serpent 或者Twofish。 生成器的内部状态包括一个256位的分组密码密钥和-一个128位的计数器。


  • 9.4.1初始化

  这部分非常简单。将密钥和计数器设置为0.以表示生成器还没有获取种子。


  • 9.4.2更新种子

  更新种子操作通过输入任意字符串更新生成器状态。这时我们不用关心输入字符串的具体内容。为了保证输入字符串和现有密钥完全混合,我们使用了散列函数。


  • 9.4.3生成块

  这个内部函数的作用就是生成多个随机块,但只能被生成器调用。伪随机数生成器之外的任何模块都不能调用该函数。


  • 9.4.4生成随机数

  当用户请求随机数时,生成器就会调用这个函数来产生随机数据,输出长度最多可达20字节。同时这个丽数还会确保之前生成的结果信息都已经被清除。


  • 9.5 累加器

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

  假设环境中有多个嫡源,并且每个熵源在任何时候都能及时产生事件来提供嫡。具体使用哪种嫡源并不重要,只需要保证至少一个嫡源提供的熵(用于生成数据)是攻击者无法预测的。因为不知道攻击者会如何攻击,所以最好的选择就是将所有看起来不可预测的数据都用作熵源。按键时长和鼠标的移动都是合适的熵源。


  • 9.5.2 熵池

  为了更新生成器的种子,需要将事件放入一个足够大的嫡池中,“足够大”意味着攻击者无法穷举出嫡池中事件的所有可能值。利用这样的熵池生成新的种子,可以破坏攻击者之前获得的关于生成器状态的信息。


  • 9.5.4 初始化

初始化是一个简单函数。到目前为止,我们只讨论生成器和累加器,但是下面定义的函数实现了Fortuna的一些外部接口,他们是再整个伪随机数生成器上进行操作的。


  • 9.5.5 获取随机数据

该函数不只简单地对伪随机数生成器的生成器组件进行封装,还要处理更新种子。


  • 9.5.6 添加事件

当熵源产生了新的随机事件时,就会调用该函数。每个熵源都有唯一的熵源编号,这个我们不详细描述如何分配熵源编号,因为这取决于具体的环境。


  • 9.6 种子文件管理

  每次计算机重启后,我们必须等到嫡源提供足够的事件后,才能获取第一个种子来生成随机数据:另外,我们也无法保证第一次种子后的状态是攻击者无法预测的。解决方案就是使用种子文件。伪随机数生成器保存了一个嫡文件,叫作种子文件,种子只有伪随机数生成器可以获取。每次重启之后,伪随机数生成器读取种子文件并使用文件内容作为嫡源来获得一个未知状态。


  • 9.6.1 写种子文件

第一个问题就是如何生成种子文件,这可以通过一个简单的函数实现。


  • 9.6.2 更新种子文件

显然,种子文件能够被读取,不过通常将种子文件的读取和更新操作一起完成。


  • 9.7 选择随机元素

  一种用来在任意范围中选择一个随机数的 正确方法是使用试错法。比如为了选择0,...,4中的-一个随机数,由于8是2的幂,所以可以先在0,...,7中选择一个随机数,如果结果是5或更大就重新选择,直到结果在0,...,4之间。换言之,可以生成一个位数正确的随机数并丢弃那些不合适的值。
下面是在0,.....,n-1(n≥2)中选择一个随机数算法的正式描述:

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

  这个过程可能有点浪费,在最坏的情况下,平均一半的中间值会被丢弃。下面是一个改进方案,举个例子,由于2^32-1是5的倍数,所以我们可以在0,...,2^32-2中选择-个随机数并对5取模。而从0,...,2^32-2中选择一个值,我们使用尝试法时中间值被丢弃的概率就非常低了。首先选择一个合适的k满足2^k≥n,定义q:=[2^k/n],利用尝试法在0,...,nq-1中选择一个随机数r,一旦生成一个合适的r,那么最终的随机数就是(r mod m)。

|第十章|素数

  • 10.1 整除性与素数

  如果b除以a余数为0,则称a是b的一个因子(记作a|b.读作“a整除b")。比如,7是35的一个因子,记作7|35。 如果一个数只有1和它自身两个正因子,我们就称这个数是素数。比如,13是索数,两个因子为1和13。最初几个素数很容易找到: 2.3.5.7.11......如果一个整数大于1且不为素数,我们就称为合数。1既不是素数也不是合数。

引理1     如果a|b且b|c. 那么a|c。
引理2     如果n为大于1的正整数且d为n除 1之外最小的因子,那么d是素数。
定理3     (欧几里得)素数有无穷多个。

另一个有用的定理是算术基本定理:任何一个大于1的整数都可以唯一表示为有限个素数的乘积(不考虑素数的先后顺序)。比如,15=3x5,255=3x5x17,60=2x2x3x5。


  • 10.2 产生小素数

  有时候拥有一张小素数的列表是非常有用的,这就要用到Eratosthenes的筛选法,筛选法至今仍然是生成小素数最好的算法。下面的伪代码中20可以用任何适当小的常数代替。


  • 10.3 素数的模运算

  素数模运算的基本规则是,把模运算当作普通的整数运算,但是每一次的结果 r都要对p进行取模运算。取模运算非常容易:用p除r,去掉商,将所得的余数作为结果。例如,对25取模7运算时用7除25,得到商为3余数为4,于是(25 mod7)=4。


  • 10.3.1 加法和减法

  模p加法运算很简单,只需将两个数相加,如果结果大于或者等于p,就减去p。由于加法运算的两个参数都在0,...,p-1之中,它们的和不可能超过2p-1,所以最多只要做一次减去p的运算,得到的结果就在正确的范围内了。


  • 10.3.2 乘法

  乘法比加法复杂一点。为计算(ab modp).首先按整数乘法的规则计算ab的值,然后对结果做取模p运算。由于ab的最大可能值为(p-1)^2=p^2-2p+1,所以就需要执行一次长除法来找到满足ab qptr且0≤r<p的(q,r),去掉q,r 就是结果。


  • 10.3.3 群和有限域

数学家称模索数p的数的集合为有限域,也经常称为“mod p"域,或者简称“mod p"。对于modp域中的计算有如下一些性质:
■对运算中的每个数,可以加上或减去p的任何倍数而不改变运算的结果。
■所有运算的结果都在0, 1...p-1范围内。
■可以在整数范围内做整个计算,只在最后-一步做模运算。
我们还需要引人群的概念,这是一个简单的数学术语。群就是一-个集合, 并且在集合中的元素上定义了一种运算,比如加法或者乘法。Zp中的元素和加法一起就构成了 一个加法群,群中任意两个数相加的结果也是群中的元素。如果在这个群中进行乘法运算就不能使用
0 (这是因为乘以0没有什么意义,而且0不能做除数)。但是,*.p-1和模p乘法一起也构成一个群,这个群称为模p乘法群并且有多种表示方法,我们使用的是Z,。一个有限域包括两个群:加法群和乘法群。比如,有限城Z,包括了由模p加法定义的加法群和乘法群Zp。

  • 10.3.4 GCD 算法

  首先回顾一下GCD的概念:两个数a和b的最大公因子(或GCD)就是满足k|a和k|b的最大整数k。换句话说,gcd(a,b) 是能够同时整除a和b的最大的数。

  • 10.3.5 扩展欧几里得算法

  欧几里得算法的主要思想是,在计算gcd(a,b)的同时找到满足gcd(a,b) = ua +vb的两个整数u和v,找到这样的u和v我们就能够计算a/b (mod p)了。


  • 10.3.6 模2运算

  模2加法就是编程语言中的异或函数(XOR),乘法是简单的AND运算。在模2域中,只有一个可能的逆(1/1= 1),所以除法运算与乘法运算是相同的。另外,Z2 域也是分析某些计算机算法得重要工具。

问题

  • 为什么随机数种子文件的读取更新操作一起完成?

  无论何时使用种子文件来更新种子,都要在用户请求随机数据之前更新种子文件,而且必须要保证磁盘上的数据也进行了更新。在一些文件系统中, 对文件数据和文件管理信息的操作是分开进行的,所以更新种子文件时文件管理信息和实际的文件信息不符,如果此时机器断电了,种子文件就会损坏甚至丢失,这样的文件系统对安全系统来说是不适用的。所以要保证随机数文件的读取更新一起完成。

思考

  • 研究c语言中自带的随机数生成器。

在C语言中,ANSIC C程序库提供rand()函数来产生随机数。但事实上,rand()是并不是一个真正的随机数产生器,即可以预测随机序列的顺序,在默认随机种子情况下产生0~99之间的随机数,其随机序列为{83,86,77,15,……},比如以下程序:

#include<stdio.h>
#include<stdlib.h>
 
int main(){
    int num;
    printf("A set of random numbers:\n");
    for(int i = 0; i < 10; i++){
        num = rand()%100; //产生0-99之间的随机数;
        printf("%d ",num);
    }
    putchar('\n');
    return 0;
}

  rand()使用的是产生伪随机数的“魔术公式”,它的随机数种子是一直没有变的,所以每次产生的随机序列是一样的。在调用rand()函数之前,可以使用srand()函数设置随机数种子,如果没有设置随机数种子,rand()函数在调用时,自动设计随机数种子为1。随机种子相同,每次产生的随机数也会相同。rand()函数被包含在头文件<stdlib.h>中。
  如果想要构造每次产生不同的随机数序列的函数,则需要引入一个不断变化的随机种子。最常用的就是时间变量,利用时间函数time()获取系统时间(其返回值time_t一定是数值类型)。time()的参数是一个time_t类型对象的地址,时间值则存储在该地址中。所以,可以使用空指针0或者NULL作为参数。此时,时间通过返回值机制提供。time()函数包含在头文件<time.h>中。写一个简单地程序说明time()函数;


#include<stdio.h>
#include<time.h>
 
int main(){
    int num1,num2;
    printf("The return value of time:\n");
    num1 = time(0);
    num2 = time(NULL);
    printf("%d,%d\n",num1,num2);
    return 0;
}

实践

  • 编写一个随机数生成算法。

#include <stdio.h>  
#include <time.h>  
static unsigned long rand_seed;  
void mysrand (unsigned long int);  
void myrand (long,long);  
int  
main (void)  
{  
    printf("*************************************************\n\n");
    printf("                  伪随机数生成器                 \n\n");
    printf("*************************************************\n\n");
    int num;
    long high,low;
    printf("请输入需要生成的伪随机数个数: ");
    scanf("%d",&num);
    printf("\n");
    printf("请输入生成随机数的上限值: ");
    scanf("%ld",&high);
    printf("\n");
    printf("请输入生成随机数的下限值: ");
    scanf("%ld",&low);
    printf("\n");
    int i;  
    int count = 0;
    mysrand (time (NULL));
    printf("*****************开始随机数生成******************\n\n");
    for (i = 0; i < num; i++)  
      {  
          myrand (high,low);  
          count++;
          if(count == 12)
          {
              printf("\n");
              count = 0;
          }
      } 
    printf("\n\n");
    printf("*****************结束随机数生成******************\n\n");
    return 0;  
}  
  
void  
mysrand (unsigned long seed)  
{  
    rand_seed = seed;  
}  
  
void  
myrand (long high, long low)  
{  
    rand_seed = (rand_seed * 16807L) % ((1 << 31) - 1);  
    printf ("%3ld ", rand_seed%(high+1-low)+low);  
}
posted @ 2021-04-11 13:46  马梓峻  阅读(112)  评论(0编辑  收藏  举报