实战篇-OpenSSL之调用EVP框架实现AES多种加密模式

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

一、OpenSSL EVP简介

OpenSSL EVP(high-level cryptographic functions)提供了丰富的密码学中的各种函数。Openssl 中实现了各种对称算法、摘要算法以及签名/验签算法。EVP 函数将这些具体的算法进行了封装。

通过这样的统一的封装,使得只需要在初始化参数的时候做很少的改变,就可以使用相同的代码但采用不同的加密算法进行数据的加密和解密。

一句话,EVP是封装的高层接口,通过它加解密,可以不用关心更多细节问题,使用更简单。

二、EVP中对称加密与解密流程

EVP中用于对称加密的函数,主要有下面这些。

一般加密流程,执行步骤如下所示:

  • EVP_CIPHER_CTX_init,初始化对称计算上下文
  • EVP_EncryptInit_ex,加密初始化函数,本函数调用具体算法的init 回调函数,将外送密钥key 转换
    为内部密钥形式,将初始化向量iv 拷贝到ctx 结构中。
  • EVP_EncryptUpdate,加密函数,用于多次计算,它调用了具体算法的 do_cipher 回调函数。
  • EVP_EncryptFinal_ex,获取加密结果,函数可能涉及填充,它调用了具体算法的 do_cipher 回调函数。

一般解密流程,执行步骤如下所示:

  • EVP_CIPHER_CTX_init,初始化对称计算上下文
  • EVP_DecryptInit_ex,解密初始化函数。
  • EVP_DecryptUpdate,解密函数,用于多次计算,它调用了具体算法的 do_cipher 回调函数。
  • EVP_DecryptFinal_ex,获取解密结果,函数可能涉及去填充,它调用了具体算法的 do_cipher 回调函数。

根据上述的步骤,封装一个基本的,EVP加解密函数,如下:

EvpAES::EvpAES()
{
    // 初始化CTX
    ctx = EVP_CIPHER_CTX_new();
    EVP_CIPHER_CTX_init(ctx);
}

EvpAES::~EvpAES()
{
    // 释放CTX
    EVP_CIPHER_CTX_cleanup(ctx);
    EVP_CIPHER_CTX_free(ctx);
}

bool EvpAES::encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const QByteArray &ivec, const EVP_CIPHER *ciper, bool enc)
{
    if (enc)
    {
        // 指定加密算法及key和iv
        int ret = EVP_EncryptInit_ex(ctx, ciper, NULL, (const unsigned char*)key.data(), (const unsigned char*)ivec.data());
        if(ret != 1)
        {
            return false;
        }

        // 进行加密操作
        int mlen = 0;
        out.resize(in.size() + AES_BLOCK_SIZE);
        ret = EVP_EncryptUpdate(ctx, (unsigned char*)out.data(), &mlen, (const unsigned char*)in.data(), in.size());
        if(ret != 1)
        {
            return false;
        }

        // 结束加密操作
        int flen = 0;
        ret = EVP_EncryptFinal_ex(ctx, (unsigned char *)out.data() + mlen, &flen);
        if(ret != 1)
        {
            return false;
        }
        out.resize(mlen + flen);
        return true;
    }
    else
    {
        // 指定解密算法及key和iv
        int ret = EVP_DecryptInit_ex(ctx, ciper, NULL, (const unsigned char*)key.data(), (const unsigned char*)ivec.data());
        if(ret != 1)
        {
            return false;
        }

        // 进行解密操作
        int mlen = 0;
        out.resize(in.size());
        ret = EVP_DecryptUpdate(ctx, (unsigned char*)out.data(), &mlen, (const unsigned char*)in.data(), in.size());
        if(ret != 1)
        {
            return false;
        }

        // 结束解密操作
        int flen = 0;
        ret = EVP_DecryptFinal_ex(ctx, (unsigned char *)out.data() + mlen, &flen);
        if(ret != 1)
        {
            return false;
        }
        out.resize(mlen + flen);
        return true;
    }
}

三、通过EVP实现AES多种加密模式

使用EVP的好处就是,不用考虑诸如对齐填充、秘钥、处理长度等等细节,这些细节每个加密模式,可能都不一样。EVP把这些加密算法全部统一了,以统一的方式去操作。

与直接调用具体的加密函数相比,EVP的步骤更多了一些,所以具体使用哪种,根据自己实际情况来即可。

我们在前面的encrypt函数基础上,封装更多的加密模式方法出来,以供外部使用。

这里实现了ECB、CBC、CFB1、CFB8、CFB128、OFB128、CTR、GCM、XTS、OCB共10种模式。如下:

