目录
1 AES简介
对称加密是一种广泛使用的数据加密算法,它使用相同的密钥进行加密和解密。
AES (Advanced Encryption Standard) 加密算法就是一种广泛使用的对称加密算法。该算法用来替代原先的DES,已经被多方分析且广为全世界所使用。经过五年的甄选流程,AES由美国国家标准与技术研究院(NIST)于2001年11月26日发布于FIPS PUB 197,并在2002年5月26日成为有效的标准。2006年,AES已然成为对称密钥加密中最流行的算法之一。
AES加密算法的一个数据区块的长度为128位(16字节),如果待加密数据的长度是不是区块的倍数,一般需要指定填充方式(指明不需要填充的除外), 对待加密数据进行对齐。
AES加密密钥的长度支持128位(16字节),192位(24字节),256(32字节)位三种模式。密钥的长度必须是这三个长度之一。
1.1 加密模式
ECB:电码本模式(Electronic Codebook Book),这种模式是将整个明文分成若干段相同的小段,然后对每一小段进行加密。 需要填充数据进行对齐,安全性较弱,不推荐使用。
CBC:密码分组链接模式(Cipher Block Chaining),为了克服ECB的弱点,CBC模式使用初始化向量(IV)来增强加密。首先,CBC将明文块xor与IV一起使用, 然后将结果加密到密文块。 在下一个块中,它使用加密结果与明文块异或,直到最后一个块。 在这种模式下,加密不能并行化,但解密可以并行化。需要填充数据进行对齐。
CTR:计算器模式(Counter),这种模式将计数器的值用作IV。它与OFB非常相似,但每次都使用计数器而不是IV进行加密。 这种模式有两个优点,包括加密/解密并行化,并且一个块中的噪声不会影响其他块。
OFB:输出反馈模式(Output FeedBack),此模式可用作流式加密。首先,它对IV进行加密,然后使用加密结果对明文进行异或运算,得到密文。 它不需要填充数据,也不会受到噪声块的影响。
CFB:密码反馈模式(Cipher FeedBack),此模式可用作流式加密。 首先,它对IV进行加密,然后它将与明文块进行异或运算以获得密文。然后CFB对加密结果进行xor加密。需要IV初始化向量。 在这种模式下,解密可以并行化,但加密不能并行化。
GCM:伽罗瓦/计数器模式(Galois/Counter Mode),该模式是CTR模式的扩展。 GCM受到了广泛关注,并由NIST推荐。GCM模型输出密文和认证标签。与算法的其他操作模式相比,这种模式的主要优点是效率高。 该模式需要IV初始化向量和TagLen长度。
1.2 填充方式
选择是否填充,以及使用何种填充方式进行数据块对齐。
1.3 秘钥
AES加密解密使用的秘钥,会使用选定的字符集将输入的秘钥转换为字节数组,字节数组的长度限定为16,24,或者32。
1.4 偏移量
初始化向量IV,ECB模式不支持该字段。IV的长度为128位(16字节)。
2 OpenSSL EVP代码实现
OpenSSL EVP(high-level cryptographic functions)提供了丰富的密码学中的各种函数。OpenSSL中实现了各种对称算法、摘要算法以及签名/验签算法。EVP 函数将这些具体的算法进行了封装。 EVP系列的函数的声明包含在”evp.h”里面,这是一系列封装了OpenSSL加密库里面所有算法的函数。通过这样的统一的封装,使得只需要在初始化参数的时候做很少的改变,就可以使用相同的代码但采用不同的加密算法进行数据的加密和解密。 EVP系列函数主要封装了加密、摘要、编码三大类型的算法,使用算法前需要调OpenSSL_add_all_algorithms函数。
2.1 基本数据结构
EVP_CIPHER与EVP_CIPHER_CTX两个基本结构,加密函数EVP_Encrypt(EVP_Cipher)一些列函数都是以这两个结构为基础实现了。文件evp_enc.c是最高层的封装实现,,而各个e_*.c文件则是真正实现了各种算法的加解密功能,当然它们其实也是一些封装函数,真正的算法实现在各个算法同名目录里面的文件实现。
注意: EVP_CIPHER是、EVP_CIPHER_CTX的成员,在加密时通过指定的加密算法(其实就是加密函数),返回对应的EVP_CIPHER的指针,然后EVP_EncryptInit函数中 调用 EVP_CIPHER来初化EVP_CIPHER_CTX。
2.1.1 EVP_CIPHER结构体
#include<openssl/evp.h>
typedef struct evp_cipher_st
{
// 是算法类型的nid识别号,openssl里面每个对象都有一个内部唯一的识别ID
int nid;
// 是每次加密的数据块的长度,以字节为单位
int block_size;
// 是每次加密的数据块的长度,以字节为单位
int key_len;
// 初始化向量的长度
int iv_len;
// 标志位
unsigned long flags;
// 算法结构初始化函数,可以设置为加密模式还是解密模式
int (*init)(EVP_CIPHER_CTX *ctx, const unsigned char *key, const unsigned char *iv, int enc);
// 进行数据加密或解密的函数
int (*do_cipher)(EVP_CIPHER_CTX *ctx, unsigned char *out, const unsigned char *in, unsigned int inl);
// 释放EVP_CIPHER_CTX结构里面的数据和设置
int (*cleanup)(EVP_CIPHER_CTX *);
// 设定ctx->cipher_data数据的长度
int ctx_size;
// 在EVP_CIPHER_CTX结构中通过参数设置一个ASN1_TYPE
int (*set_asn1_parameters)(EVP_CIPHER_CTX *, ASN1_TYPE *);
// 从一个ASN1_TYPE中取得参数
int (*get_asn1_parameters)(EVP_CIPHER_CTX *, ASN1_TYPE *);
// 其它各种操作函数
int (*ctrl)(EVP_CIPHER_CTX *, int type, int arg, void *ptr);
// 应用数据
void *app_data;
}EVP_CIPHER;
2.1.2 EVP_CIPHER_CTX结构体
#include<openssl/evp.h>
typedef struct evp_cipher_ctx_st
{
const EVP_CIPHER *cipher; // 是该结构相关的一个EVP_CIPHER算法结构
ENGINE *engine; // 如果加密算法是ENGINE提供的,那么该成员保存了相关的函数接口
int encrypt; // 加密或解密的标志
int buf_len; // 该结构缓冲区里面当前的数据长度
unsigned char oiv[EVP_MAX_IV_LENGTH]; // 初始的初始化向量
unsigned char iv[EVP_MAX_IV_LENGTH]; // 工作时候使用的初始化向量
unsigned char buf[EVP_MAX_BLOCK_LENGTH]; // 保存下来的部分需要数据
int num; // 在cfb/ofb模式的时候指定块长度
void *app_data; // 应用程序要处理数据
int key_len; // 密钥长度,算法不一样长度也不一样
unsigned long flags;
void *cipher_data; // 加密后的数据
int final_used;
int block_mask;
unsigned char final[EVP_MAX_BLOCK_LENGTH]; //
} EVP_CIPHER_CTX;
2.2 加解密接口函数
/*************************************************************
功 能:创建密码上下文
参 数:a - [out] 密码上下文
返 回:
*************************************************************/
void EVP_CIPHER_CTX_init(EVP_CIPHER_CTX *a);
/*************************************************************
功 能:清除密码上下文中的所有信息并释放与其关联的任何已分配内存,包括ctx本身;
应在使用密码的所有操作完成后调用此函数,以便敏感信息不回保留在内存中。
参 数:a - [in] 密码上下文
返 回:
*************************************************************/
int EVP_CIPHER_CTX_cleanup(EVP_CIPHER_CTX *a);
/*************************************************************
功 能:初始化加密上下文ctx
参 数: ctx - [in/out] 由EVP_CIPHER_CTX_init 创建
cipher - [in] 使用的算法类型,例如:EVP_aes_256_cbc()、EVP_aes_128_cbc()
impl - [in] 密码类型,如果impl为 NULL,则使用默认实现。一般都设置为NULL
key - [in] 密钥
iv - [in] 偏移量
返 回:1 - 成功;0 - 失败
*************************************************************/
int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher,
ENGINE *impl, const unsigned char *key,
const unsigned char *iv);
/*************************************************************
功 能:加密
参 数: ctx - [in] 由EVP_CIPHER_CTX_init 创建
out - [out]输出密文数据
outl - [out]输出密文数据长度
in - [in] 输入明文数据
inl - [in] 输入明文数据长度
返 回:1 - 成功;0 - 失败
*************************************************************/
int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl,
const unsigned char *in, int inl);
/*************************************************************
功 能:缓冲区中剩余的数据加密,必须在 EVP_EncryptUpdate() 之后调用。
参 数: ctx - [in] 由EVP_CIPHER_CTX_init 创建
out - [out]缓冲区中剩余的数据加密的密文数据
outl - [out]缓冲区中剩余的数据加密的密文数据长度
返 回:1 - 成功;0 - 失败
*************************************************************/
int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl);
/*************************************************************
功 能:初始化解密上下文ctx
参 数: ctx - [in/out] 由EVP_CIPHER_CTX_init 创建
cipher - [in] 使用的算法类型,例如:EVP_aes_256_cbc()、EVP_aes_128_cbc()
impl - [in] 密码类型,如果impl为 NULL,则使用默认实现。一般都设置为NULL
key - [in] 密钥
iv - [in] 偏移量
返 回:1 - 成功;0 - 失败
*************************************************************/
int EVP_DecryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher,
ENGINE *impl, const unsigned char *key,
const unsigned char *iv);
/*************************************************************
功 能:解密
参 数: ctx - [in] 由EVP_CIPHER_CTX_init 创建
out - [out]输出明文数据
outl - [out]输出明文数据长度
in - [in] 输入密文数据
inl - [in] 输入密文数据长度
返 回:1 - 成功;0 - 失败
*************************************************************/
int EVP_DecryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl,
const unsigned char *in, int inl);
/*************************************************************
功 能:缓冲区中剩余的数据解密,必须在 EVP_DecryptUpdate() 之后调用。
参 数: ctx - [in] 由EVP_CIPHER_CTX_init 创建
out - [out]缓冲区中剩余的数据解密的明文数据
outl - [out]缓冲区中剩余的数据解密的明文数据长度
返 回:1 - 成功;0 - 失败
*************************************************************/
int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *outm, int *outl);
2.3 填充方式说明
OpenSSL默认使用PKCS7Padding的填充方式。
使用EVP接口实现对称加密的时候有个EVP_CIPHER_CTX_set_padding()函数,看上去能给加密上下文ctx设置不同的padding模式。
int EVP_CIPHER_CTX_set_padding(EVP_CIPHER_CTX *c, int pad);
但是实际上在现在的版本中它只能把默认的pkcs7/pkcs5模式改成nopadding模式, 看evp_enc.c里面的实现就知道,EVP_CIPHER_CTX_set_padding这里只区分了pad是不是0,set成0是nopadding,set成非0就是pkcs7padding。
int EVP_CIPHER_CTX_set_padding(EVP_CIPHER_CTX *ctx, int pad)
{
int ok;
OSSL_PARAM params[2] = { OSSL_PARAM_END, OSSL_PARAM_END };
unsigned int pd = pad;
if (pad)
ctx->flags &= ~EVP_CIPH_NO_PADDING;
else
ctx->flags |= EVP_CIPH_NO_PADDING;
if (ctx->cipher != NULL && ctx->cipher->prov == NULL)
return 1;
params[0] = OSSL_PARAM_construct_uint(OSSL_CIPHER_PARAM_PADDING, &pd);
ok = evp_do_ciph_ctx_setparams(ctx->cipher, ctx->algctx, params);
return ok != 0;
}
其他padding模式openssl是不支持了,如果实际有需要自己给消息做好padding,然后再用EVP_CIPHER_CTX_set_padding把ctx设置成nopadding就好了。
3 演示Demo
3.1 开发环境
-
OpenSSL 1.0.2l
-
Visual Studio 2015
-
Windows 10 Pro x64
3.2 功能介绍
演示程序主界面如下图所示,包括加密算法设置,输入/输出数据格式设置以及加密与解密等功能。
支持aes-128-cbc、aes-128-ecb、aes-192-cbc、aes-192-ecb、aes-256-cbc·、aes-256-ecb等AES加解密算法。
支持String(文本)、Hex(十六进制)、Base64等多种数据格式。
3.3 下载地址
开发环境:
-
Windows 10 pro x64
-
Visual Studio 2015
-
OpenSSL 1.0.2l