OpenSSL
的加密算法库enc
提供了丰富的对称加密算法,下面说明一下如何通过命令行实现加密解密:
$ openssl enc -aes-256-cbc -e -K 3132333435363738393031323334353631323334353637383930313233343536 -iv 30303030303030303030303030303030 -in in.txt -nopad -nosalt -base64 -A
ayDcrt+pXn5ruS9G6WEsYMXHpvVy5KDg5Mkjjtabm7cT5wCqtrwm3qh+YoVnHSLbspACEephhkvlmrtgcaFSag==
这里设定的key是12345678901234561234567890123456
,iv是0000000000000000
。这里要注意的是,-K
、-iv
选项后面内容需要以16进制的方式输入,否则输出的结果会有问题。-e
选项表示编码,-base64
选项表明需要以base64转码后的字符串进行显示,-A
选项表明base64结果以一行的方式输出,-in
选项表明输入内容以文件方式传入,-nopad
表明不进行填充补齐(OpenSSL默认的padding模式为PKCS7Padding)。若需要解码,则只需将-e
改为-d
,并更改输入文件内容即可。
特别说明:网上搜索的AES在线加密解密工具里面,有一些网站的AES-256-CBC结果是不正确,AES算法CBC模式,不管是128、192还是256位,其中iv向量的长度都是16字节(AES_BLOCK_SIZE)。
在OpenSSL的中,可以通过AES_set_encrypt_key
函数进行设置,对应的设置代码如下:
AES_KEY aes_key;
if (AES_set_encrypt_key((const unsigned char*)password.c_str(), 256 /* 128 192 */, &aes_key) < 0)
{
assert(false);
}
#include "pch.h"
#include
#include
#include
#include
#include
#include
#include
#include
#if _DEBUG
#pragma comment(lib,"libcrypto64MDd.lib")
#pragma comment(lib,"libssl64MDd.lib")
#else
#pragma comment(lib,"libcrypto64MT.lib")
#pragma comment(lib,"libssl64MT.lib")
#endif
extern "C" {
#include "openssl/applink.c"
};
using namespace std;
static const std::string base64_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
static inline bool is_base64(unsigned char c) {
return (isalnum(c) || (c == '+') || (c == '/'));
}
// base64 编码
std::string base64_encode(char const* bytes_to_encode, int in_len) {
std::string ret;
int i = 0;
int j = 0;
unsigned char char_array_3[3];
unsigned char char_array_4[4];
while (in_len--) {
char_array_3[i++] = *(bytes_to_encode++);
if (i == 3) {
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for (i = 0; (i < 4); i++)
ret += base64_chars[char_array_4[i]];
i = 0;
}
}
if (i)
{
for (j = i; j < 3; j++)
char_array_3[j] = '\0';
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for (j = 0; (j < i + 1); j++)
ret += base64_chars[char_array_4[j]];
while ((i++ < 3))
ret += '=';
}
return ret;
}
// base64 解码
std::string base64_decode(std::string & encoded_string) {
int in_len = encoded_string.size();
int i = 0;
int j = 0;
int in_ = 0;
unsigned char char_array_4[4], char_array_3[3];
std::string ret;
while (in_len-- && (encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
char_array_4[i++] = encoded_string[in_]; in_++;
if (i == 4) {
for (i = 0; i < 4; i++)
char_array_4[i] = base64_chars.find(char_array_4[i]);
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (i = 0; (i < 3); i++)
ret += char_array_3[i];
i = 0;
}
}
if (i) {
for (j = i; j < 4; j++)
char_array_4[j] = 0;
for (j = 0; j < 4; j++)
char_array_4[j] = base64_chars.find(char_array_4[j]);
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
}
return ret;
}
std::string aes_256_cbc_encode(const std::string& password, const std::string& data)
{
// 这里默认将iv全置为字符0
unsigned char iv[AES_BLOCK_SIZE] = {
'0','0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0' };
AES_KEY aes_key;
if (AES_set_encrypt_key((const unsigned char*)password.c_str(), password.length() * 8, &aes_key) < 0)
{
//assert(false);
return "";
}
std::string strRet;
std::string data_bak = data;
unsigned int data_length = data_bak.length();
// ZeroPadding
int padding = 0;
if (data_bak.length() % (AES_BLOCK_SIZE) > 0)
{
padding = AES_BLOCK_SIZE - data_bak.length() % (AES_BLOCK_SIZE);
}
// 在一些软件实现中,即使是16的倍数也进行了16长度的补齐
/*else
{
padding = AES_BLOCK_SIZE;
}*/
data_length += padding;
while (padding > 0)
{
data_bak += '\0';
padding--;
}
for (unsigned int i = 0; i < data_length / (AES_BLOCK_SIZE); i++)
{
std::string str16 = data_bak.substr(i*AES_BLOCK_SIZE, AES_BLOCK_SIZE);
unsigned char out[AES_BLOCK_SIZE];
::memset(out, 0, AES_BLOCK_SIZE);
AES_cbc_encrypt((const unsigned char*)str16.c_str(), out, AES_BLOCK_SIZE, &aes_key, iv, AES_ENCRYPT);
strRet += std::string((const char*)out, AES_BLOCK_SIZE);
}
return strRet;
}
std::string aes_256_cbc_decode(const std::string& password, const std::string& strData)
{
// 这里默认将iv全置为字符0
unsigned char iv[AES_BLOCK_SIZE] = {
'0','0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0' };
AES_KEY aes_key;
if (AES_set_decrypt_key((const unsigned char*)password.c_str(), password.length() * 8, &aes_key) < 0)
{
//assert(false);
return "";
}
std::string strRet;
for (unsigned int i = 0; i < strData.length() / AES_BLOCK_SIZE; i++)
{
std::string str16 = strData.substr(i*AES_BLOCK_SIZE, AES_BLOCK_SIZE);
unsigned char out[AES_BLOCK_SIZE];
::memset(out, 0, AES_BLOCK_SIZE);
AES_cbc_encrypt((const unsigned char*)str16.c_str(), out, AES_BLOCK_SIZE, &aes_key, iv, AES_DECRYPT);
strRet += std::string((const char*)out, AES_BLOCK_SIZE);
}
return strRet;
}
int main()
{
// 原始字符串
string str = "test342432535534654365476456456436545645000000000000000000000001";
cout << "str(origin): " << str.c_str() << endl;
// AES key
string key = "12345678901234561234567890123456";
string str_encode = aes_256_cbc_encode(key, str);
string str_encode_base64 = base64_encode(str_encode.c_str(), str_encode.length());
// 加密后的结果,以base64编码输出
cout << "str_encode_base64: " << str_encode_base64.c_str() << endl;
string str_decode_base64 = base64_decode(str_encode_base64);
string str_decode = aes_256_cbc_decode(key, str_decode_base64);
//解密后的结果
cout << "str_decode: " << str_decode.c_str() << endl;
return 0;
}
如果是PKCS7Padding
,假设数据长度需要填充n(n>0)个字节才对齐(16的倍数),那么填充n个字节,每个字节都是n。如下:
... | DD DD DD DD DD DD DD DD DD DD DD DD 04 04 04 04 |
需要填充4个字节才对齐为16的倍数,所以在后面填充四个值为04的字节。
如果数据本身就已经对齐为16的倍数了,则填充一块长度为块大小(16个)的数据,每个字节都是块大小(值为16)。如下:
... | DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD | 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16
之所以这么设计,是因为PKCS7填充后的数据,最后一个字节表示填充的字节数。如果已经对齐,不额外填充一个块大小,那么最后一个字节是数据内容,会导致接收方无法正确判断填充的字节数(对方会认为填充了0xDD个字节)。
如果是ZeroPadding
,数据长度不对齐时使用0填充,否则不填充。如上述实例代码注释的,部分实现上会将已对齐的数据,再填充一个块大小。这里其实不填充也可以,因为ZeroPadding
通常适用于以\0
结尾的二进制字符串。同时这种填充方式无法区分纯文本数据字节和填充字节,如下例所示:
... | DD DD DD DD DD DD DD DD DD DD DD DD 00 00 //原始数据
... | DD DD DD DD DD DD DD DD DD DD DD DD 00 00 00 00 //填充后数据
上例中,如果原始数据结尾本身包含’\0’,那么接收方解码后得到的数据结尾会丢弃掉所有’\0’,此时接收方得到的结果会与发送方的有所不同。
关于所有Padding
方式的详细说明,可以参考维基百科。