使用openssl库进行AES算法的加密
介绍
最近维护一个插件,需要添加一个小的功能,希望能够对用户的密码进行加密保存到文件中,然后再从文件中提取出加密后的数据,并进行解密。
功能很简单,目前也有着许多的开源库包含了各种各样的加密算法。这里我采用了openssl库中的AES加密算法。
原本以为这个实现会很容易,但是最后还是出现了一些小的问题,记录下来做一个备份。
问题:
其中出现的最主要的问题就是,我们是从一个xml配置文件中读取用户密码,最初是以明文存储的。但是我们利用AES加密算法后,将加密数据写回到文件中,却总是无法正常的再次读取出来。当时我考虑的是,加密后的数据基本上都是乱码的数据,所以在输出文件的时候按照字节流进行输出,xml读取的时候读取出原来加密后的字节流就可以完成解密了。
但是,结果并不理想,先不说每次输出文件的时候需要按照字节流进行输出,就会给编程带来一定的复杂。除此之外,xml读取的时候也并不能很好的读取。究其根本,xml主要面向是文本文件的,当时采用的是tinyXML来进行文本的读取,所以希望面向文本文件的提取函数提取二进制流就有些强人所难了。
后来就考虑另外一个问题,我们把加密后的数据进行直接转换成合法的ascii码应该就可以了,当时考虑的是将每个字节的内容直接按照十六进制的值输出到文件内。但是突然想起俩base64编码,它本来就是完成这个功能的。
关于Base64编码
Base64是一种用64个字符来表示任意二进制数据的方法。
只有一个字节的值在0~127是合法的ASCII码。对于不合法的数据,我们就会看到各种乱码。比如对于jpg,pdf等文件,用记事本打开会看到一大堆乱码。
是因为二进制文件包含很多无法显示和打印的字符,所以,如果要让记事本这样的文本处理软件能处理二进制数据,就需要一个二进制到字符串的转换方法。
Base64是一种最常见的二进制编码方法。
我们将原来的二进制数据进行Base64编码之后,在存储到文本文件中,就不会出现乱码的问题,xml文件也能够很好的提取出来编码后的数据,在解密之前,我们只需要将xml中提取到的数据先进行Base64的解码然后再进行AES算法的解密就可以完美解决问题了。
代码示例
#include <string>
#include <iostream>
#include <fstream>
#include <assert.h>
#include <openssl/aes.h>
#include <openssl/evp.h>
using namespace std;
const int BUFFER_SIZE = 1024;
// base64编码
int Base64Encode(const char *encoded, int encodedLength, char *decoded)
{
return EVP_EncodeBlock((unsigned char*)decoded, (const unsigned char*)encoded, encodedLength);
}
// base解码
int Base64Decode(const char *encoded, int encodedLength, char *decoded)
{
return EVP_DecodeBlock((unsigned char*)decoded, (const unsigned char*)encoded, encodedLength);
}
// AES算法加密
string EncryptAES(const string& strKey, const string& strData)
{
AES_KEY aes_key;
if (AES_set_encrypt_key((const unsigned char*)strKey.c_str(), strKey.length() * 8, &aes_key) < 0)
{
assert(false);
return "";
}
string strEncryptedData;
string strDataBak = strData;
unsigned int data_length = strDataBak.length();
int padding = 0;
if (strDataBak.length() % AES_BLOCK_SIZE > 0)
{
padding = AES_BLOCK_SIZE - strDataBak.length() % AES_BLOCK_SIZE;
}
data_length += padding;
while (padding > 0)
{
strDataBak += '\0';
padding--;
}
for (unsigned int i = 0; i < data_length / AES_BLOCK_SIZE; i++)
{
string strBlock = strDataBak.substr(i*AES_BLOCK_SIZE, AES_BLOCK_SIZE);
unsigned char out[AES_BLOCK_SIZE];
::memset(out, 0, AES_BLOCK_SIZE);
AES_encrypt((const unsigned char*)strBlock.c_str(), out, &aes_key);
strEncryptedData += string((const char*)out, AES_BLOCK_SIZE);
}
char buffer[BUFFER_SIZE];
Base64Encode(strEncryptedData.c_str(), strEncryptedData.size(),buffer);
return string(buffer,strlen(buffer));
}
// AES算法解密
string DecryptAES(const string& strKey, const string& strDecryBase64Data)
{
char buffer[BUFFER_SIZE];
Base64Decode(strDecryBase64Data.c_str(), strDecryBase64Data.size(), buffer);
string strEncryData(buffer, strlen(buffer));
AES_KEY aes_key;
if (AES_set_decrypt_key((const unsigned char*)strKey.c_str(), strKey.length() * 8, &aes_key) < 0)
{
assert(false);
return "";
}
std::string strRet;
for (unsigned int i = 0; i < strEncryData.length() / AES_BLOCK_SIZE; i++)
{
std::string strBlock = strEncryData.substr(i*AES_BLOCK_SIZE, AES_BLOCK_SIZE);
unsigned char out[AES_BLOCK_SIZE];
::memset(out, 0, AES_BLOCK_SIZE);
AES_decrypt((const unsigned char*)strBlock.c_str(), out, &aes_key);
strRet += std::string((const char*)out, AES_BLOCK_SIZE);
}
string::size_type pos = strRet.find_last_not_of('\0');
if (pos != string::npos)
{
strRet = strRet.substr(0, pos + 1);
}
return strRet;
}
int main(int argc, char *argv[])
{
//密钥长度要求为128,192,256字节,并且不可为空
string strKey = "encrypted_123456";
string strOriginData = "encry12345_";
string strEncryBase64Data = EncryptAES(strKey, strOriginData);
string strDecryBase64Data = DecryptAES(strKey, strEncryBase64Data);
cout << "OriginData:\t\t" << strOriginData << endl;
cout << "EncryBase64Data:\t" << strEncryBase64Data << endl;
cout << "DecryBase64Data:\t" << strDecryBase64Data << endl;
}