#include "EvpAES.h"
#include <openssl/evp.h>
#include <openssl/aes.h>

#define KEY_SIZE_16B            16
#define KEY_SIZE_24B            24
#define KEY_SIZE_32B            32

bool EvpAES::ecb_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, bool enc)
{
    // 检查密钥合法性(只能是16、24、32字节)
    Q_ASSERT(key.size() == KEY_SIZE_16B || key.size() == KEY_SIZE_24B || key.size() == KEY_SIZE_32B);

    // 根据key大小创建EVP_CIPHER
    const EVP_CIPHER * cipher = nullptr;
    if (key.size() == KEY_SIZE_16B)
    {
        cipher = EVP_aes_128_ecb();
    }
    else if (key.size() == KEY_SIZE_24B)
    {
        cipher = EVP_aes_192_ecb();
    }
    else
    {
        cipher = EVP_aes_256_ecb();
    }

    // 执行加解密
    return encrypt(in, out, key, QByteArray(), cipher, enc);
}

bool EvpAES::cbc_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const QByteArray &ivec, bool enc)
{
    // 检查密钥合法性(只能是16、24、32字节)
    Q_ASSERT(key.size() == KEY_SIZE_16B || key.size() == KEY_SIZE_24B || key.size() == KEY_SIZE_32B);
    Q_ASSERT(ivec.size() == KEY_SIZE_16B); // 初始向量为16字节

    // 根据key大小创建EVP_CIPHER
    const EVP_CIPHER * cipher = nullptr;
    if (key.size() == KEY_SIZE_16B)
    {
        cipher = EVP_aes_128_cbc();
    }
    else if (key.size() == KEY_SIZE_24B)
    {
        cipher = EVP_aes_192_cbc();
    }
    else
    {
        cipher = EVP_aes_256_cbc();
    }

    // 执行加解密
    return encrypt(in, out, key, ivec, cipher, enc);
}

bool EvpAES::cfb1_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const QByteArray &ivec, bool enc)
{
    // 检查密钥合法性(只能是16、24、32字节)
    Q_ASSERT(key.size() == KEY_SIZE_16B || key.size() == KEY_SIZE_24B || key.size() == KEY_SIZE_32B);
    Q_ASSERT(ivec.size() == KEY_SIZE_16B); // 初始向量为16字节

    // 根据key大小创建EVP_CIPHER
    const EVP_CIPHER * cipher = nullptr;
    if (key.size() == KEY_SIZE_16B)
    {
        cipher = EVP_aes_128_cfb1();
    }
    else if (key.size() == KEY_SIZE_24B)
    {
        cipher = EVP_aes_192_cfb1();
    }
    else
    {
        cipher = EVP_aes_256_cfb1();
    }

    // 执行加解密
    return encrypt(in, out, key, ivec, cipher, enc);
}

bool EvpAES::cfb8_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const QByteArray &ivec, bool enc)
{
    // 检查密钥合法性(只能是16、24、32字节)
    Q_ASSERT(key.size() == KEY_SIZE_16B || key.size() == KEY_SIZE_24B || key.size() == KEY_SIZE_32B);
    Q_ASSERT(ivec.size() == KEY_SIZE_16B); // 初始向量为16字节

    // 根据key大小创建EVP_CIPHER
    const EVP_CIPHER * cipher = nullptr;
    if (key.size() == KEY_SIZE_16B)
    {
        cipher = EVP_aes_128_cfb8();
    }
    else if (key.size() == KEY_SIZE_24B)
    {
        cipher = EVP_aes_192_cfb8();
    }
    else
    {
        cipher = EVP_aes_256_cfb8();
    }

    // 执行加解密
    return encrypt(in, out, key, ivec, cipher, enc);
}

bool EvpAES::cfb128_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const QByteArray &ivec, bool enc)
{
    // 检查密钥合法性(只能是16、24、32字节)
    Q_ASSERT(key.size() == KEY_SIZE_16B || key.size() == KEY_SIZE_24B || key.size() == KEY_SIZE_32B);
    Q_ASSERT(ivec.size() == KEY_SIZE_16B); // 初始向量为16字节

    // 根据key大小创建EVP_CIPHER
    const EVP_CIPHER * cipher = nullptr;
    if (key.size() == KEY_SIZE_16B)
    {
        cipher = EVP_aes_128_cfb128();
    }
    else if (key.size() == KEY_SIZE_24B)
    {
        cipher = EVP_aes_192_cfb128();
    }
    else
    {
        cipher = EVP_aes_256_cfb128();
    }

    // 执行加解密
    return encrypt(in, out, key, ivec, cipher, enc);
}

