openssl之EVP系列之6---EVP_Encrypt系列函数编程架构及样例

openssl之EVP系列之6---EVP_Encrypt系列函数编程架构及样例
    ---依据openssl doc/crypto/EVP_EncryptInit.pod和doc/ssleay.txt cipher.doc部分翻译和自己的理解写成
 作者:DragonKing, Mail: wzhah@263.net ,

公布于:http://openssl.126.com 

版本号:openssl-0.9.7
    在前面的两篇文章,已经对EVP_Encrypt*...*系列函数做了具体的介绍,本章将说明该系列函数通用的应用架构,并举几个函数应用样例。
    【应用架构】
    一般来说。EVP_Encrypt*...*系列函数的应用架构例如以下所描写叙述(如果加密算法为3DES):
    1.定义一些必须的变量
    
    char key[EVP_MAX_KEY_LENGTH];
    char iv[EVP_MAX_IV_LENGTH];
    EVP_CIPHER_CTX ctx;
    unsigned char out[512+8];
    int outl;
    
    2.给变量key和iv赋值,这里使用了函数EVP_BytesToKey。该函数从输入password产生了密钥key和初始化向量iv,该函数将在后面做介绍。假设能够有别的办法设定key和iv。该函数的调用不是必须的
    EVP_BytesToKey(EVP_des_ede3_cbc,EVP_md5,NULL,passwd,strlen(passwd),key,iv);
    
    3.初始加密算法结构EVP_CIPHER_CTX
    EVP_EncryptInit_ex(&ctx, EVP_des_ede3_cbc(), NULL, key, iv);
    
    4.进行数据的加密操作
    while (....)
    {
     EVP_EncryptUpdate(ctx,out,&outl,in,512);
    }
    一般来说採用了循环的结构进行处理,每次循环加密数据为512字节,密文输出到out,out和int应该是指向不同样的内存的。
    
    5.结束加密,输出最后的一段512字节的数据
    EVP_EncryptFinal_ex(&ctx, out, &outl)
    该函数会进行加密的检測,假设加密过程有误,通常会检查出来。


    
    说明:加密跟上述过程是一样的,仅仅只是要使用EVP_Decrypt*...*系列函数。


    
    【样例】
    1.取得算法RC5使用的循环次数(round)
     int nrounds;
     EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GET_RC5_ROUNDS, 0, &nrounds);
    2.取得算法RC2的有效密钥长度
     int key_bits;
     EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GET_RC2_KEY_BITS, 0, &key_bits);
    3.设置算法RC5使用的循环次数(round)
     int nrounds;
     EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_SET_RC5_ROUNDS, nrounds, NULL);
    4.设置算法RC2的有效密钥长度
     int key_bits;
     EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_SET_RC2_KEY_BITS, key_bits, NULL);
    5.使用Blowfish算法加密一个字符串
     int do_crypt(char *outfile)
     {
     unsigned char outbuf[1024];
     int outlen, tmplen;
    
     //事实上key和iv一般应该从别的地方得到,这里固定了至少作为演示使用的
     unsigned char key[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
     unsigned char iv[] = {1,2,3,4,5,6,7,8};
     char intext[] = "Some Crypto Text";
     EVP_CIPHER_CTX ctx;
     FILE *out;
     EVP_CIPHER_CTX_init(&ctx);
     EVP_EncryptInit_ex(&ctx, EVP_bf_cbc(), NULL, key, iv);
    
     if(!EVP_EncryptUpdate(&ctx, outbuf, &outlen, intext, strlen(intext)))
     {
     /* 出错处理*/
     return 0;
     }
    
     //注意,传入给以下函数的输出缓存參数必须注意不能覆盖了原来的加密输出的数据
     if(!EVP_EncryptFinal_ex(&ctx, outbuf + outlen, &tmplen))
     {
     /* 出错处理*/
     return 0;
     }
     outlen += tmplen;
     EVP_CIPHER_CTX_cleanup(&ctx);
     //注意,保存到文件的时候要使用二进制模式打开文件,由于密文数据是二进制的,并且。不能採用strlen函数,由于密文字符串不是以NULL(0)为结束的字符串
     out = fopen(outfile, "wb");
     fwrite(outbuf, 1, outlen, out);
     fclose(out);
     return 1;
     }
    上面举的样例加密的密文能够使用openssl提供的应用程序cipher.exe来解密,命令例如以下:
     openssl bf -in cipher.bin -K 000102030405060708090A0B0C0D0E0F -iv 0102030405060708 -d
    
    6.使用文件I/O和80位密钥RC2算法的通用加密解密函数实例。该函数能够进行加密,也能够进行解密,由參数do_encrypt决定。该參数为1时则运行加密,为0时则运行解密。
     int do_crypt(FILE *in, FILE *out, int do_encrypt)
     {
     /*为输出缓冲设置足够的附加空间*/
     inbuf[1024], outbuf[1024 + EVP_MAX_BLOCK_LENGTH];
     int inlen, outlen;
    
     //事实上key和iv一般应该从别的地方得到。这里固定了至少作为演示使用的
     unsigned char key[] = "0123456789";
     unsigned char iv[] = "12345678";
     //这时候不进行key和IV的设置,由于我们还要改动參数
     EVP_CIPHER_CTX_init(&ctx);
     EVP_CipherInit_ex(&ctx, EVP_rc2(), NULL, NULL, NULL, do_encrypt);
     EVP_CIPHER_CTX_set_key_length(&ctx, 10);
     //完毕參数设置。进行key和IV的设置
     EVP_CipherInit_ex(&ctx, NULL, NULL, key, iv, do_encrypt);
    
     for(;;) 
     {
     inlen = fread(inbuf, 1, 1024, in);
     if(inlen <= 0) break;
     if(!EVP_CipherUpdate(&ctx, outbuf, &outlen, inbuf, inlen))
     {
     /*出错处理 */
     return 0;
     }
     fwrite(outbuf, 1, outlen, out);
     }
     if(!EVP_CipherFinal_ex(&ctx, outbuf, &outlen))
     {
     /* 出错处理*/
     return 0;
     }
     fwrite(outbuf, 1, outlen, out);
     EVP_CIPHER_CTX_cleanup(&ctx);
     return 1;
     }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
0、此程调试环境 运行uname -a的结果如下: Linux freescale 3.0.35-2666-gbdde708-g6f31253 #1 SMP PREEMPT Thu Nov 30 15:45:33 CST 2017 armv7l GNU/Linux 简称2017 armv7l GNU/Linux 1、openssl 直接处理AES的API 在openssl/aes.h定义。是基本的AES库函数接口,可以直接调用,但是那个接口是没有填充的。而如果要与Java通信,必须要有填充模式。所以看第2条。 2、利用openssl EVP接口 在openssl/evp.h定义。在这个接口提供的AES是默认是pkcs5padding方式填充方案。 3、注意openssl新老版本的区别 看如下这段 One of the primary differences between master (OpenSSL 1.1.0) and the 1.0.2 version is that many types have been made opaque, i.e. applications are no longer allowed to look inside the internals of the structures. The biggest impact on applications is that: 1)You cannot instantiate these structures directly on the stack. So instead of: EVP_CIPHER_CTX ctx; you must instead do: EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); .... EVP_CIPHER_CTX_free(ctx); 2)You must only use the provided accessor functions to access the internals of the structure. 4、注意加密的内容是数据不限制是否为字符串 openssl接口加密的是数据,不限制是否为字符串,我看到有些人在加密时使用strlen(),来获取要加密的长度,如果是对字符串加密的话没有问题,如果不是字符串的话,用它获取的长度是到第一个0处,因为这个函数获取的是字符串长度,字符串是以零为终止的。 5、在调用EVP_EncryptFinal_ex时不要画蛇添足 正常加解密处理过程,引用网友的代码如下,经测试正确。 int kk_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; ctx = EVP_CIPHER_CTX_new(); EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv); //EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, iv); EVP_EncryptUpdate(ctx, ciphertext, &len;, plaintext, plaintext_len); ciphertext_len = len; EVP_EncryptFinal_ex(ctx, ciphertext + len, &len;); ciphertext_len += len; EVP_CIPHER_CTX_free(ctx); return ciphertext_len; } int kk_decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *key, unsigned char *iv, unsigned char *plaintext) { EVP_CIPHER_CTX *ctx; int len; int plaintext_len; ctx = EVP_CIPHER_CTX_new(); EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv); //EVP_DecryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, iv); EVP_DecryptUpdate(ctx, plaintext, &len;, ciphertext, ciphertext_len); plaintext_len = len; EVP_DecryptFinal_ex(ctx, plaintext + len, &len;); plaintext_len += len; EVP_CIPHER_CTX_free(ctx); return plaintext_len; } 我看到有人提供的代码在加密长度正好是16字节的整数倍时特意不去调用EVP_EncryptFinal_ex,这实在是画蛇添足啊,不论什么情况下,最后一定要调用EVP_EncryptFinal_ex一次,然后结束加密过程。 6、Base64陷阱 如果用到了base64,要注意如下: 1)base64算法是将3个字节变成4个可显示字符。所以在如果数据长度不是3字节对齐时,会补0凑齐。 2)在解密时先要解base64,再解AES。在解base64后,要减掉补上的0。算法就去查看base64后的字符串尾处有几个=号,最多是2个,如果正好要加密的数据是3的倍数,不需要补0,那么base64后的数据尾处就没有=,如果补了1个0,就有一个=号。 算法如下: int encode_str_size = EVP_EncodeBlock(base64, en, el); int length = EVP_DecodeBlock(base64_out, base64, encode_str_size ); //EVP_DecodeBlock内部同样调用EVP_DecodeInit + EVP_DecodeUpdate + Evp_DecodeFinal实现,但是并未处理尾部的'='字符,因此结果字符串长度总是为3的倍数 while(base64[--encode_str_size] == '=') length--; 算法网友提供,测试正确。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值