解决OpenSSL调用EVP_EncryptUpdate进行解密,返回长度少一个block_size的问题

本文介绍了解决OpenSSL解密过程中返回长度比预期少16字节的问题。通过关闭填充功能并确保输入长度符合块大小的整数倍,可以避免长度减少的情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        最近在参考OpenSSL的man3中的调用示例调用EVP_EncryptUpdate的解密功能时,发现返回的长度总是少16字节(以AES-128-ecb为例),于是看OpenSSL源码,遂发现在evp_enc.c中的EVP_DecryptUpdate函数中在发现ctx->flags若有PADDING的情况下可能对解密返回的数据长度减去一个block_size的大小,应该是没有走EVP_CIPH_NO_PADDING的分支。因此需要调用EVP_CIPHER_CTX_set_padding函数强制设置ctx为NO_PADDING。

调用实例:

EVP_CIPHER_CTX *ctx;
unsigned char outbuf[1024];                                                       //缓冲区用于存放解密得到的明文
unsigned char inbuf[1024] = {0x01, 0x02, 0x03, 0x04............};  //这里填密文
int inlen,outlen;
inlen = 1024;                                                                                //密文长度为1024字节
ctx = EVP_CIPHER_CTX_new();
EVP_CipherInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, iv, 0);  //最后的0表示解密,key为密钥,iv为IV,在ecb模式下iv为空
EVP_CIPHER_CTX_set_padding(ctx, 0);                                     //最后的0表示不padding
EVP_CipherUpdate(ctx, outbuf, &outlen, inbuf, inlen);                 //此时outlen应该就和inlen是一致的了
EVP_CIPHER_CTX_free(ctx);
 

        注意,使用了不padding之后,就不需要调用EVP_CipherFinal_ex函数来结束整个contex了,且每次输入的长度要满足这个算法的块大小的整数倍

