Linux下使用openssl库编程实现对称加密解密

时间:2021-10-13 18:31:28

一、背景

需求是对文件内容加密解密,对称秘钥方式有DES、3DES等方法,这样加密方与解密方可以双方约定好秘钥就能完成文件的安全传输;

二、相关知识

2.1 对称加密算法DES

概念:DES数据加解密就是将数据按照8个字节为单位进行DES加密或解密得到一段8个字节的密文或者明文,不足8个字节的以0x00或0xFF进行补齐
应用场景:DES算法在POS、ATM、磁卡及智能卡(IC卡)、加油站、高速公路收费站等领域被广泛应用,以此来实现关键数据的保密,如信用卡持卡人的PIN的加密传输,IC卡与POS间的双向认证、金融交易数据包的MAC校验等,均用到DES算法。


2.2 ECB、CBC、CFB、OFB模式的区别

  1. ECB:Electronic Code Book,电子密码本模式,最基本的加密模式,也就是通常理解的加密,相同的明文将永远加密成相同的密文,无初始向量,容易受到密码本重放攻击,一般情况下很少用;关键在于各段分组数据是独立计算的,加密的最小单位仍然是8个字节;
  2. CBC:Cipher Block Chaining,密码分组链接,明文被加密前要与前面的密文进行异或运算后再加密,因此只要选择不同的初始向量,相同的密文加密后会形成不同的密文,这是目前应用最广泛的模式。CBC加密后的密文是上下文相关的,但明文的错误不会传递到后续分组,但如果一个分组丢失,后面的分组将全部作废(同步错误)。CBC则是对各段分组数据之间有所关联,可以理解为比ECB多了异或运算的步骤;
  3. CFB:Cipher FeedBack,密码反馈,类似于自同步序列密码,分组加密后,按8位分组将密文和明文进行移位异或后得到输出同时反馈回移位寄存器,优点最小可以按字节进行加解密,也可以是n位的,CFB也是上下文相关的,CFB模式下,明文的一个错误会影响后面的密文(错误扩散)。
  4. OFB:Output Feedback,输出反馈,将分组密码作为同步序列密码运行,和CFB相似,不过OFB用的是前一个n位密文输出分组反馈回移位寄存器,OFB没有错误扩散问题。

2.3 3DES算法

3DES算法顾名思义就是3次DES算法,其算法原理如下:设Ek()和Dk()代表DES算法的加密和解密过程,K代表DES算法使用的密钥,P代表明文,C代表密表;

