密码工程学习笔记2
知识点归纳
最有收获的内容
知道了怎么选择工作模式
-
坚决不用ECB
-
通常是 CBC 和 CTR 二选一
-
如果能较好地生成唯一的瞬时值,推荐使用CTR模式,不存在碰撞,消息泄露最少
-
否则还是用CBC的随机IV模式,不好的瞬时值将产生严重的问题
碰撞与密文分析
-
由于明文是结构化的,所以容易猜测通信数据中,哪些密文对应的明文是相同的。
-
CBC模式一旦出现了一个分组的碰撞,就会泄露一个密文分组的明文。
-
CTR模式没有碰撞问题,因为CTR模式下,任意两个密钥分组都不相同,所以从两个相同的明文或者相同的密文中不能获得更多的信息。
遇到的问题与解决过程
Q1:如何攻击分组密码?具体攻击手段?
《分组密码的攻击方法与实例分析》
- 差分密码分析
- 线性密码分析
- 高阶差分密码分析
- 截断差分密码分析
- 不可能差分密码分析
- 积分攻击
- 插值攻击
- 相关密钥攻击
Q2:iv是写在文件的头部,还是应该和密钥一样保护起来?
(一)iv是否有保护的必要?
-
没有保护的必要。
-
总:iv相当于在所有密文分组前,加了一个密文分组。其效力等同于一个密文分组。如果要保护iv,那就要保护所有的密文分组。
-
即使攻击者知道了第一个密文分组是iv,在没有密钥的情况下,依旧无法解密出第一个明文分组。
-
是否会泄露信息?也不会。iv 和 P1(第一个明文分组) 异或之后,被密钥加密,iv不会泄露对破译有用的信息。
-
对接收者来说,iv只会影响到C1(第一个密文分组)的破译,对其他密文分组的破译没有影响。
(二)iv是否会泄露工作模式?
-
如果将iv放在密文的首部,那么密文会比明文多16字节
-
在攻击方不知道明文长度的情况下,不会泄露工作模式
-
即便工作模式泄露,也没有恶劣影响
Q3:在SM4的OFB,CFB,CTR模式中,iv初始化向量有什么用?
- OFB模式中,iv初始化向量 + key + 函数 ==> 第一个流密码 ==> 加密第一组明文
- 之后,第一个流密码 + key + 函数 ==> 第二个流密码 ==> 加密第二组明文
- CFB模式中,iv初始化向量等同于第0个密文分组,用于第一组明文的加密,生成第一组密文
- 类似于CBC
- CTR模式中,iv初始化向量用于辅助产生好的瞬时值,避免瞬时值出现重复,或者可以被预测
- 不好的瞬时值将造成恶劣的影响
实践内容
使用OpenSSL实现SM4加解密文件
可以切换5种工作模式,ECB、CBC随机iv模式、OFB、CFB、CTR
#include "SM4.h"
void hex_str_to_byte(const char *in, int len, unsigned char *out)
{
char *str = (char *)malloc(len);
memset(str, 0, len);
memcpy(str, in, len);
for (int i = 0; i < len; i+=2){
//小写转大写
if(str[i] >= 'a' && str[i] <= 'f'){
str[i] = str[i] - 0x20;
}
if(str[i+1] >= 'a' && str[i] <= 'f'){
str[i+1] = str[i+1] - 0x20;
}
//处理前4bit
if(str[i] >= 'A' && str[i] <= 'F'){
out[i/2] = (str[i]-'A'+10)<<4;
}else{
out[i/2] = (str[i] & ~0x30)<<4;
}
//处理后4bit, 并组合起来
if(str[i+1] >= 'A' && str[i+1] <= 'F'){
out[i/2] |= (str[i+1]-'A'+10);
}else{
out[i/2] |= (str[i+1] & ~0x30);
}
}
free(str);
}
//输入对称加密算法 和 加密模式
//输出密文到文件
int SM4_encrypt(const EVP_CIPHER *type,const char *filename,const char *outfilename)
{
FILE *fp1 = fopen(filename,"rb");
FILE *fp2 = fopen(outfilename,"wb");
//初始化
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
EVP_CIPHER_CTX_init(ctx);
EVP_EncryptInit_ex(ctx,type,NULL,NULL,NULL);
//随机生成密钥
EVP_CIPHER_CTX_rand_key(ctx,SM4Key);
//判断模式
unsigned char iv[IV_LEN];
if(type != EVP_sm4_ecb()){
//随机iv模式
EVP_CIPHER_CTX_rand_key(ctx,iv);
EVP_EncryptInit_ex(ctx,type,NULL,SM4Key,iv);
//先在密文头部写入16字节的iv
fwrite(iv,1,IV_LEN,fp2);
#ifdef DEBUG
printf("iv: ");
for(int i=0;i<IV_LEN;i++){
printf("%02x",iv[i]);
}
printf("\n");
#endif
}else{
EVP_EncryptInit_ex(ctx,type,NULL,SM4Key,NULL);
}
//读取明文,写入密文
unsigned char inbuf[1024] = {0};
unsigned char outbuf[4096] = {0};
int outlen;
int inlen = 0;
while((inlen = fread(inbuf,1,sizeof(inbuf),fp1))){
EVP_EncryptUpdate(ctx,outbuf,&outlen,inbuf,inlen);
fwrite(outbuf,1,outlen,fp2);
fflush(fp2);
}
int finlen;
EVP_EncryptFinal_ex(ctx,outbuf,&finlen);
fwrite(outbuf,1,finlen,fp2);
fflush(fp2);
fclose(fp1);
fclose(fp2);
EVP_CIPHER_CTX_cleanup(ctx);
return 1;
}
int SM4_decrypt(const EVP_CIPHER *type,const char *filename,const char *outfilename)
{
FILE *fp1 = fopen(filename,"rb");
FILE *fp2 = fopen(outfilename,"wb");
//上下文初始化
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
EVP_CIPHER_CTX_init(ctx);
//读出iv,解密初始化
unsigned char iv[IV_LEN];
if(type != EVP_sm4_ecb()){
fread(iv,1,IV_LEN,fp1);
EVP_DecryptInit_ex(ctx,type,NULL,SM4Key,iv);
}else{
EVP_DecryptInit_ex(ctx,type,NULL,SM4Key,NULL);
}
//读写输入文件,输出文件
unsigned char inbuf[1024] = {0};
unsigned char outbuf[4096] = {0};
int outlen;
int inlen = 0;
while((inlen = fread(inbuf,1,sizeof(inbuf),fp1))){
// printf("inlen = %d\n",inlen);
EVP_DecryptUpdate(ctx,outbuf,&outlen,inbuf,inlen);
fwrite(outbuf,1,outlen,fp2);
fflush(fp2);
}
int finlen;
EVP_DecryptFinal_ex(ctx,outbuf,&finlen);
fwrite(outbuf,1,finlen,fp2);
fflush(fp2);
fclose(fp1);
fclose(fp2);
EVP_CIPHER_CTX_cleanup(ctx);
return 1;
}
int main(int argc,const char* argv[])
{
if(strcmp(argv[2],"-d")==0){
//解密 ./a.out cbc -d -K aaaaaaaaaaaaaa 1.enc 1.jpg
hex_str_to_byte(argv[4],KEY_LEN*2,SM4Key);
if(strcmp(argv[1],"cbc")==0){
SM4_decrypt(EVP_sm4_cbc(),argv[5],argv[6]);
}else if(strcmp(argv[1],"ecb")==0){
SM4_decrypt(EVP_sm4_ecb(),argv[5],argv[6]);
}else if(strcmp(argv[1],"cfb")==0){
SM4_decrypt(EVP_sm4_cfb(),argv[5],argv[6]);
}else if(strcmp(argv[1],"ofb")==0){
SM4_decrypt(EVP_sm4_ofb(),argv[5],argv[6]);
}else if(strcmp(argv[1],"ctr")==0){
SM4_decrypt(EVP_sm4_ctr(),argv[5],argv[6]);
}else{
printf("usage: ./a.out cbc -d -K aaaaaaaaaaaaaa infile outfile");
printf("没有%s解密模式\n",argv[1]);
return 1;
}
}else{
//加密 ./a.out cbc 1.jpg 2.jpg
if(strcmp(argv[1],"cbc")==0){
SM4_encrypt(EVP_sm4_cbc(),argv[2],argv[3]);
}else if(strcmp(argv[1],"ecb")==0){
SM4_encrypt(EVP_sm4_ecb(),argv[2],argv[3]);
}else if(strcmp(argv[1],"cfb")==0){
SM4_encrypt(EVP_sm4_cfb(),argv[2],argv[3]);
}else if(strcmp(argv[1],"ofb")==0){
SM4_encrypt(EVP_sm4_ofb(),argv[2],argv[3]);
}else if(strcmp(argv[1],"ctr")==0){
SM4_encrypt(EVP_sm4_ctr(),argv[2],argv[3]);
}else{
printf("usage: ./a.out cbc infile outfile");
printf("没有%s加密模式\n",argv[1]);
return 2;
}
}
#ifdef DEBUG
printf("key: ");
for(int i=0;i<KEY_LEN;i++){
printf("%02x",SM4Key[i]);
}
printf("\n");
#endif
return 0;
}
运行结果:ECB模式
gcc SM4_ED.c -lcrypto -DDEBUG #如果不想输出随机KEY的值,关闭Debug
./a.out ecb 1.jpg 2.jpg #KEY = 5f22c2023ef43dfae0789870559d50ea
openssl sm4-ecb -K 5f22c2023ef43dfae0789870559d50ea -in 1.jpg -out 1.jpg.enc
diff 1.jpg.enc 2.jpg #与openssl命令的结果一致
./a.out ecb -d -K 5f22c2023ef43dfae0789870559d50ea 2.jpg 3.jpg #解密2.jpg 为 3.jpg
diff 1.jpg 3.jpg #解密结果和明文相同
运行结果:CBC模式
./a.out cbc 1.jpg 2.jpg
iv: 528d0855ec87cb539b9c359fa5cb005f
key: ee91523c69530f49069706c83601ee04
diff 2.jpg 1.jpg.enc #由于a.out将iv写入了密文首部,而openssl命令没有写入iv,所以两种密文不同
xxd 2.jpg | head -n 5
xxd 1.jpg.enc | head -n 5
openssl sm4-cbc -K ee91523c69530f49069706c83601ee04 -iv 528d0855ec87cb539b9c359fa5cb005f -in 1.jpg -out 1.jpg.enc
./a.out cbc -d -K ee91523c69530f49069706c83601ee04 2.jpg 3.jpg
diff 1.jpg 3.jpg #解密结果和明文相同
运行结果:OFB模式
运行结果:CFB模式
运行结果:CTR模式
微精通
《Windows C/C++ 加解密实战》
Linux man -k命令 grep -nr命令