首页 > Note > 使用 openssl 进行 AES 加解密

使用 openssl 进行 AES 加解密

2019年1月8日 发表评论 阅读评论

在我之前的一篇博客里介绍了 对称加密的模式 . 这里主要聊一聊如何使用 openssl 来进行 AES 加密 .

一. OPENSSL crypto API

openssl 加密 API 分两个部分: High Level and Low Level . 对于大部分人来说,使用 High Level 就够用了, 这些 API 被冠以 EVP (Envelope) ,表示对 Low Level 的封装。High Level API 提供了包括 对称/非对称加解密签名, 验证, 哈希MAC 等一系列组件,屏蔽了 Low Level API 的复杂逻辑,使用起来安全高效。对于除非有需要进行加密算法级别的改进,否则不建议使用 Low Level API.
大部分 EVP API 有一个 int 型返回值 ,用来表示操作是否成功:1 表示成功, 0 表示失败。但有些时候也会返回 -1 ,表达如内存分配或者其他什么错误。官方指导代码如下:

    if(1 != EVP_xxx()) goto err;
    if(1 != EVP_yyy()) goto err;

    /* ... do some stuff ... */

err:
    ERR_print_errors_fp(stderr);

二. 使用 EVP API 进行 AES 加解密

对于 AES 加解密,EVP API 分为两种,EVP_Encrypt / EVP_Decryp 系列 和 EVP_Cipher 系列。后者是对前者进一步的封装。它们具体使用的套路都是一样的:

  1. 创建加解密上下文 EVP_CIPHER_CTX
  2. 调用 xxxInit() 函数,使用 key(密钥)、iv(前置向量)cipher(算法) 对上下文初始化
  3. 调用 xxxUpdate() 函数进行加解密。该函数支持流式操作。即对于一段明文来说,分成多组按顺序进行加密,和一次性全部加密,不影响其生成的密文的正确性。
  4. 调用 xxxFinal() 函数获取上下文中遗留的信息
  5. 释放上下文, 完成加解密。

示例代码:

#include <iostream>
#include <cassert>
#include <cmath>
#include <openssl/aes.h>
#include <openssl/evp.h>
#include <openssl/kdf.h>

const char *key = "wandoer.com_wandoer.com_wandoer.";
char iv[16] = { '_','i','v','_','m','u','s','t','_','b','e','_','1','6','B','_' };
const char *plain_text = "The EVP interface supports the ability to perform authenticated encryption and decryption, as well as the option to attach unencrypted,"
                         " associated data to the message. Such Authenticated-Encryption with Associated-Data (AEAD) schemes provide confidentiality by encrypting the data, and also"
                         " provide authenticity assurances by creating a MAC tag over the encrypted data. The MAC tag will ensure the data is not accidentally altered or maliciously"
                         " tampered during transmission and storage.";

template<typename CIPHER>
bool encrypt(CIPHER cipher,const char* key, const char* iv, const unsigned char *in, int in_len, unsigned char *out, int *out_len) {
    bool ret = false;

    //创建加密上下文
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    int tmp = 0;

    //初始化加密上下文
    int rst = EVP_EncryptInit(ctx, cipher, reinterpret_cast<const unsigned char *>(key),
                              reinterpret_cast<const unsigned char *>(iv));
    if (rst != 1) goto RET;

    //加密操作
    rst = EVP_EncryptUpdate(ctx, out, &tmp, in, in_len);
    if (rst != 1) goto RET;
    *out_len = tmp;
        
    //获取加密上下文中遗留的信息
    rst = EVP_EncryptFinal(ctx, out + tmp, &tmp);
    if (rst != 1) goto RET;
    if (tmp > 0) {
        *out_len += tmp;
    }

    ret = true;

RET:
    //释放加密上下文
    EVP_CIPHER_CTX_free(ctx);
    ctx = NULL;
    return ret;
}

