Pseudo Random Number Generation Lab 伪随机数 seed


Pseudo Random Number Generation Lab

生成随机数是安全软件中的一个相当常见的任务。 在许多情况下,用户未提供加密密钥,而是在软件内生成。 他们的随机性非常重要; 否则,攻击者可以预测加密密钥,从而击败加密的目的。 许多开发人员知道如何从他们的先前经验中生成随机数(例如,对于Monte Carlo仿真),因此它们使用类似的方法来为安全目的生成随机数。 不幸的是,一系列随机数对于蒙特卡罗模拟可能是良好的,但它们可能对加密密钥不利。 开发人员需要知道如何生成安全的随机数,或者他们会犯错误。 在一些知名的产品中,包括类似的错误,包括Netscape和Kerberos。

在这个实验室中,我们将学习为什么典型的随机数生成方法不适用于生成秘密,例如加密密钥。 他们将进一步学习标准的方法来生成适合安全目的的伪随机数。 此实验室涵盖以下主题:

  • 伪随机数生成
  • 随机数生成中的错误
  • 生成加密密钥
  • /dev/random/dev/urandom 设备文件

代码仓库:https://github.com/SKPrimin/HomeWork/tree/main/SEEDLabs/Crypto_Random_Number

任务1:以错误的方式生成加密密钥

要生成良好的伪随机数,我们需要从一个随机的东西开始; 否则,结果将是非常可预测的。 以下程序使用当前时间作为伪随机数发生器的种子。

#include 
#include 
#include 
#define KEYSIZE 16
void main()
{
    int i;
    char key[KEYSIZE];
    printf("%lld\n", (long long)time(NULL));
    srand(time(NULL));
    for (i = 0; i < KEYSIZE; i++)
    {
        key[i] = rand() % 256;
        printf("%.2x", (unsigned char)key[i]);
    }
    printf("\n");
}

库函数时间()将时间返回以来秒以来的秒数,1970-01-01 00:00:00 +0000(UTC)。 运行上面的代码,并描述您的观察。 然后,注释退出行srand (time(NULL)); ,再次运行程序,并描述您的观察。 在两种情况下使用观察来解释代码中SRAND()和时间()函数的目的。

代码如下,首先注释掉 srand (time(NULL));

gcc task1.c -o task1 
./task1

在反复运行多次之后发现,随着时间改变,注释掉 srand (time(NULL));

后编译运行的结果只有秒数在变,但是生成的随机数是不变的。 取消注释后:

发现每次的随机数和秒数都不一样。 这是因为 srand(time(NULL))函数用于给随机数生成函数 rand() 设定种子;time 是 C 语言获取当前系统时间的函数,以秒作单位, 代表当前时间自 Unix 标准时间戳(1970 年 1 月 1 日 0 点 0 分 0 秒, GMT)经过了多少秒;每次运行的时间不一样,故得到的随机数也不 一样。当注释掉 srand (time(NULL))时,由于没有设定种子,就会使 用默认是随机数种子 0,因此每次运行程序,产生的随机数都是相同的。

任务2:猜测密钥

2018年4月17日,Alice Fi保存了她的纳税申报表,她在磁盘上保存了回报(PDF file)。为了保护file,她使用任务1中描述的程序生成的键加密了PDF文件。她在笔记本中写下了钥匙,该笔记本电脑安全地存储在保险箱中。几个月后,鲍勃闯入了她的电脑并获得了加密纳税申报表的副本。由于爱丽丝是一个大公司的首席执行官,这一文件非常有价值。

Bob无法获得加密密钥,而是通过查看Alice的计算机,他看到了关键代表程序,并怀疑程序可以生成Alice的加密密钥。他还注意到了加密文件的时间戳,即“2018-04-17 23:08:49”。他猜到了在创建文件之前,可以在两个小时的窗口中生成密钥。

由于FI LE是PDF FI LE,其具有标题。标题的开始部分始终是版本号。围绕创建文件的时间,PDF-1.5是最常见的版本,即,标题以%PDF-1.5开始,这是8个字节的数据。接下来的8字节的数据也很容易预测。因此,Bob很容易获得明文的第16个字节。基于加密文件的元数据,他知道使用AES-128-CBC加密文件。由于AES是一个128位密码,16字节明文由一个明文组成,因此Bob知道一块明文及其匹配的密文。此外,BOb还知道来自加密的文件(IV永远不会加密)的初始向量(IV)。这是Bob知道的:

Plaintext: 255044462d312e350a25d0d4c5d80a34
Ciphertext: d06bf9d0dab8e8ef880660d2af65aa82
IV: 09080706050403020100A2B2C2D2E2F2

您的工作是帮助Bob 查出 Alice的加密密钥,因此您可以解密整个文档。 您应该编写一个程序以尝试所有可能的密钥。 想要生成正确的密钥几乎是不可能的任务。但是,由于Alice使用time()来创建她的随机数生成器,您应该能够轻松地找到她的密钥。 您可以使用date命令用来打印出特定时间之间的秒数,1970-01-01 00:00:00 +0000(UTC)。 请参阅以下示例。

