C++使用OpenSSL实现AES-256-CBC加密解密实例

OpenSSL说明

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)。

AES 128/192/256设置方式

在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 <iostream>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<openssl/aes.h>
#include<openssl/rsa.h>
#include<openssl/pem.h>
#include<openssl/err.h>

#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;
}

关于Padding额外说明

如果是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方式的详细说明,可以参考维基百科

  • 4
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值