openssl-AES-128-CTR加解密char型数组分析
本文章通过对一个unsigned char*类型的数据做简单的加解密操作来学习如何使用openssl库函数。
openssl为3.0.0,对此前版本的很多函数都不兼容。
加解密源码
#include <openssl/evp.h>
#include <openssl/err.h>
#include <string.h>
#include <openssl/rand.h>
#include <openssl/aes.h>
#include <stdlib.h>
void handleErrors(void) {
ERR_print_errors_fp(stderr);
abort();
}
int main() {
ERR_load_crypto_strings();
OpenSSL_add_all_algorithms();
// 密钥和初始化向量
unsigned char key[AES_BLOCK_SIZE];
unsigned char iv[AES_BLOCK_SIZE];
// 随机生成密钥和初始化向量
RAND_bytes(key, sizeof(key));
RAND_bytes(iv, sizeof(iv));
// 要加密的数据
unsigned char plaintext[] = "The quick brown fox jumps over the lazy dog";
// 加密后的数据
unsigned char ciphertext[sizeof(plaintext)];
// 解密后的数据
unsigned char decryptedtext[sizeof(plaintext)];
// 创建并初始化加解密上下文
EVP_CIPHER_CTX *ctx;
int len;
// 初始化加密上下文
if (!(ctx = EVP_CIPHER_CTX_new())) handleErrors();
// 初始化加密操作
if (1 != EVP_EncryptInit_ex(ctx, EVP_aes_128_ctr(), NULL, key, iv)) handleErrors();
// 执行加密操作
if (1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, sizeof(plaintext))) handleErrors();
EVP_EncryptFinal_ex(ctx, ciphertext + len, &len);
// 清理加密上下文
EVP_CIPHER_CTX_free(ctx);
// 初始化解密上下文
if (!(ctx = EVP_CIPHER_CTX_new())) handleErrors();
// 初始化解密操作
if (1 != EVP_DecryptInit_ex(ctx, EVP_aes_128_ctr(), NULL, key, iv)) handleErrors();
// 执行解密操作
if (1 != EVP_DecryptUpdate(ctx, decryptedtext, &len, ciphertext, sizeof(ciphertext))) handleErrors();
EVP_DecryptFinal_ex(ctx, decryptedtext + len, &len);
// 清理解密上下文
EVP_CIPHER_CTX_free(ctx);
// 打印解密后的数据
printf("Decrypted text: %s\n", decryptedtext);
// 清理 OpenSSL
ERR_free_strings();
return 0;
}
运行脚本
#!/bin/bash
gcc aesctr.c -o aesctr -lcrypto
./aesctr
输出的数据与加密前的数据一样,当然这只是加密字符串可以如此简单,但是如果数据是结构体呢?
代码整体分析
这段代码是一个使用 OpenSSL 库进行 AES-128-CTR 模式加密和解密的 C 程序。下面是代码的详细解析:
-
包含头文件:
<openssl/evp.h>
:提供加密算法的封装。<openssl/err.h>
:提供错误处理功能。<openssl/rand.h>
:提供随机数生成功能。<openssl/aes.h>
:提供 AES 加密算法的函数。<stdlib.h>
:提供标准库函数,如abort()
。
-
错误处理函数:
handleErrors
:当遇到错误时,打印错误信息并终止程序。
-
主函数
main
:ERR_load_crypto_strings
:加载错误字符串,以便ERR_print_errors_fp
函数可以打印出可读的错误信息。OpenSSL_add_all_algorithms
:注册所有 OpenSSL 加密算法。
-
密钥和初始化向量:
unsigned char key[AES_BLOCK_SIZE]
:定义一个 128 位(16 字节)的密钥数组。unsigned char iv[AES_BLOCK_SIZE]
:定义一个 128 位(16 字节)的初始化向量(IV)数组。
-
生成密钥和 IV:
RAND_bytes(key, sizeof(key))
:生成随机密钥。RAND_bytes(iv, sizeof(iv))
:生成随机 IV。
-
定义数据:
unsigned char plaintext[]
:定义要加密的明文数据。unsigned char ciphertext[sizeof(plaintext)]
:定义存储加密后数据的数组。unsigned char decryptedtext[sizeof(plaintext)]
:定义存储解密后数据的数组。
-
加密和解密上下文:
EVP_CIPHER_CTX *ctx
:定义一个加密上下文指针,用于存储加密和解密过程中的状态信息。
-
加密过程:
EVP_CIPHER_CTX_new
:创建一个新的加密上下文。EVP_EncryptInit_ex
:初始化加密操作,设置加密算法为 AES-128-CTR,使用密钥和 IV。EVP_EncryptUpdate
:执行加密操作,将明文数据加密到ciphertext
数组中。EVP_EncryptFinal_ex
:完成加密操作,处理任何剩余的数据。
-
解密过程:
- 与加密过程类似,但使用
EVP_DecryptInit_ex
、EVP_DecryptUpdate
和EVP_DecryptFinal_ex
函数进行解密。
- 与加密过程类似,但使用
-
打印解密后的数据:
printf
:打印解密后的文本。
-
清理 OpenSSL:
ERR_free_strings
:释放加载的错误字符串。
-
返回:
return 0
:程序正常退出。
这个程序演示了如何使用 OpenSSL 库进行 AES-128-CTR 模式的加密和解密。它首先生成随机的密钥和 IV,然后使用这些密钥和 IV 对明文数据进行加密,最后使用相同的密钥和 IV 对密文数据进行解密,以验证加密和解密过程的正确性。
EVP_CIPHER_CTX_new()初始化加密上下文
EVP_CIPHER_CTX_new
是 OpenSSL 库中的一个函数,用于创建一个新的加密上下文(EVP_CIPHER_CTX
)。这个上下文用于存储加密或解密操作的状态,包括算法特定的信息、密钥、初始化向量(IV)、加密模式等。
函数原型如下:
EVP_CIPHER_CTX *EVP_CIPHER_CTX_new(void);
这个函数不接受任何参数,并返回一个指向新创建的 EVP_CIPHER_CTX
结构的指针。如果内存分配失败,将返回 NULL
。
EVP_EncryptInit_ex加密初始化
EVP_EncryptInit_ex
是 OpenSSL 库中的一个函数,用于初始化加密操作。这个函数是加密过程中的第一步,它准备加密上下文(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);
参数说明:
ctx
:指向EVP_CIPHER_CTX
结构的指针,这个结构用于存储加密过程中的状态信息。cipher
:指向EVP_CIPHER
结构的指针,这个结构指定了要使用的加密算法和模式。impl
:指向ENGINE
结构的指针,用于指定加密操作可能使用的硬件加速引擎。如果为NULL
,则使用默认的软件实现。key
:指向密钥的指针。密钥的长度和格式取决于所选的加密算法。iv
:指向初始化向量的指针。初始化向量用于某些加密模式(如 CBC、CTR 等),以确保即使相同的明文也不会产生相同的密文。如果加密模式不需要 IV,这个参数可以为NULL
。
返回值:
- 成功时返回
1
。 - 失败时返回
0
,并可通过ERR_get_error
函数获取错误信息。
EVP_EncryptUpdate执行加密操作
EVP_EncryptUpdate
是 OpenSSL 库中的一个函数,用于执行加密操作的中间步骤。这个函数用于处理要加密的数据块,通常在初始化加密操作(使用 EVP_EncryptInit_ex
)之后和完成加密操作(使用 EVP_EncryptFinal_ex
)之前调用。
函数原型如下:
int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, const unsigned char *in, int inl);
参数说明:
ctx
:指向之前通过EVP_EncryptInit_ex
初始化的EVP_CIPHER_CTX
结构的指针。out
:指向用于存储加密数据的缓冲区的指针。加密的数据将被写入这个缓冲区。outl
:一个整数指针,用于在输入时指定out
缓冲区的大小,并在输出时存储实际写入out
缓冲区的数据大小。in
:指向要加密的数据的指针。inl
:指定in
指向的数据的大小(以字节为单位)。
返回值:
- 成功时返回
1
。 - 失败时返回
0
,并可通过ERR_get_error
函数获取错误信息。
EVP_EncryptFinal_ex完成剩余加密操作
EVP_EncryptFinal_ex
是 OpenSSL 库中的一个函数,它用于完成加密操作。在调用 EVP_EncryptUpdate
处理完所有要加密的数据后,你会调用这个函数来完成加密过程,它会处理任何剩余的数据块并确保加密过程正确结束。
函数原型如下:
int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl);
参数说明:
ctx
:指向之前通过EVP_EncryptInit_ex
和EVP_EncryptUpdate
进行初始化和更新的EVP_CIPHER_CTX
结构的指针。out
:指向用于存储最后一部分加密数据的缓冲区的指针。如果加密算法在最后需要填充(如块加密模式),填充的数据也会被写入这个缓冲区。outl
:一个整数指针,用于在输入时指定out
缓冲区的大小,并在输出时存储实际写入out
缓冲区的数据大小。
返回值:
- 成功时返回
1
。 - 失败时返回
0
,并可通过ERR_get_error
函数获取错误信息。
EVP_CIPHER_CTX_free释放初始化上下文
EVP_CIPHER_CTX_free
是 OpenSSL 库中的一个函数,用于释放之前通过 EVP_CIPHER_CTX_new
创建的加密上下文(EVP_CIPHER_CTX
)所占用的内存资源。这个函数是管理 OpenSSL 加密操作中内存使用的重要部分,确保在完成加密或解密操作后,及时释放分配的内存,避免内存泄漏。
函数原型如下:
void EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *ctx);
参数说明:
ctx
:指向EVP_CIPHER_CTX
结构的指针。这个结构是在之前通过EVP_CIPHER_CTX_new
创建的,用于存储加密或解密操作的状态和参数。
解密
解密与加密差不多
// 初始化解密上下文
if (!(ctx = EVP_CIPHER_CTX_new())) handleErrors();
// 初始化解密操作
if (1 != EVP_DecryptInit_ex(ctx, EVP_aes_128_ctr(), NULL, key, iv)) handleErrors();
// 执行解密操作
if (1 != EVP_DecryptUpdate(ctx, decryptedtext, &len, ciphertext, sizeof(ciphertext))) handleErrors();
EVP_DecryptFinal_ex(ctx, decryptedtext + len, &len);
// 清理解密上下文
EVP_CIPHER_CTX_free(ctx);
错误处理函数
您提供的代码是一个简单的错误处理函数,通常用于 OpenSSL 或其他需要处理错误堆栈的库。这个函数的作用是打印错误信息到标准错误输出(stderr
),然后终止程序。下面是对这段代码的详细解释:
-
ERR_print_errors_fp(stderr);
- 这行代码调用
ERR_print_errors_fp
函数,该函数是 OpenSSL 库中的一个函数,用于打印错误信息。 stderr
是标准错误输出流,它用于输出错误信息和调试信息。- 这个函数会打印出所有未被处理的错误到
stderr
。
- 这行代码调用
-
abort();
- 这行代码调用
abort
函数,该函数是 C 标准库中的一个函数,用于立即终止程序。 - 当
abort
被调用时,它会生成一个SIGABRT
信号,如果没有捕获该信号的信号处理函数,程序将被终止。
- 这行代码调用
这个错误处理函数通常在 OpenSSL 函数调用失败时调用,以确保程序在遇到错误时能够优雅地终止,并提供足够的错误信息供调试使用。