$ date  -d  "2018-04-15  15:00:00"  +%s
1523818800

将市区调至纽约

首先根据加密文件时间戳计算两小时内秒数范围:

date  -d  "2018-04-17  21:08:49"  +%s
date  -d  "2018-04-17  23:08:49"  +%s

1524013729

1524020929

计算密钥

编写代码获得所有可能的密钥:

#include 
#include 
#include 
#define KEYSIZE 16
#define START 1524013729
#define END 1524020929
void main()
{
    int i;
    time_t t;
    char key[KEYSIZE];
    FILE *f;
    f = fopen("key.txt", "w");
    for (t = START; t < END; t++) // 两小时之内
    {
        /* 初始化随机数发生器 */
        srand((unsigned)t);

        /* 输出 0 到 256之间的16个随机数 */
        for (i = 0; i < KEYSIZE; i++)
        {
            key[i] = rand() % 256;
            fprintf(f, "%.2x", (unsigned char)key[i]);
        }
        fprintf(f, "\n");
    }
}

编译执行

gcc task2.c -o task2 
./task2

keys.txt文件是根据开始结束时间创建的,在这段时间内可能生成的所有键都在keys.txt中

验证密钥

现在要做的就是对所有密钥一个一个的尝试,直到成功为止,于是编写脚本get_key.py:

# /*   get_key.py  */
from Crypto import Random
from Crypto.Cipher import AES

ciphertext = "d06bf9d0dab8e8ef880660d2af65aa82"
IV = "09080706050403020100A2B2C2D2E2F2".lower().decode("hex")
plaintext1 = "255044462d312e350a25d0d4c5d80a34".decode("hex")

with open('key.txt') as f:
    keys = f.readlines()

for k in keys:
    key = (k[:-1]).decode("hex")
    cipher = AES.new(key, AES.MODE_CBC, IV)
    encrypted = cipher.encrypt(plaintext1)
    if ciphertext == encrypted.encode("hex")[0:32]:
        print("Match found")
        print("key: "+k[:-1])
        print("Ciphertext: " + ciphertext)
        print("Encrypted: " + encrypted.encode("hex"))

运行脚本

python get_key.py

得到密钥:95fa2030e73ed3f8da761b4eb805dfd7

时间作为seed值并不是一个真正的随机数,用时间生成随机数不可取。

任务3:测量内核的熵

在虚拟世界中,难以创建随机性,即,单独的软件很难创建随机数。 大多数系统诉诸物理世界获得随机性。 Linux从以下物理资源中获得了随机性:

void  add_keyboard_randomness(unsigned  char  scancode);
void  add_mouse_randomness(__u32  mouse_data);
void  add_interrupt_randomness(int  irq);
void  add_blkdev_randomness(int  major);

前两个非常容易理解:第一个使用按键之间的时间; 第二个使用鼠标运动和中断定时;第三个一个使用中断定时收集随机数。 当然,并非所有中断都是良好的随机性。 例如,定时器中断不是一个不错的选择,因为它是可预测的。 但是,磁盘中断是更好的度量。 最后一个测量块设备请求的文件。
使用熵测量随机性,这与信息理论中的熵的含义不同。 在这里,它只是意味着系统当前具有多少位随机数。 您可以使用以下命令突出内核在当前时刻有多少熵。

$  cat  /proc/sys/kernel/random/entropy_avail

让我们通过Watch运行上面的命令来监视熵的更改,该命令定期执行程序,显示全屏中的输出。 以下命令每0.1秒运行CAT程序。

$  watch  -n .1  cat  /proc/sys/kernel/random/entropy_avail

请运行上述命令。当它运行时,移动鼠标,单击鼠标,键入一些内容,阅读大文件,访问网站。哪些活动显著增加了熵。请在报告中描述您的观察结果。

运行以下命令:实时监测熵

watch  -n .1  cat  /proc/sys/kernel/random/entropy_avail

发现每次移动鼠标、敲击键盘等都会引起熵的变化

任务4:从/ dev /random获取伪随机数

Linux将从物理资源收集的随机数据存储到随机池中,然后使用两个设备将随机性转换为伪随机数。 这两个设备是/ dev /随机和/ dev / urandom。 他们有不同的行为。 / dev /随机设备是阻塞设备。

即,每次通过该设备发出随机数时,随机池的熵将会减小。 当熵达到零时,/ dev /随机将阻塞,直到它增益足够的随机性。

让我们设计一个实验以观察/开/随机设备的行为。 我们将使用cat命令从/ dev /随机读取伪随机数。 我们将输出管向exdump管子进行漂亮的打印。

cat /dev/random | hexdump
watch -n .1 cat /proc/sys/kernel/random/entropy_avail

