使用 openssl 进行 AES 加解密
目录
在我之前的一篇博客里介绍了 对称加密的模式 . 这里主要聊一聊如何使用 openssl
来进行 AES 加密
.
一. OPENSSL crypto API
openssl 加密 API 分两个部分: High Level
and Low Level
. 对于大部分人来说,使用 High Level 就够用了, 这些 API 被冠以 EVP
(Envelope) ,表示对 Low Level 的封装。High Level API 提供了包括 对称/非对称加解密
, 签名
, 验证
, 哈希
,MAC
等一系列组件,屏蔽了 Low Level API 的复杂逻辑,使用起来安全高效。对于除非有需要进行加密算法级别的改进,否则不建议使用 Low Level API.
大部分 EVP API 有一个 int 型返回值 ,用来表示操作是否成功:1 表示成功, 0 表示失败。但有些时候也会返回 -1 ,表达如内存分配或者其他什么错误。官方指导代码如下:
1 2 3 4 5 6 7 |
if(1 != EVP_xxx()) goto err; if(1 != EVP_yyy()) goto err; /* ... do some stuff ... */ err: ERR_print_errors_fp(stderr); |
二. 使用 EVP API 进行 AES 加解密
对于 AES 加解密,EVP API 分为两种,EVP_Encrypt / EVP_Decryp
系列 和 EVP_Cipher
系列。后者是对前者进一步的封装。它们具体使用的套路都是一样的:
- 创建加解密上下文
EVP_CIPHER_CTX
- 调用
xxxInit()
函数,使用key(密钥)、iv(前置向量)
和cipher(算法)
对上下文初始化 - 调用
xxxUpdate()
函数进行加解密。该函数支持流式操作。即对于一段明文来说,分成多组按顺序进行加密,和一次性全部加密,不影响其生成的密文的正确性。 - 调用
xxxFinal()
函数获取上下文中遗留的信息 - 释放上下文, 完成加解密。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
#include <iostream> #include <cassert> #include <cmath> #include <openssl/aes.h> #include <openssl/evp.h> #include <openssl/kdf.h> const char *key = "wandoer.com_wandoer.com_wandoer."; char iv[16] = { '_','i','v','_','m','u','s','t','_','b','e','_','1','6','B','_' }; const char *plain_text = "The EVP interface supports the ability to perform authenticated encryption and decryption, as well as the option to attach unencrypted," " associated data to the message. Such Authenticated-Encryption with Associated-Data (AEAD) schemes provide confidentiality by encrypting the data, and also" " provide authenticity assurances by creating a MAC tag over the encrypted data. The MAC tag will ensure the data is not accidentally altered or maliciously" " tampered during transmission and storage."; template<typename CIPHER> bool encrypt(CIPHER cipher,const char* key, const char* iv, const unsigned char *in, int in_len, unsigned char *out, int *out_len) { bool ret = false; //创建加密上下文 EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); int tmp = 0; //初始化加密上下文 int rst = EVP_EncryptInit(ctx, cipher, reinterpret_cast<const unsigned char *>(key), reinterpret_cast<const unsigned char *>(iv)); if (rst != 1) goto RET; //加密操作 rst = EVP_EncryptUpdate(ctx, out, &tmp, in, in_len); if (rst != 1) goto RET; *out_len = tmp; //获取加密上下文中遗留的信息 rst = EVP_EncryptFinal(ctx, out + tmp, &tmp); if (rst != 1) goto RET; if (tmp > 0) { *out_len += tmp; } ret = true; RET: //释放加密上下文 EVP_CIPHER_CTX_free(ctx); ctx = NULL; return ret; } template<typename CIPHER> bool decrypt(CIPHER cipher, const char* key, const char* iv, const unsigned char *in, int in_len, unsigned char *out, int *out_len) { bool ret = false; EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); int tmp = 0; int rst = EVP_DecryptInit(ctx, cipher, reinterpret_cast<const unsigned char *>(key), reinterpret_cast<const unsigned char *>(iv)); if (rst != 1) goto RET; rst = EVP_DecryptUpdate(ctx, out, &tmp, in, in_len); if (rst != 1) goto RET; *out_len = tmp; rst = EVP_DecryptFinal(ctx, out + tmp, &tmp); if (rst != 1) goto RET; if (tmp > 0) { *out_len += tmp; } ret = true; RET: EVP_CIPHER_CTX_free(ctx); ctx = NULL; return ret; } template<typename CIPHER> bool do_crypto(CIPHER cipher, const char* key, const char* iv, const unsigned char* in, int in_len, unsigned char* out, int* out_len, bool encrypt) { bool ret = false; EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); int tmp = 0; int rst = EVP_CipherInit(ctx, cipher, reinterpret_cast<const unsigned char*>(key), reinterpret_cast<const unsigned char*>(iv), encrypt ? 1 : 0); if (rst != 1) goto RET; rst = EVP_CipherUpdate(ctx, out, &tmp, in, in_len); if (rst != 1) goto RET; *out_len = tmp; rst = EVP_CipherFinal(ctx, out + tmp, &tmp); if (rst != 1) goto RET; if (tmp > 0) { *out_len += tmp; } ret = true; RET: EVP_CIPHER_CTX_free(ctx); ctx = NULL; return ret; } int main(void) { //TODO init OPENSSL // EVP_CIPHER 是 OPENSSL 内置的一系列对称加密算法。可以使用 EVP_xxx_() 来获取,使用后无需释放。 // OPENSSL 具体提供哪些内置算法可以在 evp.h 里查看 const EVP_CIPHER *cipher = EVP_aes_256_cfb(); // 计算加解密结果 buffer 的大小。AES 对称加密后数据的大小与加密前相同。 // 考虑到可能有 padding 存在,大小为大于输入的,最小的 AES_BLOCK_SIZE 倍整数。 int out_len = ceil((float)strlen(plain_text) / AES_BLOCK_SIZE) * AES_BLOCK_SIZE; unsigned char *cipher_text = (unsigned char *)malloc(out_len); unsigned char *re_plain_text = (unsigned char *)malloc(out_len); //实际加解密输出的 buffer 大小 int enc_len = 0, dec_len = 0; /** Method 1 * 使用 EVP_Encrypt / EVP_Decrypt 系列函数 */ memset(cipher_text, 0, out_len); memset(re_plain_text, 0, out_len); bool encryped = encrypt(cipher,key,iv, reinterpret_cast<const unsigned char *>(plain_text), strlen(plain_text), cipher_text, &enc_len); assert(encryped); bool decrypted = decrypt(cipher,key, iv, cipher_text, enc_len, re_plain_text, &dec_len); assert(decrypted); if (dec_len < out_len) re_plain_text[dec_len] = '\0'; int cmp = strcmp(plain_text, reinterpret_cast<char *>(re_plain_text)); assert(cmp == 0); /** Method 2 * 使用 EVP_Cipher 系列函数 */ memset(cipher_text, 0, out_len); memset(re_plain_text, 0, out_len); encryped = do_crypto(cipher, key, iv, reinterpret_cast<const unsigned char*>(plain_text), strlen(plain_text), cipher_text, &enc_len, true); assert(encryped); decrypted = do_crypto(cipher, key, iv, cipher_text, enc_len, re_plain_text, &dec_len, false); assert(decrypted); cmp = strcmp(plain_text, reinterpret_cast<char*>(re_plain_text)); assert(cmp == 0); free(cipher_text); free(re_plain_text); //TODO free OPENSSL return 0; } |
需要注意的地方有:
- key 和 iv 的长度。我们知道 AES 一个组的大小是 16Byte, 所以 iv 的长度为
16Byte
。key 的长度随着选择的 cipher 的不同而不同,如 AES_128_CFB 表达 key 长度为 128 位, AES_256_CFB 表示 key 长度为 256 位。如果 key 或 iv 的长度过长,超出的部分不会被使用,反之如果过短,则结果不可预测。 如果不清楚 key 和 iv 到底需要多长,可以使用EVP_CIPHER_iv_length() / EVP_CIPHER_key_length()
来获取。 - 加解密上下文
EVP_CIPHER_CTX
要记得释放
,否则遗留在内存中的上下文信息可以会泄漏机密。
到这里,使用 OPENSSL 进行 AES 加解密已经基本讲完了。没错就是这么简单。如果想做更深一个层次的了解,可以看看以下部分。
三. 使用 Low Level API 进行 AES 加解密
我们知道,一个完整的 AES 分组加解密方法分为两个部分,一是 AES 部分, 二是分组模式部分。
- 在 OPENSSL 中,AES 算法部分在
aes.h
中,为AES_encrypt / AES_decrypt
。它是 AES 加解密的核心部分,每次处理 16 个字节(128位) 的数据加解密。 - 分组与模式算法在
modes.h
中(具体实现在openssl/crypto/modes
路径下) ,如CRYPTO_cfb128_encrypt
,它将数据分成每 16 字节一组,再将每一组/组与组之间进行不同的模式的计算
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
#include <iostream> #include <cassert> #include <openssl/aes.h> const char *key = "wandoer.com_wandoer.com_wandoer."; char iv[16] = { '_','i','v','_','m','u','s','t','_','b','e','_','1','6','B','_' }; const char *plain_text = "The EVP interface supports the ability to perform authenticated encryption and decryption, as well as the option to attach unencrypted," " associated data to the message. Such Authenticated-Encryption with Associated-Data (AEAD) schemes provide confidentiality by encrypting the data, and also" " provide authenticity assurances by creating a MAC tag over the encrypted data. The MAC tag will ensure the data is not accidentally altered or maliciously" " tampered during transmission and storage."; bool aes_256_crypto(const char* key, const char* iv, const unsigned char* in, unsigned char* out, int len, bool encrypt) { bool ret = false; AES_KEY aes_key; int num = 0; unsigned char* uiv = (unsigned char*)malloc(strlen(iv)); memcpy(uiv, iv, strlen(iv)); int rst = AES_set_encrypt_key(reinterpret_cast<const unsigned char*>(key), 256, &aes_key); if (rst != 0) goto ERR; AES_cfb128_encrypt(reinterpret_cast<const unsigned char*>(in), out, len, &aes_key, uiv, &num, encrypt ? 1 : 0); ret = true; ERR: free(uiv); return ret; } int main() { int len = strlen(plain_text); unsigned char* cipher_text = (unsigned char*)malloc(len); unsigned char* re_plain_text = (unsigned char*)malloc(len); memset(cipher_text, 0, len); memset(re_plain_text, 0, len); aes_256_crypto(key, iv, reinterpret_cast<const unsigned char*>(plain_text), cipher_text, len, true); aes_256_crypto(key, iv, cipher_text, re_plain_text, len, false); int cmp = memcmp(plain_text, re_plain_text, len); assert(cmp == 0); free(cipher_text); free(re_plain_text); return 0; } |
需要注意,在 AES_cfb128_encrypt()
函数中的参数 iv
是一个 non const
指针。这表示 iv 是会发生变化的。AES_cfb128_encrypt 同样支持流操作,在第二次及以后的操作中需要注意传入前一次操作之后的 iv(如果了解对称加密分组的原理,则不难理解这何要这样做)。