使用 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 ,表达如内存分配或者其他什么错误。官方指导代码如下:
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()函数获取上下文中遗留的信息 - 释放上下文, 完成加解密。
示例代码:
#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 字节一组,再将每一组/组与组之间进行不同的模式的计算
示例代码:
#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(如果了解对称加密分组的原理,则不难理解这何要这样做)。