template<typename CIPHER>
bool decrypt(CIPHER cipher, const char* key, const char* iv, const unsigned char *in, int in_len, unsigned char *out, int *out_len) {
    bool ret = false;
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    int tmp = 0;
    int rst = EVP_DecryptInit(ctx, cipher, reinterpret_cast<const unsigned char *>(key),
                              reinterpret_cast<const unsigned char *>(iv));
    if (rst != 1) goto RET;

    rst = EVP_DecryptUpdate(ctx, out, &tmp, in, in_len);
    if (rst != 1) goto RET;
    *out_len = tmp;

    rst = EVP_DecryptFinal(ctx, out + tmp, &tmp);
    if (rst != 1) goto RET;
    if (tmp > 0) {
        *out_len += tmp;
    }

    ret = true;

RET:
    EVP_CIPHER_CTX_free(ctx);
    ctx = NULL;
    return ret;
}

template<typename CIPHER>
bool do_crypto(CIPHER cipher, const char* key, const char* iv, const unsigned char* in, int in_len, unsigned char* out, int* out_len, bool encrypt) {
    bool ret = false;
    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
    int tmp = 0;
    int rst = EVP_CipherInit(ctx, cipher, reinterpret_cast<const unsigned char*>(key), reinterpret_cast<const unsigned char*>(iv), encrypt ? 1 : 0);
    if (rst != 1) goto RET;
    rst = EVP_CipherUpdate(ctx, out, &tmp, in, in_len);
    if (rst != 1) goto RET;
    *out_len = tmp;

    rst = EVP_CipherFinal(ctx, out + tmp, &tmp);
    if (rst != 1) goto RET;
    if (tmp > 0) {
        *out_len += tmp;
    }

    ret = true;

RET:
    EVP_CIPHER_CTX_free(ctx);
    ctx = NULL;
    return ret;
}


int main(void) {
    //TODO init OPENSSL 

    // EVP_CIPHER 是 OPENSSL 内置的一系列对称加密算法。可以使用 EVP_xxx_() 来获取,使用后无需释放。
    // OPENSSL 具体提供哪些内置算法可以在 evp.h 里查看
    const EVP_CIPHER *cipher = EVP_aes_256_cfb();
    

    // 计算加解密结果 buffer 的大小。AES 对称加密后数据的大小与加密前相同。
    // 考虑到可能有 padding 存在,大小为大于输入的,最小的 AES_BLOCK_SIZE 倍整数。
    int out_len = ceil((float)strlen(plain_text) / AES_BLOCK_SIZE) * AES_BLOCK_SIZE;
    unsigned char *cipher_text = (unsigned char *)malloc(out_len);
    unsigned char *re_plain_text = (unsigned char *)malloc(out_len);

    //实际加解密输出的 buffer 大小
    int enc_len = 0, dec_len = 0; 


    /** Method 1
    * 使用 EVP_Encrypt / EVP_Decrypt 系列函数
    */
    memset(cipher_text, 0, out_len);
    memset(re_plain_text, 0, out_len);
    bool encryped = encrypt(cipher,key,iv, reinterpret_cast<const unsigned char *>(plain_text),
                                strlen(plain_text), cipher_text, &enc_len);
    assert(encryped);
    
    bool decrypted = decrypt(cipher,key, iv, cipher_text, enc_len, re_plain_text, &dec_len);
    assert(decrypted);
    if (dec_len < out_len)
        re_plain_text[dec_len] = '\0';

    int cmp = strcmp(plain_text, reinterpret_cast<char *>(re_plain_text));
    assert(cmp == 0);

    /** Method 2
    * 使用 EVP_Cipher 系列函数
    */
    memset(cipher_text, 0, out_len);
    memset(re_plain_text, 0, out_len);
    encryped = do_crypto(cipher, key, iv, reinterpret_cast<const unsigned char*>(plain_text), strlen(plain_text), cipher_text, &enc_len, true);
    assert(encryped);
    decrypted = do_crypto(cipher, key, iv, cipher_text, enc_len, re_plain_text, &dec_len, false);
    assert(decrypted);
    cmp = strcmp(plain_text, reinterpret_cast<char*>(re_plain_text));
    assert(cmp == 0);

    free(cipher_text);
    free(re_plain_text);

    //TODO  free OPENSSL

    return 0;
}

