实战篇-OpenSSL之AES加密算法-ECB模式

本文属于《OpenSSL加密算法库使用系列教程》之一,欢迎查看其它文章。

一、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
encAES_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}
尾部填充了66,填充后数据长度为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}
尾部填充了1616,填充后数据长度为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



若对你有帮助,欢迎点赞、收藏、评论,你的支持就是我的最大动力!!!

同时,阿超为大家准备了丰富的学习资料,欢迎关注公众号“超哥学编程”,即可领取。

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

百里杨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值