目录
前言
AES(Advanced Encryption Standard, 高级加密标准)是一种广泛使用的对称密钥加密算法。它是由比利时密码学家 Joan Daemen 和 Vincent Rijmen 设计的,并在2001年被美国国家标准与技术研究院(NIST)采纳为官方标准。
AES 的主要特点包括:
-
安全性:AES 被认为是非常安全的,至今没有已知的有效攻击可以破解其完整版本。
-
效率:AES 在软件和硬件实现上都非常高效,适合多种计算环境。
-
密钥长度:AES 支持128位、192位和256位的密钥长度,分别对应
AES-128
、AES-192
和AES-256
。 -
块大小:AES 处理的数据块大小固定为128位。
AES 的工作模式也很重要,常见的工作模式包括 ECB(电子密码本模式)、CBC(密码分组链接模式)、CFB(密码反馈模式)、OFB(输出反馈模式)以及更现代的 GCM(Galois/Counter Mode)等。
AES 常用于各种场景,例如网络通信加密、硬盘加密、文件传输加密等。它是现代加密技术的一个基石。
OpenSSL
OpenSSL 是一个强大的安全套件,广泛用于实现安全的网络通信。它基于 SSL 和 TLS 协议,提供了多种加密算法、密码学功能以及安全协议的实现。OpenSSL 不仅可以用来实现服务器端和客户端之间的安全通信,还可以用于加密文件存储、安全电子邮件传输等多种应用场景。
主要功能包括:
-
加密算法:支持多种加密算法,如 AES、DES、3DES、RSA、DSA、ECC 等。
-
安全协议:实现了 SSL/TLS 协议,用于保护网络通信的安全。
-
证书管理:可以用来生成数字证书和公私钥对。
-
随机数生成器:提供了安全的随机数生成器,这对于密码学应用至关重要。
-
密码学哈希函数:支持多种哈希算法,如 SHA-1、SHA-256、SHA-512 等。
-
消息认证码:实现 HMAC 等算法,用于消息完整性验证。
-
密钥协商:支持 Diffie-Hellman 密钥交换等协议。
-
安全套接字层(SSL)/传输层安全(TLS):提供 API 用于实现安全的客户端-服务器通信。
使用 OpenSSL 的常见场景:
-
Web 服务器:使用 SSL/TLS 保护 HTTP 请求。
-
客户端应用程序:实现与 Web 服务器的安全通信。
-
安全电子邮件:使用 S/MIME 协议加密邮件。
-
文件加密:使用 OpenSSL 命令行工具加密文件。
-
安全数据库连接:通过 SSL/TLS 连接数据库服务器。
下面主要是学习通过调用 OpenSSL 接口实现 AES 加密。
EVP接口
EVP
(Encryption and Decryption Primitives)是 OpenSSL 中的一个高级加密接口,它简化了使用 OpenSSL 中的加密算法的过程。EVP 提供了一组简单的函数,可以用来处理各种加密操作,而无需直接与底层的密码算法打交道。
基本流程
-
初始化上下文 (
EVP_CIPHER_CTX
):创建一个新的上下文结构体,并设置加密算法类型。 -
初始化加解/密 (
EVP_EncryptInit_ex
或EVP_DecryptInit_ex
):初始化加密或解密上下文。 -
更新加密/解密操作 (
EVP_EncryptUpdate
或EVP_DecryptUpdate
):实际执行加密或解密操作。 -
最终化加密/解密操作 (
EVP_EncryptFinal_ex
或EVP_DecryptFinal_ex
):完成加密或解密操作,并处理任何剩余的数据。 -
清理上下文 (
EVP_CIPHER_CTX_free
):释放上下文结构体。
接口介绍
头文件
#include <openssl/evp.h> #include <openssl/aes.h>
数据类型
// 使用到的数据类型
// OpenSSL 内部用于处理加密和解密操作的上下文结构,结构体包含了所有用于执行加密或解密操作所需的状态信息
typedef struct evp_cipher_ctx_st EVP_CIPHER_CTX;
// 加密算法的封装,提供了加密算法的基本信息和相关的操作接口,结构体包含了所有用于描述加密算法所需的信息。
typedef struct evp_cipher_st EVP_CIPHER;
初始化加密算法
// 用于设置使用 256 位密钥长度的 AES 加密算法,并采用 CBC(Cipher Block Chaining)模式。
const EVP_CIPHER *cipherType = EVP_aes_256_cbc();
除了 EVP_aes_256_cbc()
,OpenSSL 提供了多种其他接口来支持不同的加密算法和模式,具体可以查看头文件 openssl/evp.h 。
初始化上下文
// 用途:分配一个新的 EVP_CIPHER_CTX 结构体实例。
// 返回值:返回一个新的 EVP_CIPHER_CTX 指针,如果失败则返回 NULL。
EVP_CIPHER_CTX *EVP_CIPHER_CTX_new(void);
// 初始化 EVP_CIPHER_CTX 上下文,初始化结构体参数
void EVP_CIPHER_CTX_init(EVP_CIPHER_CTX *a);
通过密码获取秘钥和IV
下面接口可以从给定的密码(通常是一个字节数组)和盐(salt)生成加密密钥和初始化向量(Initialization Vector, IV);
/*
* 参数说明
* type: 指定要使用的加密算法类型,如 EVP_aes_256_cbc()。
* md: 指定用于密钥扩展的摘要算法,如 EVP_sha256()、EVP_md5()。
* salt: 指向盐的指针,盐是用来增加密码强度的随机数据。
* data: 指向密码的指针,密码通常是一个字节数组。
* datal: 密码的长度(以字节为单位)。
* count: 密钥派生函数的迭代次数,较大的值通常会使攻击者更难以暴力破解密码。
* key: 指向存储生成密钥的缓冲区的指针。
* iv: 指向存储生成初始化向量的缓冲区的指针。
*
* 如果成功,EVP_BytesToKey 返回 1;如果失败,则返回 0。
*/
int EVP_BytesToKey(const EVP_CIPHER *type, const EVP_MD *md,
const unsigned char *salt, const unsigned char *data,
int datal, int count, unsigned char *key,
unsigned char *iv);
这个函数特别适用于基于密码的加密场景,例如当你需要使用一个密码来加密或解密数据时。
加密秘钥和初始化向量值也可以手动指定。
初始化加解/密
/*
* 参数:
* ctx:指向 EVP_CIPHER_CTX 的指针。
* type:指定使用的加密算法。
* impl:可选参数,通常为 NULL,表示使用默认的实现。
* key:密钥。
* iv:初始向量(如果算法需要的话)。
*
* 成功返回 1,失败返回 0
*/
// 初始化 EVP_CIPHER_CTX 以便执行加密/解密操作。
int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher,
ENGINE *impl, const unsigned char *key,
const unsigned char *iv);
int EVP_DecryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher,
ENGINE *impl, const unsigned char *key,
const unsigned char *iv);
更新加密/解密操作
/*
* 参数:
* ctx:指向 EVP_CIPHER_CTX 的指针。
* out:指向缓冲区的指针,用于存放加密/解密后的数据。
* outl:指向整型变量的指针,该变量用于接收加密/解密后数据的实际长度。
* in:指向缓冲区的指针,其中包含待加密/待解密的数据。
* inl:待加密/待解密数据的长度。
*
* 返回值:成功返回 1,失败返回 0。
*/
// 更新加密/解密操作,处理数据块。
int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl,
const unsigned char *in, int inl);
int EVP_DecryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl,
const unsigned char *in, int inl);
最终化加密/解密操作
/*
* 参数:
* ctx:指向 EVP_CIPHER_CTX 的指针。
* out:指向缓冲区的指针,用于存放加密后的数据。
* outl:指向整型变量的指针,该变量用于接收加密后数据的实际长度。
*
* 返回值:成功返回 1,失败返回 0。
*/
// 完成加密/解密操作,并处理最后一个数据块。
int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl);
int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *outm, int *outl);
清理上下文
/*
* 参数:
* ctx:指向 EVP_CIPHER_CTX 的指针。
*/
// 清理 EVP_CIPHER_CTX 结构体的状态
int EVP_CIPHER_CTX_cleanup(EVP_CIPHER_CTX *a);
// 释放 EVP_CIPHER_CTX 结构体实例。
void EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *a);
示例代码
现在我们要使用 AES-256-CBC 加密模式进行加密和解密。
加密部分
/*
*****************************************************************************************
* 函 数 名: aes_encrypt_string
* 功能说明: AES加密字符串
* 形 参: _pPassword : 密码
* _pInput : 输入数据
* _InLen : 输入数据长度
* _pOutBuf : 输出AES编码数据
* _pOutLen : 输出AES编码数据长度
* 返 回 值: 0:成功, -1:失败
*****************************************************************************************
*/
int aes_encrypt_string(char *_pPassword, char *_pInput, int _InLen, char *_pOutBuf, int *_pOutLen)
{
// 上下文结构
EVP_CIPHER_CTX *pEn_ctx = NULL;
int ret = -1;
int flen = 0, outlen = 0;
int i, nrounds = 1;
// 存储秘钥和初始化向量
unsigned char key[32] = {};
unsigned char iv[32] = {};
// 参数判断
if (_pPassword == NULL || _pInput == NULL || _pOutBuf == NULL || _pOutLen == NULL) {
return ret;
}
// 设置使用 256 位密钥长度的 AES 加密算法,并采用 CBC 模式。
const EVP_CIPHER *cipherType = EVP_aes_256_cbc();
if( cipherType == NULL ){
goto clean;
}
/*
* Gen key & IV for AES 256 CBC mode. A SHA1 digest is used to hash the supplied key material.
* nrounds is the number of times the we hash the material. More rounds are more secure but
* slower.
*/
// 通过输入密码产生密钥key和初始化向量iv
i = EVP_BytesToKey(cipherType, EVP_md5(), NULL, _pPassword, strlen(_pPassword), nrounds, key, iv);
if (i != 32) {
printf("Key size is %d bits - should be 256 bits\n", i);
goto clean;
}
pEn_ctx = EVP_CIPHER_CTX_new(); //创建加密上下文
EVP_CIPHER_CTX_init(pEn_ctx); //初始化 EVP_CIPHER_CTX 上下文
EVP_EncryptInit_ex(pEn_ctx, cipherType, NULL, key, iv); //初始化加密操作
/* Update cipher text */
if (!EVP_EncryptUpdate(pEn_ctx, (unsigned char*)_pOutBuf, &outlen,(unsigned char*)_pInput, _InLen)) { //处理数据
perror("\n Error,ENCRYPR_UPDATE:");
goto clean;
}
/* updates the remaining bytes */
if (!EVP_EncryptFinal_ex(pEn_ctx, (unsigned char*)(_pOutBuf + outlen), &flen)) { //完成加密操作,处理剩余字节
perror("\n Error,ENCRYPT_FINAL:");
goto clean;
}
*_pOutLen = outlen + flen;
ret = 0; /* SUCCESS */
clean:
// 清理内存
if( pEn_ctx )
EVP_CIPHER_CTX_cleanup(pEn_ctx);
if( pEn_ctx )
EVP_CIPHER_CTX_free(pEn_ctx);
return ret;
}
解密部分
/*
*****************************************************************************************
* 函 数 名: aes_decrypt_string
* 功能说明: AES解密得到字符串
* 形 参: _pPassword : 密码
* _pInput : 输入需解密的数据
* _InLen : 输入需解密的数据长度
* _pOutBuf : 输出AES解密后的字符串
* _pOutLen : 输出AES编码数据长度
* 返 回 值: 0:成功, -1:失败
*****************************************************************************************
*/
int aes_decrypt_string(char *_pPassword, char *_pInput, int _InLen, char *_pOutBuf, int *_pOutLen)
{
// 上下文结构
EVP_CIPHER_CTX *pDe_ctx = NULL;
int ret = -1;
int flen = 0, outlen = 0;
int i, nrounds = 1;
// 存储秘钥和初始化向量
unsigned char key[32] = {};
unsigned char iv[32] = {};
// 参数判断
if (_pPassword == NULL || _pInput == NULL || _pOutBuf == NULL || _pOutLen == NULL) {
return ret;
}
// 设置使用 256 位密钥长度的 AES 加密算法,并采用 CBC 模式。
const EVP_CIPHER *cipherType = EVP_aes_256_cbc();
if( cipherType == NULL ){
goto clean;
}
/*
* Gen key & IV for AES 256 CBC mode. A SHA1 digest is used to hash the supplied key material.
* nrounds is the number of times the we hash the material. More rounds are more secure but
* slower.
*/
// 通过输入密码产生密钥key和初始化向量iv
i = EVP_BytesToKey(cipherType, EVP_md5(), NULL, _pPassword, strlen(_pPassword), nrounds, key, iv);
if (i != 32) {
printf("Key size is %d bits - should be 256 bits\n", i);
goto clean;
}
pDe_ctx = EVP_CIPHER_CTX_new(); //创建加密上下文
EVP_CIPHER_CTX_init(pDe_ctx); //初始化 EVP_CIPHER_CTX 上下文
EVP_DecryptInit_ex(pDe_ctx, cipherType, NULL, key, iv); //初始化解密操作
/* Update cipher text */
if (!EVP_DecryptUpdate(pDe_ctx, (unsigned char*)_pOutBuf, &outlen, (unsigned char*)_pInput, _InLen)) { //处理数据
perror("\n Error,ENCRYPR_UPDATE:");
goto clean;
}
/* updates the remaining bytes */
if (EVP_DecryptFinal_ex(pDe_ctx, (unsigned char*)(_pOutBuf + outlen), &flen) != 1) { //完成解密操作,处理剩余字节
perror("\n Error,ENCRYPT_FINAL:");
goto clean;
}
*_pOutLen = outlen + flen;
clean:
// 清理内存
if( pDe_ctx )
EVP_CIPHER_CTX_cleanup(pDe_ctx);
if( pDe_ctx )
EVP_CIPHER_CTX_free(pDe_ctx);
return ret;
}
主函数实现
int main(int argc, char *argv[])
{
if( argc < 3 )
{
printf("Usage: %s <STRING> <PASSWORD>\n", argv[0]);
return -1;
}
int i;
char acString[96] = {0};
char acPassword[96] = {0};
char acEncrypt[2048] = {0};
char acDecrypt[2048] = {0};
int enLen = 0, deLen = 0;
strcpy(acString, argv[1]);
strcpy(acPassword, argv[2]);
/* 打印加密的字符串和密码 */
printf("===========Org=============\n");
printf("password: %s\n", acPassword);
printf("string: %s\n", acString);
printf("slen: %d\n", strlen(acString));
printf("===========================\n\n");
/* AES加密 */
printf("===========ENC=============\n");
i = aes_encrypt_string( acPassword, acString, strlen(acString), acEncrypt, &enLen );
if( i < 0 ){
printf("enc error.\n");
return -1;
}
printf("Enc: %s\n", acEncrypt); //打印加密数据
printf("elen: %d\n", enLen);
printf("===========================\n\n");
/* AES解密 */
printf("============DEC=============\n");
i = aes_decrypt_string( acPassword, acEncrypt, enLen, acDecrypt, &deLen );
if( i < 0 ){
printf("dec error.\n");
return -1;
}
acDecrypt[deLen] = 0;
printf("Dec: %s\n", acDecrypt);
//打印解密数据
printf("dlen: %d\n", deLen);
printf("===========================\n\n");
return 0;
}
编写Makefile
# 编译器
CC := gcc
CPP := g++
# 编译选项
#CFLAGS := -Wall -Werror -g
CFLAGS := -g
# 目标文件
TARGET := aesTest
# 源文件
SRCS := main.c
# 头文件路径
INC_DIRS := -I /opt/linux/openssl/include
# 动态库路径
LIB_DIRS := -L /opt/linux/openssl/lib
# 链接的动态库
LD_LIBS := -lcrypto
# 生成目标文件
OBJS := $(SRCS:.c=.o)
# 生成可执行文件
$(TARGET): $(OBJS)
$(CC) $^ -o $@ $(CFLAGS) $(INC_DIRS) $(LIB_DIRS) $(LD_LIBS)
# 生成目标文件
%.o: %.c
$(CC) -c $< -o $@ $(CFLAGS) $(INC_DIRS) $(LIB_DIRS) $(LD_LIBS)
# 清理目标文件和可执行文件
clean:
rm -f $(OBJS) $(TARGET)
执行make,生成可执行文件,运行结果如下:
通过上面的学习,我们可以自己写一个简易的文件加解密的工具,工程用Qt打开;
该工程的源码查看:study: 用于C++的学习同步 - Gitee.com
编译运行后显示主界面如下:
选择文件进行加密,设置输出文件:
点击加密,开始加密,查看加密后的文件:
两个文件的md5值不同,已经加密成功;
下面开始解密,选择加密文件和解密后文件路径,点击解密:
解密后文件的md5值与源文件一样,说明已经解密成功了;
该工程的源码查看:study: 用于C++的学习同步 - Gitee.com