下面代码生成的encrypted.bin文件是哪个文件加密后的结果:#include <openssl/evp.h> #include <openssl/pem.h> #include <openssl/rand.h> #include <openssl/err.h> #include <openssl/applink.c> #include <stdio.h> #include <stdlib.h> #define AES_KEY_SIZE 32 #define RSA_KEY_SIZE 2048 #define GCM_IV_LEN 12 #define GCM_TAG_LEN 16 #define CHECK(cond, msg) do { \ if (!(cond)) { \ fprintf(stderr, "错误: %s (位置: %s:%d)\n", msg, __FILE__, __LINE__); \ ERR_print_errors_fp(stderr); \ exit(EXIT_FAILURE); \ } \ } while(0) EVP_PKEY* load_public_key(const char* pubkey_path) { BIO* bio = BIO_new_file(pubkey_path, "r"); CHECK(bio != NULL, "无法打开公钥文件"); EVP_PKEY* pubkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL); CHECK(pubkey != NULL, "解析公钥失败"); BIO_free(bio); return pubkey; } unsigned char* rsa_encrypt(EVP_PKEY* pubkey, const unsigned char* aes_key, size_t* encrypted_len) { EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pubkey, NULL); CHECK(ctx != NULL, "创建RSA上下文失败"); CHECK(EVP_PKEY_encrypt_init(ctx) > 0, "初始化RSA加密失败"); size_t outlen; CHECK(EVP_PKEY_encrypt(ctx, NULL, &outlen, aes_key, AES_KEY_SIZE) > 0, "计算缓冲区大小失败"); unsigned char* encrypted = malloc(outlen); CHECK(encrypted != NULL, "内存分配失败"); CHECK(EVP_PKEY_encrypt(ctx, encrypted, &outlen, aes_key, AES_KEY_SIZE) > 0, "RSA加密失败"); *encrypted_len = outlen; EVP_PKEY_CTX_free(ctx); return encrypted; } void aes_gcm_encrypt( const unsigned char* plaintext, size_t plaintext_len, const unsigned char* aes_key, unsigned char* iv, unsigned char* tag, unsigned char** ciphertext, size_t* ciphertext_len) { EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); CHECK(ctx != NULL, "创建AES上下文失败"); CHECK(RAND_bytes(iv, GCM_IV_LEN) > 0, "生成IV失败"); CHECK(EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, aes_key, iv) > 0, "初始化AES失败"); *ciphertext = malloc(plaintext_len + EVP_MAX_BLOCK_LENGTH); CHECK(*ciphertext != NULL, "内存分配失败"); int len; CHECK(EVP_EncryptUpdate(ctx, *ciphertext, &len, plaintext, plaintext_len) > 0, "加密失败"); *ciphertext_len = len; CHECK(EVP_EncryptFinal_ex(ctx, *ciphertext + len, &len) > 0, "最终加密失败"); *ciphertext_len += len; CHECK(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, GCM_TAG_LEN, tag) > 0, "获取标签失败"); EVP_CIPHER_CTX_free(ctx); } int main() { OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL); unsigned char aes_key[AES_KEY_SIZE]; CHECK(RAND_bytes(aes_key, sizeof(aes_key)) > 0, "生成AES密钥失败"); EVP_PKEY* pubkey = load_public_key("public.pem"); size_t encrypted_aes_len; unsigned char* encrypted_aes = rsa_encrypt(pubkey, aes_key, &encrypted_aes_len); const char* plaintext = "这是敏感文件内容"; size_t plaintext_len = strlen(plaintext); unsigned char iv[GCM_IV_LEN]; unsigned char tag[GCM_TAG_LEN]; unsigned char* ciphertext; size_t ciphertext_len; aes_gcm_encrypt((unsigned char*)plaintext, plaintext_len, aes_key, iv, tag, &ciphertext, &ciphertext_len); FILE* out = fopen("encrypted.bin", "wb"); CHECK(out != NULL, "无法创建输出文件"); fwrite(&encrypted_aes_len, sizeof(size_t), 1, out); fwrite(encrypted_aes, 1, encrypted_aes_len, out); fwrite(iv, 1, GCM_IV_LEN, out); fwrite(tag, 1, GCM_TAG_LEN, out); fwrite(&ciphertext_len, sizeof(size_t), 1, out); fwrite(ciphertext, 1, ciphertext_len, out); fclose(out); free(encrypted_aes); free(ciphertext); EVP_PKEY_free(pubkey); printf("加密成功!输出文件: encrypted.bin\n"); return 0; }
03-21
<think>好的,我现在要仔细分析用户提供的C语言代码,确定生成的encrypted.bin文件是哪个文件加密后的结果。首先,我需要理解代码的整体结构和加密流程。 首先,我看到代码中包含了多个OpenSSL的头文件,比如EVP、PEM、RAND等,这表明代码使用了OpenSSL库的加密功能。主函数main里进行了初始化,生成AES密钥,加载公钥,进行RSA加密,然后用AES-GCM加密明文,最后将数据写入encrypted.bin。 接下来,我需要逐步分析各个函数的作用。在main函数中,首先生成了一个随机的AES密钥,长度为32字节(AES_KEY_SIZE定义为32)。然后使用load_public_key函数加载public.pem的公钥,接着用rsa_encrypt函数用RSA公钥加密这个AES密钥,得到encrypted_aes和它的长度。 然后,代码定义了一个明文字符串"这是敏感文件内容",并获取其长度。接下来调用aes_gcm_encrypt函数,使用之前生成的AES密钥对明文进行加密,生成密文、IV(初始化向量)和tag(用于验证完整性的标签)。 在写入文件的部分,代码将加密后的AES密钥长度、加密的AES密钥、IV、tag、密文长度和密文按顺序写入encrypted.bin文件中。这里需要注意,整个过程中并没有读取外部文件,而是直接将代码中硬编码的字符串作为明文进行加密。因此,生成的encrypted.bin实际上是加密了这个硬编码的字符串,而不是用户提到的1.txt或其他外部文件。 用户可能误解代码中的明文是来自某个文件,比如1.txt,但根据当前代码,明文是固定的字符串。因此,需要指出这一点,并建议用户如果需要加密文件,应修改代码读取文件内容作为明文。同时,提醒用户注意密钥管理和文件处理的安全性,比如备份原始文件、安全保存私钥等。</think>根据代码逻辑分析,`encrypted.bin` 文件是 **对代码中硬编码的字符串明文 "这是敏感文件内容" 进行加密后的结果**,而非直接加密某个外部文件(如1.txt)。以下是关键分析过程: --- ### **代码执行流程解析** 1. **密钥生成阶段** - `RAND_bytes(aes_key, sizeof(aes_key))`:生成 256 位 (32 字节) 随机 AES 密钥 - `load_public_key("public.pem")`:加载 RSA 公钥文件 `public.pem` 2. **混合加密过程** - **RSA 加密 AES 密钥**:`rsa_encrypt` 函数用 RSA 公钥加密 AES 密钥,生成 `encrypted_aes` - **AES-GCM 加密明文**:`aes_gcm_encrypt` 函数直接加密硬编码字符串 `"这是敏感文件内容"`,生成 `ciphertext` 3. **文件输出结构** ```c encrypted.bin 文件内容结构: +----------------------+----------------+----------+----------+-----------------+-----------------+ | RSA加密后的AES密钥长度 | RSA加密的AES密钥 | GCM IV | GCM TAG | 密文数据长度 | AES加密的密文数据 | | (size_t类型) | (二进制数据) | 12字节 | 16字节 | (size_t类型) | (二进制数据) | +----------------------+----------------+----------+----------+-----------------+-----------------+ ``` --- ### **为何不是外部文件?** 代码中 **未出现文件读取操作**,明文数据直接来自代码内的字符串: ```c const char* plaintext = "这是敏感文件内容"; // 直接定义明文内容 size_t plaintext_len = strlen(plaintext); ``` --- ### **如何修改为加密1.txt文件?** 若需加密外部文件,需修改以下代码段: ```c // 替换原始明文定义 FILE* input_file = fopen("1.txt", "rb"); CHECK(input_file != NULL, "无法打开输入文件"); fseek(input_file, 0, SEEK_END); long plaintext_len = ftell(input_file); fseek(input_file, 0, SEEK_SET); unsigned char* plaintext = malloc(plaintext_len); CHECK(plaintext != NULL, "内存分配失败"); fread(plaintext, 1, plaintext_len, input_file); fclose(input_file); ``` --- ### **关键安全特性** 1. **混合加密机制** - 使用 RSA 加密 AES 密钥(解决密钥分发问题) - 使用 AES-256-GCM 加密数据(提供认证加密) 2. **随机化要素** - AES 密钥 (`RAND_bytes` 生成) - GCM IV (`RAND_bytes` 生成) 3. **完整性保护** - GCM 模式生成的 16 字节认证标签 (`tag`),可验证数据是否被篡改 --- ### **使用注意事项** 1. **依赖文件**:需要预先存在 `public.pem` 公钥文件 2. **解密需求**:必须配合对应的 RSA 私钥才能解密 AES 密钥,进而解密数据 3. **安全风险**:当前代码的明文为硬编码字符串,若需加密文件需按上文修改读取逻辑 建议通过实际文件操作替换硬编码明文,以实现真正的文件加密功能。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值