请运行上面的命令,同时使用Watch命令监视熵。

如果您没有移动鼠标或键入任何内容,会发生什么。 然后,随机移动鼠标,看看你是否可以观察任何差异。 请描述和解释您的观察。

问题:如果服务器使用/开/随机以使用客户端生成随机会话密钥。 请描述如何在此类服务器上启动拒绝服务(DOS)攻击。

不移动鼠标时不会有任何反应,只有移动鼠标到到一定程度时才会出现新的一行数据。

且entropy_avail值会从0~70不断变化,与此前的数据相比大小减小

任务 5:从/dev/urandom获取伪随机数

Linux 提供了另一种通过 /dev/urandom 设备访问随机池的方法,除了此设备不会阻塞。/dev/random 和 /dev/urandom 都使用池中的随机数据来生成伪随机数。 当熵不够时,/dev/random 将暂停,而 /dev/urandom 将继续生成新数字。将池中的数据视为"seed",正如我们所知,我们可以使用seed来生成任意数量的伪随机数。

让我们看看 /dev/urandom 的行为。我们再次使用 cat 从此设备获取伪随机数。请运行以下命令,并描述移动鼠标是否对结果有任何影响。

$  cat  /dev/random   |  hexdump
watch -n .1 cat /proc/sys/kernel/random/entropy_avail
cat /dev/urandom | hexdump

不管三七二十一,控制台都会疯狂打印数据

质量评测

让我们测量随机数的质量。我们可以使用名为 ent 的工具,该工具已安装在虚拟机中。根据其手册,"ent将各种测试应用于存储在文件中的字节序列,并报告这些测试的结果。该程序可用于评估伪随机数生成器,用于加密和统计采样应用程序,压缩算法以及感兴趣的文件信息密度的其他应用程序"。让我们首先从 /dev/urandom 生成 1 MB 的伪随机数,并将它们保存在一个文件中。然后,我们对文件运行 ent。请描述您的结果,并分析随机数的质量是否良好。

$  head  -c  1M  /dev/urandom  >  output.bin
$  ent output.bin

在本地发现ent指令无法在seed12上使用。这里我们又请来了老朋友kali

评测结果显示

Entropy = 7.999828 bits per byte.
熵=每一个字节7.999828位。————表示该文件的信息非常密集——基本上是随机的。

Optimum compression would reduce the size
of this 1048576 byte file by 0 percent.
最佳压缩将将1048576字节文件的大小缩小为0%。————

Chi square distribution for 1048576 samples is 249.82, and randomly
would exceed this value 57.98 percent of the times.
1048576样品的卡方分布为249.82,随机将超过此值的时间为57.98%。————卡方检验是最常用的数据随机性检验

Arithmetic mean value of data bytes is 127.5494 (127.5 = random).
数据字节的算术平均值为127.5494(127.5 =随机)。————值高于127.5表示它是随机的

Monte Carlo value for Pi is 3.140064774 (error 0.05 percent).
PI的Monte Carlo值为3.140064774(错误0.05%)。————如果是随机的,Pi的蒙特卡罗值将接近于Pi的值,它只有0.02%,因此我们可以认为它是随机的

Serial correlation coefficient is -0.001884 (totally uncorrelated = 0.0).
串行相关系数为-0.001884(完全不相关= 0.0)。————序列相关系数——这个量度量文件中每个字节对前一个字节的依赖程度,对于随机数,这个值将接近0

真正的随机数

从理论上讲,/dev/random设备更安全,但在实践中,没有太大区别,因为/dev/urandom使用的"seed"是随机且不可预测的(每当新的随机数据可用时,/dev/urandom都会重新播种)。 /dev/random 的阻塞行为的一个大问题是,阻塞可能导致拒绝服务攻击。因此,建议我们使用 /dev/urandom 来获取随机数。要在我们的程序中执行此操作,我们只需要直接从此设备文件中读取即可。以下代码片段演示了如何操作。

#define  LEN  16    //  128  bits
unsigned  char  *key  =   (unsigned  char  *)  malloc(sizeof(unsigned  char)*LEN); FILE *  random  =  fopen("/dev/urandom",  "r");
fread(key,  sizeof(unsigned  char) *LEN,  1,  random);
fclose(random);

请修改上述代码片段以生成 256 位加密密钥。

/*   task5.c   */
#include 
#include 
#define LEN 32 //  256  bits

void main()
{

    int i;
    unsigned char *key = (unsigned char *)malloc(sizeof(unsigned char) * LEN);
    FILE *random = fopen("/dev/urandom", "r");
    for (i = 0; i < LEN; i++)
    {
        fread(key, sizeof(unsigned char) * LEN, 1, random);
        printf("%.2x", *key);
    }
    printf("\n");
    fclose(random);
}

编译运行

gcc task5.c -o task5
./task5

这是真正的随机数,因为它是从/dev/urandom读取的