以 aes-cbc-128, PKCS7 填充方式为例。
以下为使用两种不同api的实现相同加解密的代码:
int AesDecrypt(const std::string& sessionkey, const std::string& encrypted_data, const std::string& iv, std::string &plaintext)
{
// set decrypt key
AES_KEY aes_key;
//设置秘钥位数,只能为 128,192,256; 若key_bits为256,也可以设置key_bits为128,此时秘钥只使用了前 (128/8 = 16)位
const int key_bits = 8 * sessionkey.size();
ret = AES_set_decrypt_key((const unsigned char*)sessionkey.c_str(), key_bits, &aes_key);
if (ret != 0)
{
ERROR("AES_set_decrypt_key failed, ret: %d, key: %s, key_bits: %d", ret, sessionkey.c_str(), key_bits);
return -2;
}
// decrypt
plaintext.resize(encrypted_data.size() + AES_BLOCK_SIZE);// assign decrypt block
AES_cbc_encrypt((const unsigned char*)encrypted_data.c_str(), (unsigned char*)plaintext.c_str(), encrypted_data.size(), &aes_key, (unsigned char*)iv.c_str(), AES_DECRYPT);// 加密:AES_ENCRYPT,解密:AES_DECRYPT
return 0;
}
/***
* @brief aes-128-cbc加解密 pkcs#7填充;
* do_encrypt = 1 加密; do_encrypt = 0 解密
*/
int AesDecrypt(const string &iv, const string &key, const string& cipherText, string& plainText, int do_encrypt)
{
/* Allow enough space in output buffer for additional block */
unsigned char outbuf[1024 + EVP_MAX_BLOCK_LENGTH] = {0};
int outlen;
EVP_CIPHER_CTX *ctx;
/* Don't set key or IV right away; we want to check lengths */
ctx = EVP_CIPHER_CTX_new();
EVP_CipherInit_ex(ctx, EVP_aes_128_cbc(), NULL, NULL, NULL,
do_encrypt);
OPENSSL_assert(EVP_CIPHER_CTX_key_length(ctx) == 16);
OPENSSL_assert(EVP_CIPHER_CTX_iv_length(ctx) == 16);
/* Now we can set key and IV */
EVP_CipherInit_ex(ctx, NULL, NULL, reinterpret_cast<unsigned const char*>(key.c_str()), reinterpret_cast<unsigned const char*>(iv.c_str()), do_encrypt);
EVP_CipherUpdate(ctx, outbuf, &outlen, reinterpret_cast<unsigned const char*>(cipherText.c_str()), cipherText.length());
// Update cipher_text with the final remaining bytes.
int lastBlockLen = 0;
EVP_CipherFinal_ex(ctx, reinterpret_cast<unsigned char*>(&outbuf[outlen]), &lastBlockLen);
if ((outlen + lastBlockLen) == 0)
{
EVP_CIPHER_CTX_free(ctx);
cout << "EVP_CipherUpdate and EVP_CipherFinal_ex failed, cipherText(" + cipherText + ")." << endl;
return -1;
}
plainText.assign(reinterpret_cast<char*>(outbuf));
EVP_CIPHER_CTX_free(ctx);
return 0;
}
第一个代码段是专门针对 AES-CBC的,AES_set_decrypt_key和AES_cbc_encrypt是对通用加解密函数的封装而已,相应的更多封装api可以参考 OpenSSL中AES加密的用法
第二个代码段是更为通用的加解密函数,通过 EVP_CipherInit_ex 进行相应的特殊配置
aes加解密算法是一种将明文块分为一个个 128bit 块进行加密的算法, 所以对于无法恰好整分128bit的明文数据,就需要填充,填充方式的不同也是造成各种加密数据无法成功解密的原因之一。
aes的各种不同区分:
1、秘钥长度
aes算法分为 128,192,256 三种秘钥长度的加密方式 ,也就是常见的 aes-128 , aes-192 , aes-256。
2、加密模式
ECB模式
按照块密码的块大小被分为数个块,并对每个块进行独立加密。
优点:
1.简单;
2.有利于并行计算;
3.误差不会被传送;
缺点:
1.不能隐藏明文的模式;
2.可能对明文进行主动攻击;
CBC模式:
每个平文块先与前一个密文块进行异或后,再进行加密。在这种方法中,每个密文块都依赖于它前面的所有平文块。
同时,为了保证每条消息的唯一性,在第一个块中需要使用初始化向量。
优点:
1.不容易主动攻击,安全性好于ECB,适合传输长度长的报文,是SSL、IPSec的标准。
缺点:
1.不利于并行计算;
2.误差传递;
3.需要初始化向量IV
CFB模式:
模式类似于CBC,可以将块密码变为自同步的流密码。
优点:
1.隐藏了明文模式;
2.分组密码转化为流模式;
3.可以及时加密传送小于分组的数据;
缺点:
1.不利于并行计算;
2.误差传送:一个明文单元损坏影响多个单元;
3.唯一的IV;
OFB模式:
可以将块密码变成同步的流密码。它产生密钥流的块,然后将其与平文块进行异或,得到密文。
优点:
1.隐藏了明文模式;
2.分组密码转化为流模式;
3.可以及时加密传送小于分组的数据;
缺点:
1.不利于并行计算;
2.对明文的主动攻击是可能的;
3.误差传送:一个明文单元损坏影响多个单元;
PCBC模式
略
CTR模式
略
关于cbc模式的一张图:
3、填充方式
None //不填充。
PKCS7 //填充字符串由一个字节序列组成,每个字节填充该字节序列的长度。
Zeros //填充字符串由设置为零的字节组成。
ANSIX923 //ANSIX923 填充字符串由一个字节序列组成,此字节序列的最后一个字节填充字节序列的长度,其余字节均填充数字零。
ISO10126 //ISO10126 填充字符串由一个字节序列组成,此字节序列的最后一个字节填充字节序列的长度,其余字节填充随机数据。
在调用EVP_EncryptFinal_ex函数时,padding是默认启用的。
是否使用填充,可以通过EVP_CIPHER_CTX_set_padding函数设置,第二个参数为0则禁用padding,为1则启用padding。
默认的填充方式为: PKCS#7。
PKCS#7填充时将明文长度扩充为16的整数倍,每一个填充的字节值为填充的长度。
假设块长度为128bit,也就是16个字节。
那么当需要padding的字符串长度为11时(假设字符源串16进制为 : DD DD DD DD DD DD DD DD | DD DD DD DD ),则padding结果为:
| DD DD DD DD DD DD DD DD | DD DD DD DD 04 04 04 04 |
参考:
1、 solohac —— openssl AES加密以及padding
2、 游蓝海 —— OpenSSL中AES加密的用法