3DES加密过程为:C=Ek3(Dk2(Ek1(P)))
3DES解密过程为:P=Dk1((EK2(Dk3(C)))

这里可以K1=K3,但不能K1=K2=K3(如果相等的话就成了DES算法了);key长度支持24位、16位;

所以,3DES-CBC 流程基本与DES-CBC一样,多了两层加密解密来提供强度的支撑;

2.4 OpenSSL算法库

上述三个小节简单介绍了加解密的原理,这一个小节对编程方法进行介绍;
OpenSSL是一个开源的SSL实现,其中集成了多种加密算法。DES、3DES算法在里面均有实现,对于算法的使用主要就是考虑: 加密模式、秘钥长度、补齐方式
而且针对算法的应用性,OpenSSL实现了一个通用框架EVP接口,通过使用EVP我们就能够很方便地完成加密解密功能;

EVP主要支持以下算法:
  1. 实现了base64编解码BIO;
  2. 实现了加解密BIO;
  3. 实现了摘要BIO;
  4. 实现了reliable BIO;
  5. 封装了摘要算法;
  6. 封装了对称加解密算法;
  7. 封装了非对称密钥的加密(公钥)、解密(私钥)、签名与验证以及辅助函数;
  8. 基于口令的加密(PBE);
  9. 对称密钥处理;
  10. 数字信封:数字信封用对方的公钥加密对称密钥,数据则用此对称密钥加密
  11. 其他辅助函数。

以上是对一些资料[1][2][3]进行整合,感谢原作者分享;

三、实现

使用到EVP主要函数如下:
  1. EVP_CIPHER_CTX_init // 初始化对称计算上下文。
  2. EVP_CIPHER_CTX_cleanup // 清除对称算法上下文数据,它调用用户提供的销毁函数销清除存中的内部密钥以及其他数据。
  3. EVP_des_ede3_ecb // 返回一个EVP_CIPHER;
  4. EVP_EncryptInit和EVP_EncryptInit_ex // 加密初始化函数,本函数调用具体算法的init回调函数,将外送密钥key转换为内部密钥形式,将初始化向量iv拷贝到ctx结构中。
  5. EVP_EncryptUpdate // 加密函数,用于多次计算,它调用了具体算法的do_cipher回调函数。
  6. EVP_EncryptFinal和EVP_EncryptFinal_ex // 获取加密结果,函数可能涉及填充,它调用了具体算法的do_cipher回调函数。
  7. EVP_DecryptInit和EVP_DecryptInit_ex // 解密初始化函数。
  8. EVP_DecryptUpdate // 解密函数,用于多次计算,它调用了具体算法的do_cipher回调函数。
  9. EVP_DecryptFinal和EVP_DecryptFinal_ex // 获取解密结果,函数可能涉及去填充,它调用了具体算法的do_cipher回调函数。
加密文件内容,把cipher类型作为输入参数交给了外部控制,函数中先把秘钥、向量、文件内容写成固定的形式;
int do_encrypt(const EVP_CIPHER *type, const char *ctype)
{
unsigned char outbuf[1024];
int outlen, tmplen;
unsigned char key[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23};
unsigned char iv[] = {1,2,3,4,5,6,7,8};
char intext[] = "Helloworld";
EVP_CIPHER_CTX ctx;
FILE *out;
EVP_CIPHER_CTX_init(&ctx);
EVP_EncryptInit_ex(&ctx, type, NULL, key, iv);

if(!EVP_EncryptUpdate(&ctx, outbuf, &outlen, intext, (int)strlen(intext))) {
printf("EVP_EncryptUpdate\n");
return FAILURE;
}

if(!EVP_EncryptFinal_ex(&ctx, outbuf + outlen, &tmplen)) {
printf("EVP_EncryptFinal_ex\n");
return FAILURE;
}

outlen += tmplen;
EVP_CIPHER_CTX_cleanup(&ctx);

out = fopen("cipher.bin", "wb");
fwrite(outbuf, 1, outlen, out);
fflush(out);
fclose(out);
    return SUCCESS;
}


解密则是逆过程,实现流程差不多
int do_decrypt(const EVP_CIPHER *type, const char *ctype)
{
unsigned char inbuf[1024] = {0};
unsigned char outbuf[1024]= {0};
int outlen, inlen, tmplen;
unsigned char key[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23};
unsigned char iv[] = {1,2,3,4,5,6,7,8};

EVP_CIPHER_CTX ctx;
FILE *in = NULL;
EVP_CIPHER_CTX_init(&ctx);
EVP_DecryptInit_ex(&ctx, type, NULL, key, iv);

in = fopen("cipher.bin", "r");
inlen = fread(inbuf, 1, sizeof(inbuf), in);
fclose(in);

printf("Readlen: %d\n", inlen);
if(!EVP_DecryptUpdate(&ctx, outbuf, &outlen, inbuf, inlen)) {
printf("EVP_DecryptUpdate\n");
return FAILURE;
}

if(!EVP_DecryptFinal_ex(&ctx, outbuf + outlen, &tmplen)) {
printf("EVP_DecryptFinal_ex\n");
return FAILURE;
}

outlen += tmplen;
EVP_CIPHER_CTX_cleanup(&ctx);

printf("Result: \n%s\n", outbuf);

return SUCCESS;
}
然后是主函数进行测试
int main(int argc, char *argv[])
{
do_encrypt(EVP_des_cbc(), "des-cbc");
do_decrypt(EVP_des_cbc(), "des-cbc");

do_encrypt(EVP_des_ede_cbc(), "des-ede-cbc");
do_decrypt(EVP_des_ede_cbc(), "des-ede-cbc");

do_encrypt(EVP_des_ede3_cbc(), "des-ede3-cbc");
do_decrypt(EVP_des_ede3_cbc(), "des-ede3-cbc");

return 0;
}
结果表明测试方法正确,EVP接口在加密解密过程中会自动扩充、还原数据长度;
注意的地方是文本内容较少,只需一次IO就完成文件写入、读取,大文件内容的处理还需要加上循环;

四、总结

3DES是加强版的对称加密算法,同时CBC工作模式是最广泛使用的模式,他能够保证加密后的数据块是上下文关联的,不容易受到重放攻击;
编程上使用OpenSSL的EVP接口集能够比较方便地完成加密解密过程,并且不用过多考虑字节补齐的问题,EVP加解密过程中会自动扩充、还原数据长度;


参考文章:
[1] http://www.w2bc.com/article/100696

[2] http://blog.csdn.net/ywq1981/article/details/4669490

[3] http://blog.chinaunix.net/uid-127037-id-2919487.html