密钥加解密实验
1.实验描述
本实验的学习目的是让学生熟悉加密的概念,熟悉和了解加密算法(cipher)、加密模式(encryption mode)、 填充(padding)、以及初始向量(IV)的定义与作用。此外,学生还可以通过使用工具和编写程序加密/解密信息。
2.实验环境
本实验中,我们将使用 openssl 命令行工具及其库。实验环境中已自带命令行工具,需安装 openssl 开发库。
sudo apt-get update
sudo apt-get install -y libssl-dev
编辑器使用 bless 十六进制编辑器,需预先安装。
sudo apt-get install -y bless
3.实验内容
3.1 使用不同的加密算法与加密模式进行加密
在 /home/shiyanlou 目录下新建一个文件 plain.txt并输入shiyanlou,输入下列指令进行加密:
openssl enc ciphertype -e -in plain.txt -out cipher.bin \
-K 00112233445566778899aabbccddeeff \
-iv 0102030405060708
将其中的ciphertype替换成指定的加密类型,比如-aes-128-cbc, -aes-128-cfb, -bf-cbc 等,分别使用上述三种加密类型进行加密,如下所示:
3.2 加密模式——ECB 与 CBC 的区别
首先根据实验指导下载图片:
wget http://labfile.oss.aliyuncs.com/courses/241/pic_original.bmp
sudo apt-get install gpicview
使用 CBC 模式对这张图片进行加密,并用bless编辑器修改文件头:
打开图片,结果如下所示:
按照上述方法,再使用 ECB 模式对这张图片进行加密,结果如下所示:
3.3 加密模式——损坏的密文
首先新建一个长度至少是 64 字节的文本文件,输入vi /home/shiyanlou/corrupted.txt创建文件并输入四行i love shiyanlou作为内容。
然后对文本进行加密:
然后使用bless编辑器打开密文,文件中第 30 位是 EA,我们将其改为 FA,以制造损坏的密文。
然后再进行解密:
可以看到,这样得到的解密后的文件是有部分内容出现损坏的
3.4 填充
在 /home/shiyanlou 目录下新建一个文件 test20.txt,输入i love shiyanlou!!!
并对文件进行加密:
可以看出,加密文件的大小为 32 个字节,比原始文件多了 12 个字节。因为我使用了 AES-128-CBC,所以每个块都是 16 个字节。原始文件的内容是 20 个字节,所以第一个块是 16 个字节,第二个块是 4 个字节。第二个块不足 16 个字节,所以填充了 12 个字节。
3.5 使用 Openssl 加密库进行编程
首先下载我们的词典到 /home/shiyanlou 目录:
cd /home/shiyanlou
wget http://labfile.oss.aliyuncs.com/courses/241/words.txt
然后在 /home/shiyanlou 目录新建 fileKey.c 文件,并输入以下代码:
#include<openssl/conf.h>
#include<openssl/evp.h>
#include<openssl/err.h>
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#define True 1
#define False 0
void handleErrors(void)
{
ERR_print_errors_fp(stderr);
abort();
}
int encrypt(unsigned char *plaintext, int plaintext_len, unsigned char *key,
unsigned char *iv, unsigned char *ciphertext)
{
EVP_CIPHER_CTX *ctx;
int len;
int ciphertext_len;
if(!(ctx = EVP_CIPHER_CTX_new())) handleErrors();
if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv))
handleErrors();
if(1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len))
handleErrors();
ciphertext_len = len;
if(1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) handleErrors();
ciphertext_len += len;
EVP_CIPHER_CTX_free(ctx);
return ciphertext_len;
}
int append(char* buffer){
int length = (int)strlen(buffer);
if (length > 16)
return False;
memset(buffer+strlen(buffer),' ', 16-length);
buffer[16] = '\0';
return True;
}
int main(int argc, char const *argv[])
{
char buffer[50];
int i = 0;
char iv[17];
memset(iv, 0, 17);
unsigned char *plaintext = "This is a top secret.";
unsigned char ciphertext[100];
unsigned char *cryptotext="8d20e5056a8d24d0462ce74e4904c1b513e10d1df4a2ef2ad4540fae1ca0aaf9";
ERR_load_crypto_strings();
OpenSSL_add_all_algorithms();
OPENSSL_config(NULL);
int ciphertext_len;
FILE* fp = fopen("words.txt", "r");
while (fscanf(fp, "%s\n", buffer) != EOF){
if (!append(buffer))
continue;
ciphertext_len = encrypt(plaintext, strlen(plaintext), buffer, iv, ciphertext);
unsigned char cryptohex[50];
for (i = 0; i < ciphertext_len; i++)
{
sprintf(cryptohex+i*2,"%02x", ciphertext[i]);
}
cryptohex[ciphertext_len*2] = '\0';
if (0 == strcmp(cryptohex, cryptotext)){
printf("The key is: %s\n", buffer);
break;
}
}
EVP_cleanup();
ERR_free_strings();
return 0;
}
编译 fileKey.c:
gcc -I /usr/include/openssl -L /usr/lib/ssl -o enc fileKey.c -lcrypto -ldl
运行代码,结果如下所示:
3.6 生成伪随机数
计算机本身并不适合生成随机数,所以大多数系统通过物理资源获得随机性。比如 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
进行一定的时间或动作后再次运行上述指令,会发现结果产生变化:
Linux 将从物理世界得到的随机数据存于一个随机池中,再由两个设备把随机数据转化成伪随机数。这两个设备有着不同的行为,我们先来学习 /dev/random。
可以使用以下命令从 /dev/random 得到 16 字节的伪随机数,我们把数据 pipe 到 hexdump 中查看内容
head -c 16 /dev/random | hexdump
多次运行该命令,会发现命令发生阻塞,因为每次取出伪随机数都会导致随机池的熵减少,当熵用完的时候,设备就会被阻塞直到获得足够的熵。
使用以下命令从/dev/urandom 获得 1600 字节的伪随机数
head -c 1600 /dev/urandom | hexdump
/dev/random 与 /dev/urandom 都是从池中取出随机数据来生成伪随机数的。当熵不够用的时候,/dev/random 会阻塞,而 /dev/urandom 则会持续生成新的数。