openssl之EVP系列之4---EVP_Encrypt系列函数详解(一)

openssl之EVP系列之4---EVP_Encrypt系列函数详解(一)
    ---根据openssl doc/crypto/EVP_EncryptInit.pod和doc/ssleay.txt cipher.doc部分翻译和自己的理解写成
    (作者:DragonKing, Mail: wzhah@263.net ,发布于: http://openssl.126.com 之openssl专业论坛,版本:openssl-0.9.7)
    EVP_Cipher系列包含了很多函数,我将他们大概分成两部分来介绍,一部分是基本函数系列,就是本文要介绍的,另一个部分是设置函数系列,将在后面的文章进行介绍。基本系列函数主要是进行基本的加密和解密操作的函数,他们的定义如下(openssl/evp.h):
     int EVP_CIPHER_CTX_init(EVP_CIPHER_CTX *a);
    
     int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type,
     ENGINE *impl, unsigned char *key, unsigned char *iv);
     int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out,
     int *outl, unsigned char *in, int inl);
     int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out,
     int *outl);
    
     int EVP_DecryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type,
     ENGINE *impl, unsigned char *key, unsigned char *iv);
     int EVP_DecryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out,
     int *outl, unsigned char *in, int inl);
     int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *outm,
     int *outl);
    
     int EVP_CipherInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type,
     ENGINE *impl, unsigned char *key, unsigned char *iv, int enc);
     int EVP_CipherUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out,
     int *outl, unsigned char *in, int inl);
     int EVP_CipherFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *outm,
     int *outl);
    
     int EVP_EncryptInit(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type,
     unsigned char *key, unsigned char *iv);
     int EVP_EncryptFinal(EVP_CIPHER_CTX *ctx, unsigned char *out,
     int *outl);
    
     int EVP_DecryptInit(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type,
     unsigned char *key, unsigned char *iv);
     int EVP_DecryptFinal(EVP_CIPHER_CTX *ctx, unsigned char *outm,
     int *outl);
    
     int EVP_CipherInit(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type,
     unsigned char *key, unsigned char *iv, int enc);
     int EVP_CipherFinal(EVP_CIPHER_CTX *ctx, unsigned char *outm,
     int *outl);
    
     int EVP_CIPHER_CTX_cleanup(EVP_CIPHER_CTX *a);
    其实在这里列出的函数虽然很多,但是大部分是功能重复的,有的是旧的版本支持的函数,新的版本中已经可以不再使用了。事实上,函数EVP_EncryptInit, EVP_EncryptFinal, EVP_DecryptInit, EVP_CipherInit以及EVP_CipherFinal在新代码中不应该继续使用,他们保留下来只是为了兼容以前的代码。在新的代码中,应该使用EVP_EncryptInit_ex、EVP_EncryptFinal_ex、EVP_DecryptInit_ex、EVP_DecryptFinal_ex、EVP_CipherInit_ex以及EVP_CipherFinal_ex函数,因为它们可以在每次调用完算法后,不用重新释放和分配已有EVP_CIPHER_CTX结构的内存的情况下重用该结构,方便很多。下面我们分别对这些函数进行介绍。
    【EVP_CIPHER_CTX_init】
    该函数初始化一个EVP_CIPHER_CTX结构体,只有初始化后该结构体才能在下面介绍的函数中使用。操作成功返回1,否则返回0。
    【EVP_EncryptInit_ex】
    该函数采用ENGINE参数impl的算法来设置并初始化加密结构体。其中,参数ctx必须在调用本函数之前已经进行了初始化。参数type通常通过函数类型来提供参数,如EVP_des_cbc函数的形式,即我们上一章中介绍的对称加密算法的类型。如果参数impl为NULL,那么就会使用缺省的实现算法。参数key是用来加密的对称密钥,iv参数是初始化向量(如果需要的话)。在算法中真正使用的密钥长度和初始化密钥长度是根据算法来决定的。在调用该函数进行初始化的时候,除了参数type之外,所有其它参数可以设置为NULL,留到以后调用其它函数的时候再提供,这时候参数type就设置为NULL就可以了。在缺省的加密参数不合适的时候,可以这样处理。操作成功返回1,否则返回0。
    【EVP_EncryptUpdate】
    该函数执行对数据的加密。该函数加密从参数in输入的长度为inl的数据,并将加密好的数据写入到参数out里面去。可以通过反复调用该函数来处理一个连续的数据块。写入到out的数据数量是由已经加密的数据的对齐关系决定的,理论上来说,从0到(inl+cipher_block_size-1)的任何一个数字都有可能(单位是字节),所以输出的参数out要有足够的空间存储数据。写入到out中的实际数据长度保存在outl参数中。操作成功返回1,否则返回0。
    【EVP_EncryptFinal_ex】
    该函数处理最后(Final)的一段数据。在函数在padding功能打开的时候(缺省)才有效,这时候,它将剩余的最后的所有数据进行加密处理。该算法使用标志的块padding方式(AKA PKCS padding)。加密后的数据写入到参数out里面,参数out的长度至少应该能够一个加密块。写入的数据长度信息输入到outl参数里面。该函数调用后,表示所有数据都加密完了,不应该再调用EVP_EncryptUpdate函数。如果没有设置padding功能,那么本函数不会加密任何数据,如果还有剩余的数据,那么就会返回错误信息,也就是说,这时候数据总长度不是块长度的整数倍。操作成功返回1,否则返回0。
    PKCS padding标准是这样定义的,在被加密的数据后面加上n个值为n的字节,使得加密后的数据长度为加密块长度的整数倍。无论在什么情况下,都是要加上padding的,也就是说,如果被加密的数据已经是块长度的整数倍,那么这时候n就应该等于块长度。比如,如果块长度是9,要加密的数据长度是11,那么5个值为5的字节就应该增加在数据的后面。
    【EVP_DecryptInit_ex, EVP_DecryptUpdate和EVP_DecryptFinal_ex】
    这三个函数是上面三个函数相应的解密函数。这些函数的参数要求基本上都跟上面相应的加密函数相同。如果padding功能打开了,EVP_DecryptFinal会检测最后一段数据的格式,如果格式不正确,该函数会返回错误代码。此外,如果打开了padding功能,EVP_DecryptUpdate函数的参数out的长度应该至少为(inl+cipher_block_size)字节;但是,如果加密块的长度为1,则其长度为inl字节就足够了。三个函数都是操作成功返回1,否则返回0。
    需要注意的是,虽然在padding功能开启的情况下,解密操作提供了错误检测功能,但是该功能并不能检测输入的数据或密钥是否正确,所以即便一个随机的数据块也可能无错的完成该函数的调用。如果padding功能关闭了,那么当解密数据长度是块长度的整数倍时,操作总是返回成功的结果。
    【EVP_CipherInit_ex, EVP_CipherUpdate和EVP_CipherFinal_ex】
    事实上,上面介绍的函数都是调用这三个函数实现的,它们是更底层的函数。完成了数据的加密和解密功能。他们根据参数enc决定执行加密还是解密操作,如果enc为1,则加密;如果enc为0,则解密;如果enc是-1,则不改变数据。三个函数都是操作成功返回1,否则返回0。
    【EVP_CIPHER_CTX_cleanup】
    该函数清除一个EVP_CIPHER_CTX结构中的所有信息并释放该结构占用的所有内存。在使用上述的函数完成一个加密算法过程后应该调用该函数,这样可以避免一些敏感信息遗留在内存造成安全隐犯。操作成功返回1,否则返回0。
    【EVP_EncryptInit, EVP_DecryptInit和EVP_CipherInit】
    这三个函数的功能分别跟函数EVP_EncryptInit_ex, EVP_DecryptInit_ex和EVP_CipherInit_ex功能相同,只是他们的ctx参数不需要进行初始化,并且使用缺省的算法库。三个函数都是操作成功返回1,否则返回0。
    【EVP_EncryptFinal, EVP_DecryptFinal和EVP_CipherFinal】
    这三个函数分别跟函数EVP_EncryptFinal_ex, EVP_DecryptFinal_ex以及EVP_CipherFinal_ex函数功能相同,不过,他们的参数ctx会在调用后自动释放。三个函数都是操作成功返回1,否则返回0。
  • 4
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
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--; 算法网友提供,测试正确。
OpenSSL中的EVP(Enveloped Data Processing)是一个高级API,提供了一种简单易用的方式来实现各种密码学算法EVP API支持对称加密、非对称加密、哈希、消息认证码(MAC)和数字签名等功能。 以下是一些使用EVP API的示例: 1. 对称加密 对称加密使用相同的密钥对数据进行加密解密。以下代码展示了如何使用EVP API进行对称加密: ```c EVP_CIPHER_CTX *ctx; ctx = EVP_CIPHER_CTX_new(); EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv); EVP_EncryptUpdate(ctx, out, &outl, in, inl); EVP_EncryptFinal_ex(ctx, out + outl, &tmpl); EVP_CIPHER_CTX_free(ctx); ``` 其中,key是加密密钥,iv是初始化向量,in是输入数据,inl是输入数据长度,out是输出数据,outl是输出数据长度,tmpl是临时变量。 2. 非对称加密 非对称加密使用公钥加密数据,私钥解密数据。以下代码展示了如何使用EVP API进行非对称加密: ```c EVP_PKEY *pkey; pkey = EVP_PKEY_new(); EVP_PKEY_set1_RSA(pkey, rsa); EVP_PKEY_CTX *ctx; ctx = EVP_PKEY_CTX_new(pkey, NULL); EVP_PKEY_encrypt_init(ctx); EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING); EVP_PKEY_encrypt(ctx, out, &outl, in, inl); EVP_PKEY_CTX_free(ctx); EVP_PKEY_free(pkey); ``` 其中,rsa是RSA密钥对,in是输入数据,inl是输入数据长度,out是输出数据,outl是输出数据长度。 3. 哈希 哈希函数将任意长度的数据映射为固定长度的摘要值。以下代码展示了如何使用EVP API进行哈希: ```c EVP_MD_CTX *ctx; ctx = EVP_MD_CTX_new(); EVP_DigestInit_ex(ctx, EVP_sha256(), NULL); EVP_DigestUpdate(ctx, in, inl); EVP_DigestFinal_ex(ctx, out, &outl); EVP_MD_CTX_free(ctx); ``` 其中,in是输入数据,inl是输入数据长度,out是输出数据,outl是输出数据长度。 4. MAC MAC是一种用于验证消息完整性和真实性的技术。以下代码展示了如何使用EVP API进行MAC: ```c EVP_MD_CTX *ctx; ctx = EVP_MD_CTX_new(); EVP_DigestSignInit(ctx, NULL, EVP_sha256(), NULL, pkey); EVP_DigestSignUpdate(ctx, in, inl); EVP_DigestSignFinal(ctx, out, &outl); EVP_MD_CTX_free(ctx); ``` 其中,pkey是私钥,in是输入数据,inl是输入数据长度,out是输出数据,outl是输出数据长度。 5. 数字签名 数字签名是一种用于验证消息来源和完整性的技术。以下代码展示了如何使用EVP API进行数字签名: ```c EVP_MD_CTX *ctx; ctx = EVP_MD_CTX_new(); EVP_DigestSignInit(ctx, NULL, EVP_sha256(), NULL, pkey); EVP_DigestSignUpdate(ctx, in, inl); EVP_DigestSignFinal(ctx, NULL, &slen); EVP_SignFinal(ctx, out, &outl, pkey); EVP_MD_CTX_free(ctx); ``` 其中,pkey是私钥,in是输入数据,inl是输入数据长度,out是输出数据,outl是输出数据长度,slen是临时变量。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值