bool EvpAES::ofb128_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const QByteArray &ivec, bool enc)
{
    // 检查密钥合法性(只能是16、24、32字节)
    Q_ASSERT(key.size() == KEY_SIZE_16B || key.size() == KEY_SIZE_24B || key.size() == KEY_SIZE_32B);
    Q_ASSERT(ivec.size() == KEY_SIZE_16B); // 初始向量为16字节

    // 根据key大小创建EVP_CIPHER
    const EVP_CIPHER * cipher = nullptr;
    if (key.size() == KEY_SIZE_16B)
    {
        cipher = EVP_aes_128_ofb();
    }
    else if (key.size() == KEY_SIZE_24B)
    {
        cipher = EVP_aes_192_ofb();
    }
    else
    {
        cipher = EVP_aes_256_ofb();
    }

    // 执行加解密
    return encrypt(in, out, key, ivec, cipher, enc);
}

bool EvpAES::ctr_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const QByteArray &ivec, bool enc)
{
    // 检查密钥合法性(只能是16、24、32字节)
    Q_ASSERT(key.size() == KEY_SIZE_16B || key.size() == KEY_SIZE_24B || key.size() == KEY_SIZE_32B);
    Q_ASSERT(ivec.size() == KEY_SIZE_16B); // 初始向量为16字节

    // 根据key大小创建EVP_CIPHER
    const EVP_CIPHER * cipher = nullptr;
    if (key.size() == KEY_SIZE_16B)
    {
        cipher = EVP_aes_128_ctr();
    }
    else if (key.size() == KEY_SIZE_24B)
    {
        cipher = EVP_aes_192_ctr();
    }
    else
    {
        cipher = EVP_aes_256_ctr();
    }

    // 执行加解密
    return encrypt(in, out, key, ivec, cipher, enc);
}

bool EvpAES::gcm_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const QByteArray &ivec, bool enc)
{
    // 检查密钥合法性(只能是16、24、32字节)
    Q_ASSERT(key.size() == KEY_SIZE_16B || key.size() == KEY_SIZE_24B || key.size() == KEY_SIZE_32B);
    Q_ASSERT(ivec.size() == KEY_SIZE_16B); // 初始向量为16字节

    // 根据key大小创建EVP_CIPHER
    const EVP_CIPHER * cipher = nullptr;
    if (key.size() == KEY_SIZE_16B)
    {
        cipher = EVP_aes_128_gcm();
    }
    else if (key.size() == KEY_SIZE_24B)
    {
        cipher = EVP_aes_192_gcm();
    }
    else
    {
        cipher = EVP_aes_256_gcm();
    }

    // 执行加解密
    return encrypt(in, out, key, ivec, cipher, enc);
}

bool EvpAES::xts_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const QByteArray &ivec, bool enc)
{
    // 检查密钥合法性(只能是16、32字节)
    Q_ASSERT(key.size() == KEY_SIZE_16B || key.size() == KEY_SIZE_32B);
    Q_ASSERT(ivec.size() == KEY_SIZE_16B); // 初始向量为16字节

    // 根据key大小创建EVP_CIPHER
    const EVP_CIPHER * cipher = nullptr;
    if (key.size() == KEY_SIZE_16B)
    {
        cipher = EVP_aes_128_xts();
    }
    else
    {
        cipher = EVP_aes_256_xts();
    }

    // 执行加解密
    return encrypt(in, out, key, ivec, cipher, enc);
}

bool EvpAES::ocb_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const QByteArray &ivec, bool enc)
{
    // 检查密钥合法性(只能是16、24、32字节)
    Q_ASSERT(key.size() == KEY_SIZE_16B || key.size() == KEY_SIZE_24B || key.size() == KEY_SIZE_32B);
    Q_ASSERT(ivec.size() == KEY_SIZE_16B); // 初始向量为16字节

    // 根据key大小创建EVP_CIPHER
    const EVP_CIPHER * cipher = nullptr;
    if (key.size() == KEY_SIZE_16B)
    {
        cipher = EVP_aes_128_ocb();
    }
    else if (key.size() == KEY_SIZE_24B)
    {
        cipher = EVP_aes_192_ocb();
    }
    else
    {
        cipher = EVP_aes_256_ocb();
    }

    // 执行加解密
    return encrypt(in, out, key, ivec, cipher, enc);
}

代码虽多,但结构类似,比较简单,直接看。

经测试,本类中加解密函数,支持对任意长度输入数据进行加解密。

扩展:

