本文属于《OpenSSL加密算法库使用系列教程》之一,欢迎查看其它文章。
实战篇-OpenSSL之AES加密算法-ECB模式
一、AES简介
密码学中的高级加密标准(Advanced Encryption Standard,AES),又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。
这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。经过五年的甄选流程,高级加密标准由美国国家标准与技术研究院(NIST)于2001年11月26日发布于FIPS
PUB 197,并在2002年5月26日成为有效的标准。2006年,高级加密标准已然成为对称密钥加密中最流行的算法之一。
AES属于对称加密算法,加解密使用同一个秘钥。
对称加密算法,一般有至少4种模式,即ECB、CBC、CFB、OFB等。
具体的加密原理,就不进行介绍了,本文主要从使用角度,进行说明。
以下命令行和编程实现,均基于OpenSSL开源库。在命令行中,我们可以使用命令实现对文件加解密,以验证我们的编程实现,是否正确。
二、ECB模式
电子密码本模式 Electronic Code Book(ECB)。这种模式是最早采用和最简单的模式,它将加密的数据分成若干组,每组的大小跟加密 密钥长度相同,然后每组都用相同的密钥进行加密。
其缺点是:电子编码薄模式用一个密钥加密消息的所有块,如果原消息中重复明文
块,则加密消息中的相应密文块也会重复,因此,电子编码薄模式适于加密小消息。
1、命令行操作
使用aes-128-ecb对hello.txt加密,128位密钥为8cc72b05705d5c46f412af8cbed55aad,密文为hello.en。
openssl enc -e -aes-128-ecb -in hello.txt -out hello.en -K 8cc72b05705d5c46f412af8cbed55aad
使用aes-128-ecb对hello.en解密,128位密钥为8cc72b05705d5c46f412af8cbed55aad,解密后的文件为hello.de。
openssl enc -d -aes-128-ecb -in hello.en -out hello.de -K 8cc72b05705d5c46f412af8cbed55aad
2、函数说明
生成加密/解密的Key:
int AES_set_encrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
参数名称 | 含义 |
---|---|
userKey | 用户指定的密码。注意:只能是16/24/32字节。如果密码字符串长度不够,可以在字符串末尾追加一些特定的字符,或者重复密码字符串,直到满足最少的长度。 |
bits | 密码位数。即userKey的长度 * 8,只能是128/192/256位。 |
key | 向外输出参数 |
返回值 | 0 - 成功; -1 - userkey,key为空;-2 - 密钥长度不是128/192/256 |
AES ECB加密/解密:
void AES_ecb_encrypt(const unsigned char *in, unsigned char *out, const AES_KEY *key, const int enc);
参数名称 | 含义 |
---|---|
in | 输入数据,长度固定为16字节 |
out | 输出数据,长度与输入数据一致,固定为16字节 |
key | 使用AES_set_encrypt_key/AES_set_decrypt_key生成的Key |
enc | AES_ENCRYPT 代表加密, AES_DECRYPT代表解密 |
3、编程实现
由于ECB模式,每次只能处理一个块的数据,即16字节,所以如果需要处理任意长度的数据,那么需要在原始数据末尾,先进行填充,使得数据长度为16的整数倍,随后再分块进行加密。解密时,也需要分块解密,最后将解密后的数据,进行取消填充。
(1)PKCS7填充方式
AES支持多种填充方式:如NoPadding、PKCS5Padding、ISO10126Padding、PKCS7Padding、ZeroPadding。密文长度与填充方式关系,可参考《AES加密模式和填充模式》。
PKCS7填充方式:
假设数据长度需要填充n(n>0)个字节才对齐,那么填充n个字节,每个字节都是n;如果数据本身就已经对齐了,则填充一块长度为块大小的数据,每个字节都是块大小。
举个例子最直观,这里以块大小为16字节,进行PKCS7填充,如下:
数据1: {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A}
填充后:{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06}
尾部填充了6个6,填充后数据长度为16。
数据2: {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10}
填充后:{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10}
尾部填充了16个16,填充后数据长度为32。
Padding类实现了上述的PKCS7填充功能,如下:
Padding.h
#ifndef PADDING_H
#define PADDING_H
#include <QByteArray>
/**
* @brief The Padding class
* 算法数据填充模式,提供对数据进行PKCS7填充和去除填充的相关函数。
*/
class Padding
{
public:
static int getPKCS7PaddedLength(int dataLen, int alignSize);
static QByteArray PKCS7Padding(const QByteArray &in, int alignSize);
static QByteArray PKCS7UnPadding(const QByteArray &in);
};
#endif // PADDING_H
Padding.cpp
#include "Padding.h"
/**
* @brief Padding::getPKCS7PaddedLength
* 根据原始数据长度,计算进行PKCS7填充后的数据长度
* @param dataLen 原始数据长度
* @param alignSize 对齐字节数
* @return 返回填充后的数据长度
*/
int Padding::getPKCS7PaddedLength(int dataLen, int alignSize)
{
// 计算填充的字节数(按alignSize字节对齐进行填充)
int remainder = dataLen % alignSize;
int paddingSize = (remainder == 0) ? alignSize : (alignSize - remainder);
return (dataLen + paddingSize);
}
/**
* @brief Padding::PKCS7Padding
* 采用PKCS7Padding方式,将in数据进行alignSize字节对齐填充。
* 此函数用于加密前,对明文进行填充。
* @param in 数据
* @param alignSize 对齐字节数
* @return 返回填充后的数据
*/
QByteArray Padding::PKCS7Padding(const QByteArray &in, int alignSize)
{
// 计算需要填充字节数(按alignSize字节对齐进行填充)
int remainder = in.size() % alignSize;
int paddingSize = (remainder == 0) ? alignSize : (alignSize - remainder);
// 进行填充
QByteArray temp(in);
temp.append(paddingSize, paddingSize);
return temp;
}
/**
* @brief Padding::PKCS7UnPadding
* 采用PKCS7Padding方式,将in数据去除填充。
* 此函数用于解密后,对解密结果进一步去除填充,以得到原始数据。
* @param in 数据
* @return 返回去除填充后的数据
*/
QByteArray Padding::PKCS7UnPadding(const QByteArray &in)
{
char paddingSize = in.at(in.size() - 1);
return in.left(in.size() - paddingSize);
}
代码比较简单,注释也说的很明白啦。
(2)实现ECB模式加解密
下面,函数已经封装完毕,如下:
#include "AES.h"
#include <openssl/modes.h>
#include <openssl/aes.h>
#include "Padding.h"
/**
* @brief AES::ecb_encrypt
* ECB模式加解密,填充模式采用PKCS7Padding,
* 支持对任意长度明文进行加解密。
* @param in 输入数据
* @param out 输出结果
* @param key 密钥,长度必须是16/24/32字节,否则加密失败
* @param enc true-加密,false-解密
* @return 执行结果
*/
bool AES::ecb_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, bool enc)
{
// 检查密钥合法性(只能是16、24、32字节)
Q_ASSERT(key.size() == 16 || key.size() == 24 || key.size() == 32);
if (enc)
{
// 生成加密key
AES_KEY aes_key;
if (AES_set_encrypt_key((const unsigned char*)key.data(), key.size() * 8, &aes_key) != 0)
{
return false;
}
// 进行PKCS7Padding填充
QByteArray inTemp = Padding::PKCS7Padding(in, AES_BLOCK_SIZE);
// 执行ECB模式加密
out.resize(inTemp.size()); // 调整输出buf大小
for (int i = 0; i < inTemp.size() / AES_BLOCK_SIZE; i++)
{
AES_ecb_encrypt((const unsigned char*)inTemp.data() + AES_BLOCK_SIZE * i,
(unsigned char*)out.data() + AES_BLOCK_SIZE * i,
&aes_key,
AES_ENCRYPT);
}
return true;
}
else
{
// 生成解密key
AES_KEY aes_key;
if (AES_set_decrypt_key((const unsigned char*)key.data(), key.size() * 8, &aes_key) != 0)
{
return false;
}
// 执行ECB模式解密
out.resize(in.size()); // 调整输出buf大小
for (int i = 0; i < in.size() / AES_BLOCK_SIZE; i++)
{
AES_ecb_encrypt((const unsigned char*)in.data() + AES_BLOCK_SIZE * i,
(unsigned char*)out.data() + AES_BLOCK_SIZE * i,
&aes_key,
AES_DECRYPT);
}
// 解除PKCS7Padding填充
out = Padding::PKCS7UnPadding(out);
return true;
}
}
加密过程:先通过AES_set_encrypt_key函数生成加密key,然后将输入数据进行PKCS7填充,保证得到16字节整数倍明文,ECB模式密文长度等于明文长度,故将输出缓冲out调整为明文大小,以保存密文数据,最后,将明文按16字节,分块进行加密。
解密过程:先通过AES_set_decrypt_key函数生成解密key,由于输入数据,即密文本身就是16字节整数倍,故直接按16字节进行分块解密,并将解密后的数据,进行去除填充,得到真正的明文。
经测试,本函数支持对任意长度输入数据进行加解密。
(3)测试代码
void createTestData(QByteArray& data, int size)
{
data.resize(size);
for (int i = 0; i < size; i++)
{
data[i] = i % 128;
}
}
void testAES(const QByteArray& data)
{
QByteArray plainText = data;
QByteArray encryptText;
QByteArray decryptText;
QByteArray key = QByteArray::fromHex("8cc72b05705d5c46f412af8cbed55aad");
// AES ecb模式加密验证
AES aes;
aes.ecb_encrypt(plainText, encryptText, key, true); // 加密
aes.ecb_encrypt(encryptText, decryptText, key, false); // 解密
qDebug() << "AES ecb encrypt verify" << ((decryptText == plainText) ? "succeeded" : "failed");
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// 产生1MB+3B的测试数据,为了使该测试数据长度,不为8或16的整数倍
QByteArray data;
createTestData(data, 1*1024*1024+3);
// 测试AES
testAES(data); // 测试,直接调用OpenSSL中AES算法函数
return a.exec();
}
执行结果:
本文涉及工程代码地址:https://gitee.com/bailiyang/cdemo/tree/master/Qt/49OpenSSL/OpenSSL
若对你有帮助,欢迎点赞、收藏、评论,你的支持就是我的最大动力!!!
同时,阿超为大家准备了丰富的学习资料,欢迎关注公众号“超哥学编程”,即可领取。