Task 1:用错误的方式生成加密密钥
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#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));
,rand()
函数会默认将 1 设置为随机数种子,所以导致每次运行生成的加密密钥都是相同的。
time(NULL)
:用于获取当前时间srand()
:用于设置rand()
函数的种子。
Task 2:猜测密钥
- 计算时间种子:计算从2018年4月17日晚上九点到4月18日零点的时间种子作为遍历的范围。

- 根据这些时间种子生成加密密钥并写入文件
key.txt
。
// guess_key.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#define KEYSIZE 16
int main() {
time_t start_time = 1524013200; // 2018年4月17日21点的时间戳
time_t end_time = 1524024000; // 2018年4月18日0点的时间戳
unsigned char key[KEYSIZE]; // 密钥数组
FILE *file = fopen("keys.txt", "w"); // 打开文件进行写操作
// 遍历每一秒
for (time_t seed = start_time; seed <= end_time; seed++) {
srand((unsigned int)seed); // 设置随机数种子
// 使用当前种子生成密钥
for (int i = 0; i < KEYSIZE; i++) {
key[i] = rand() % 256; // 生成0-255范围内的随机字节
}
// 将密钥写入文件,按16进制格式输出
for (int i = 0; i < KEYSIZE; i++) {
fprintf(file, "%.2x", key[i]);
}
fprintf(file, "\n"); // 每个密钥后换行
}
fclose(file); // 关闭文件
printf("密钥已写入文件 keys.txt\n");
return 0;
}

- 遍历这些密钥,对密文进行解密,并与明文进行比较,找到正确的密钥:
# guess_key.py
from Crypto.Cipher import AES
from binascii import unhexlify, hexlify
plain_text = unhexlify("255044462d312e350a25d0d4c5d80a34")
cipher_text = unhexlify("d06bf9d0dab8e8ef880660d2af65aa82")
iv = unhexlify("09080706050403020100A2B2C2D2E2F2")
def decrypt_aes(key, ciphertext, iv):
# 创建 AES 解密对象
cipher = AES.new(key, AES.MODE_CBC, iv)
# 解密数据
decrypted = cipher.decrypt(ciphertext)
return decrypted
def main():
# 读取密钥文件
with open("keys.txt", "r") as file:
keys = file.readlines()
# 遍历每一行密钥,尝试解密
for key_line in keys:
key = unhexlify(key_line.strip()) # 将密钥从16进制转换为字节
decrypted_text = decrypt_aes(key, cipher_text, iv)
if decrypted_text == plain_text:
print(f"正确的密钥是: {key_line.strip()}")
print(f"解密后的结果是: {hexlify(decrypted_text).decode()}")
print(f"明文是: {hexlify(plain_text).decode()}")
break
else:
print("没有找到匹配的密钥")
if __name__ == "__main__":
main()

Task 3:测量内核的熵
使用下面的命令可以打印出当前内核的熵:
$ cat /proc/sys/kernel/random/entropy_avail
使用 watch 命令来监控内核熵的变化情况:
$ watch -n .1 cat /proc/sys/kernel/random/entropy_avail

- 移动鼠标时:发现会使熵值显著地增大。
- 点击鼠标时:发现会使熵值显著地增大。
- 输入时:发现会使熵值显著地增大。

- 读取一个大文件时:发现会使熵值显著地增大。

- 访问一个网站时:发现会使熵值显著地增大。

以上这些操作都会使内核熵显著增大。
Task 4:从 /dev/random
中获取随机数
使用 cat 命令持续从 /dev/random
读取伪随机数,并使用管道符传递到 hexdump 进行输出。并使用 watch 命令监视内核熵的变化情况。

当不移动鼠标也不键入任何内容时,发现不会有新的伪随机数生成,只有当移动鼠标时,才会有新的伪随机数生成。
Q:假设一个服务器使用 /dev/random 与客户端生成随机会话密钥。请描述你将如何对这样的一个服务器发起拒绝服务(DoS)攻击。
A:由于 /dev/random
是一个基于熵池的伪随机生成器,并且具有阻塞特性,当熵池中的数据不足时,它会阻塞伪随机的生成,直到熵池变得足够大,利用这个机制,我们可以向服务器发起大量请求,每次请求都会使服务器调用 /dev/random
生成会话密钥,从而消耗熵池中的数据,当熵池中没有足够的熵源时,/dev/random
就会进入阻塞状态,进而拒绝服务,成功对服务器进行了拒绝服务攻击。
Task 5: 从 /dev/urandom
获取伪随机数
- 观察
/dev/urandom
生成的伪随机数
使用 cat 命令持续从 /dev/urandom
读取伪随机数,并使用管道符传递到 hexdump 进行输出。并使用 watch 命令监视内核熵的变化情况。

/dev/urandom
会源源不断地输出伪随机数,即使不移动鼠标也会生成,移动鼠标时,也会一直生成,并且内核熵在一直增加。
- 测量
/dev/urandom
生成的伪随机数的质量
首先我们从 /dev/urandom
文件中采集 1MB 的伪随机数,然后使用 ent
工具进行测量。

- 熵值为 7.999828 接近 8,完美的随机数的熵值为 8 。
- 理想压缩率为 0,说明数据非常接近完全随机。
- 卡方分布检验,概率为 58.89%,接近 50%。
- 算数平均值接近 127.5。
- 蒙特卡洛方法估计 $\pi$ 的值为 3.137180852,接近3.14
- 相关系数为0.000150,相关性很小,说明很接近完美随机数。
以上说明 /dev/urandom
生成的随机数质量良好。
- 使用
/dev/urandom
生成一个 256 bit 的加密密钥
#include <stdio.h>
#include <stdlib.h>
#define LEN 32 // 256 bits
int main() {
// 分配内存来存储 256 位(32 字节)随机密钥
unsigned char *key = (unsigned char *) malloc(sizeof(unsigned char) * LEN);
// 打开 /dev/urandom 文件获取随机数
FILE* random = fopen("/dev/urandom", "r");
// 读取 32 字节(256 位)随机数
size_t bytes_read = fread(key, sizeof(unsigned char), LEN, random);
// 输出 256 位(32 字节)密钥
printf("Generated 256-bit key:\n");
for (size_t i = 0; i < LEN; i++) {
printf("%02x", key[i]); // 输出每个字节的 2 位十六进制数
}
printf("\n");
// 关闭文件并释放内存
fclose(random);
free(key);
return 0;
}