应该DES、TripleDES等对称加密算法,也可以按照上述的方法,从encrypt函数,进行再次封装,以向外提供该算法方法。毕竟都是属于对称加密,道理上应该是可以的。这个就不去试了,感兴趣的小伙伴,自行尝试吧。

四、测试代码

void createTestData(QByteArray& data, int size)
{
    data.resize(size);
    for (int i = 0; i < size; i++)
    {
        data[i] = i % 128;
    }
}

void testEvpAES(const QByteArray& data)
{
    QByteArray plainText = data;
    QByteArray encryptText;
    QByteArray decryptText;

    QByteArray key = QByteArray::fromHex("8cc72b05705d5c46f412af8cbed55aad");
    QByteArray ivec = QByteArray::fromHex("667b02a85c61c786def4521b060265e8");

    // EvpAES ecb模式加密验证
    EvpAES aes;
    aes.ecb_encrypt(plainText, encryptText, key, true);
    aes.ecb_encrypt(encryptText, decryptText, key, false);
    qDebug() << "EvpAES ecb encrypt verify" << ((decryptText == plainText) ? "succeeded" : "failed");
    encryptText.clear();
    decryptText.clear();

    // EvpAES cbc模式加密验证
    aes.cbc_encrypt(plainText, encryptText, key, ivec, true);
    aes.cbc_encrypt(encryptText, decryptText, key, ivec, false);
    qDebug() << "EvpAES cbc encrypt verify" << ((decryptText == plainText) ? "succeeded" : "failed");
    encryptText.clear();
    decryptText.clear();

    // EvpAES cfb1模式加密验证
    aes.cfb1_encrypt(plainText, encryptText, key, ivec, true);
    aes.cfb1_encrypt(encryptText, decryptText, key, ivec, false);
    qDebug() << "EvpAES cfb1 encrypt verify" << ((decryptText == plainText) ? "succeeded" : "failed");
    encryptText.clear();
    decryptText.clear();

    // EvpAES cfb8模式加密验证
    aes.cfb8_encrypt(plainText, encryptText, key, ivec, true);
    aes.cfb8_encrypt(encryptText, decryptText, key, ivec, false);
    qDebug() << "EvpAES cfb8 encrypt verify" << ((decryptText == plainText) ? "succeeded" : "failed");
    encryptText.clear();
    decryptText.clear();

    // EvpAES cfb128模式加密验证
    aes.cfb128_encrypt(plainText, encryptText, key, ivec, true);
    aes.cfb128_encrypt(encryptText, decryptText, key, ivec, false);
    qDebug() << "EvpAES cfb128 encrypt verify" << ((decryptText == plainText) ? "succeeded" : "failed");
    encryptText.clear();
    decryptText.clear();

    // EvpAES ofb128模式加密验证
    aes.ofb128_encrypt(plainText, encryptText, key, ivec, true);
    aes.ofb128_encrypt(encryptText, decryptText, key, ivec, false);
    qDebug() << "EvpAES ofb128 encrypt verify" << ((decryptText == plainText) ? "succeeded" : "failed");
    encryptText.clear();
    decryptText.clear();

    // EvpAES ctr模式加密验证
    aes.ctr_encrypt(plainText, encryptText, key, ivec, true);
    aes.ctr_encrypt(encryptText, decryptText, key, ivec, false);
    qDebug() << "EvpAES ctr encrypt verify" << ((decryptText == plainText) ? "succeeded" : "failed");
    encryptText.clear();
    decryptText.clear();

    // EvpAES gcm模式加密验证
    aes.gcm_encrypt(plainText, encryptText, key, ivec, true);
    aes.gcm_encrypt(encryptText, decryptText, key, ivec, false);
    qDebug() << "EvpAES gcm encrypt verify" << ((decryptText == plainText) ? "succeeded" : "failed");
    encryptText.clear();
    decryptText.clear();

    // EvpAES xts模式加密验证
    aes.xts_encrypt(plainText, encryptText, key, ivec, true);
    aes.xts_encrypt(encryptText, decryptText, key, ivec, false);
    qDebug() << "EvpAES xts encrypt verify" << ((decryptText == plainText) ? "succeeded" : "failed");
    encryptText.clear();
    decryptText.clear();

    // EvpAES ocb模式加密验证
    aes.ocb_encrypt(plainText, encryptText, key, ivec, true);
    aes.ocb_encrypt(encryptText, decryptText, key, ivec, false);
    qDebug() << "EvpAES ocb encrypt verify" << ((decryptText == plainText) ? "succeeded" : "failed");
    encryptText.clear();
    decryptText.clear();
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // 产生1MB的测试数据
    QByteArray data;
    createTestData(data, 1*1024*1024);

    // 测试AES
    testEvpAES(data);  // 测试,通过EVP框架调用AES算法

    return a.exec();
}