需要注意的地方有:

  1. key 和 iv 的长度。我们知道 AES 一个组的大小是 16Byte, 所以 iv 的长度为 16Byte 。key 的长度随着选择的 cipher 的不同而不同,如 AES_128_CFB 表达 key 长度为 128 位, AES_256_CFB 表示 key 长度为 256 位。如果 key 或 iv 的长度过长,超出的部分不会被使用,反之如果过短,则结果不可预测。 如果不清楚 key 和 iv 到底需要多长,可以使用 EVP_CIPHER_iv_length() / EVP_CIPHER_key_length() 来获取。
  2. 加解密上下文 EVP_CIPHER_CTX 要记得 释放,否则遗留在内存中的上下文信息可以会泄漏机密。

到这里,使用 OPENSSL 进行 AES 加解密已经基本讲完了。没错就是这么简单。如果想做更深一个层次的了解,可以看看以下部分。

三. 使用 Low Level API 进行 AES 加解密

我们知道,一个完整的 AES 分组加解密方法分为两个部分,一是 AES 部分, 二是分组模式部分。

  • 在 OPENSSL 中,AES 算法部分在 aes.h 中,为 AES_encrypt / AES_decrypt 。它是 AES 加解密的核心部分,每次处理 16 个字节(128位) 的数据加解密。
  • 分组与模式算法在 modes.h 中(具体实现在 openssl/crypto/modes 路径下) ,如 CRYPTO_cfb128_encrypt,它将数据分成每 16 字节一组,再将每一组/组与组之间进行不同的模式的计算

示例代码:

#include <iostream>
#include <cassert>
#include <openssl/aes.h>

const char *key = "wandoer.com_wandoer.com_wandoer.";
char iv[16] = { '_','i','v','_','m','u','s','t','_','b','e','_','1','6','B','_' };
const char *plain_text = "The EVP interface supports the ability to perform authenticated encryption and decryption, as well as the option to attach unencrypted,"
" associated data to the message. Such Authenticated-Encryption with Associated-Data (AEAD) schemes provide confidentiality by encrypting the data, and also"
" provide authenticity assurances by creating a MAC tag over the encrypted data. The MAC tag will ensure the data is not accidentally altered or maliciously"
" tampered during transmission and storage.";

bool aes_256_crypto(const char* key, const char* iv, const unsigned char* in, unsigned char* out, int len, bool encrypt) {
    bool ret = false;
    AES_KEY aes_key;
    int num = 0;
    unsigned char* uiv = (unsigned char*)malloc(strlen(iv));
    memcpy(uiv, iv, strlen(iv));

    int rst = AES_set_encrypt_key(reinterpret_cast<const unsigned char*>(key), 256, &aes_key);
    if (rst != 0) goto ERR;

    AES_cfb128_encrypt(reinterpret_cast<const unsigned char*>(in), out, len, &aes_key, uiv, &num, encrypt ? 1 : 0);

    ret = true;

ERR:
    free(uiv);
    return ret;
}


int main() {
    int len = strlen(plain_text);
    unsigned char* cipher_text = (unsigned char*)malloc(len);
    unsigned char* re_plain_text = (unsigned char*)malloc(len);
    memset(cipher_text, 0, len);
    memset(re_plain_text, 0, len);

    aes_256_crypto(key, iv, reinterpret_cast<const unsigned char*>(plain_text), cipher_text, len, true);

    aes_256_crypto(key, iv, cipher_text, re_plain_text, len, false);
    
    int cmp = memcmp(plain_text, re_plain_text, len);
    assert(cmp == 0);

    free(cipher_text);
    free(re_plain_text);

    return 0;
}

需要注意,在 AES_cfb128_encrypt() 函数中的参数 iv 是一个 non const 指针。这表示 iv 是会发生变化的。AES_cfb128_encrypt 同样支持流操作,在第二次及以后的操作中需要注意传入前一次操作之后的 iv(如果了解对称加密分组的原理,则不难理解这何要这样做)。

 

 

 

  1. 本文目前尚无任何评论.
  1. 本文目前尚无任何 trackbacks 和 pingbacks.