执行结果:

在这里插入图片描述

本文涉及工程代码地址:https://gitee.com/bailiyang/cdemo/tree/master/Qt/49OpenSSL/OpenSSL



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

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

在这里插入图片描述

0、此例程调试环境 运行uname -a的结果如下: Linux freescale 3.0.35-2666-gbdde708-g6f31253 #1 SMP PREEMPT Thu Nov 30 15:45:33 CST 2017 armv7l GNU/Linux 简称2017 armv7l GNU/Linux 1、openssl 直接处理AES的API 在openssl/aes.h定义。是基本的AES库函数接口,可以直接调用,但是那个接口是没有填充的。而如果要与Java通信,必须要有填充模式。所以看第2条。 2、利用openssl EVP接口 在openssl/evp.h中定义。在这个接口中提供的AES是默认是pkcs5padding方式填充方案。 3、注意openssl新老版本的区别 看如下这段 One of the primary differences between master (OpenSSL 1.1.0) and the 1.0.2 version is that many types have been made opaque, i.e. applications are no longer allowed to look inside the internals of the structures. The biggest impact on applications is that: 1)You cannot instantiate these structures directly on the stack. So instead of: EVP_CIPHER_CTX ctx; you must instead do: EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); .... EVP_CIPHER_CTX_free(ctx); 2)You must only use the provided accessor functions to access the internals of the structure. 4、注意加密的内容是数据不限制是否为字符串 openssl接口加密的是数据,不限制是否为字符串,我看到有些人在加密时使用strlen(),来获取要加密的长度,如果是对字符串加密的话没有问题,如果不是字符串的话,用它获取的长度是到第一个0处,因为这个函数获取的是字符串长度,字符串是以零为终止的。 5、在调用EVP_EncryptFinal_ex时不要画蛇添足 正常加解密处理过程,引用网友的代码如下,经测试正确。 int kk_encrypt(unsigned char *plaintext, int plaintext_len, unsigned char *key, unsigned char *iv, unsigned char *ciphertext) { EVP_CIPHER_CTX *ctx; int len; int ciphertext_len; ctx = EVP_CIPHER_CTX_new(); EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv); //EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, iv); EVP_EncryptUpdate(ctx, ciphertext, &len;, plaintext, plaintext_len); ciphertext_len = len; EVP_EncryptFinal_ex(ctx, ciphertext + len, &len;); ciphertext_len += len; EVP_CIPHER_CTX_free(ctx); return ciphertext_len; } int kk_decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *key, unsigned char *iv, unsigned char *plaintext) { EVP_CIPHER_CTX *ctx; int len; int plaintext_len; ctx = EVP_CIPHER_CTX_new(); EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv); //EVP_DecryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, iv); EVP_DecryptUpdate(ctx, plaintext, &len;, ciphertext, ciphertext_len); plaintext_len = len; EVP_DecryptFinal_ex(ctx, plaintext + len, &len;); plaintext_len += len; EVP_CIPHER_CTX_free(ctx); return plaintext_len; } 我看到有人提供的代码在加密长度正好是16字节的整数倍时特意不去调用EVP_EncryptFinal_ex,这实在是画蛇添足啊,不论什么情况下,最后一定要调用EVP_EncryptFinal_ex一次,然后结束加密过程。 6、Base64陷阱 如果用到了base64,要注意如下: 1)base64算法是将3个字节变成4个可显示字符。所以在如果数据长度不是3字节对齐时,会补0凑齐。 2)在解密时先要解base64,再解AES。在解base64后,要减掉补上的0。算法就去查看base64后的字符串尾处有几个=号,最多是2个,如果正好要加密的数据是3的倍数,不需要补0,那么base64后的数据尾处就没有=,如果补了1个0,就有一个=号。 算法如下: int encode_str_size = EVP_EncodeBlock(base64, en, el); int length = EVP_DecodeBlock(base64_out, base64, encode_str_size ); //EVP_DecodeBlock内部同样调用EVP_DecodeInit + EVP_DecodeUpdate + Evp_DecodeFinal实现,但是并未处理尾部的'='字符,因此结果字符串长度总是为3的倍数 while(base64[--encode_str_size] == '=') length--; 算法网友提供,测试正确。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

百里杨

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

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

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

打赏作者

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

抵扣说明:

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

余额充值