GnuTLS中gnutls_cipher_encrypt2返回‘请求无效’问题求助
问题
我正在研究GnuTLS,想要编写一个使用GnuTLS函数进行文件加解密的C程序,目前仅实现了加密部分,但运行时出现问题。
代码如下:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <gnutls/gnutls.h> #include <gnutls/crypto.h> #define KEY_LENGTH 32 #define IV_LENGTH 16 int main(int argc, char *argv[]) { if (argc != 3) { fprintf(stderr, "Usage: %s <input_file> <output_file> <password>", argv[0]); return 1; } int ret; const char *input_filename = argv[1]; const char *output_filename = argv[2]; gnutls_global_init(); gnutls_cipher_hd_t handle; gnutls_cipher_algorithm_t cipher_alg = GNUTLS_CIPHER_AES_256_CBC; const gnutls_datum_t key = { (void*)"ThisIsAStrongAESKey1234567890", 32 }; const gnutls_datum_t iv = { (void*)"RandomInitializationVector", 16 }; // Initialize the session and cipher handle ret = gnutls_cipher_init(&handle, cipher_alg, &key, &iv); if (ret < 0) { fprintf(stderr, "Error initializing cipher: %s", gnutls_strerror(ret)); return 1; } // Open the input and output files FILE *input_file = fopen(input_filename, "rb"); FILE *output_file = fopen(output_filename, "wb"); if (!input_file || !output_file) { perror("Error opening files"); return 1; } // Read and encrypt the input file unsigned char plaintext[4096]; unsigned char ciphertext[4096]; size_t read_bytes; while ((read_bytes = fread(plaintext, 1, sizeof(plaintext), input_file)) > 0) { ret = gnutls_cipher_encrypt2(handle, plaintext, read_bytes, ciphertext, sizeof(ciphertext)); if (ret < 0) { fprintf(stderr, "Error encrypting: %s", gnutls_strerror(ret)); return 1; } fwrite(ciphertext, 1, read_bytes, output_file); } // Clean up fclose(input_file); fclose(output_file); gnutls_cipher_deinit(handle); gnutls_global_deinit(); return 0; }
运行加密txt文件时输出:Error encrypting: The request is invalid.
查看GnuTLS源码(lib/cipher_int.h:96)发现错误代码来自这里:
inline static int _gnutls_cipher_encrypt2(const cipher_hd_st *handle, const void *text, size_t textlen, void *ciphertext, size_t ciphertextlen) { if (likely(handle != NULL && handle->handle != NULL)) { if (handle->encrypt == NULL) { return (GNUTLS_E_INVALID_REQUEST); } return handle->encrypt(handle->handle, text, textlen, ciphertext, ciphertextlen); } return 0; }
疑问:是否初始化密码套件时遗漏步骤?调用gnutls_cipher_encrypt2()前还需配置其他内容?是否需要修改handle->encrypt?
解决方法
核心问题触发原因
GNUTLS_E_INVALID_REQUEST错误的直接原因是AES-CBC模式要求明文长度必须是16字节(AES块大小)的整数倍,而你直接读取任意长度的数据就调用加密,当读取的字节数不符合块大小要求时,gnutls_cipher_encrypt2会拒绝执行,进而触发handle->encrypt为NULL的错误分支。
此外代码还存在几个必须修正的潜在问题:
- 命令行参数判断错误:提示需要3个参数但实际判断
argc !=3,导致密码参数无法传入,硬编码密钥和IV的做法不符合安全规范 - 硬编码密钥/IV:实际场景中不能用固定字符串作为密钥,IV必须随机生成并随密文存储
- 密文输出长度错误:CBC加密后长度是块大小的整数倍,直接写入
read_bytes长度的密文会丢失数据
具体修复步骤
1. 修正命令行参数逻辑
将参数判断改为argc !=4,并添加密码参数的接收:
if (argc != 4) { fprintf(stderr, "Usage: %s <input_file> <output_file> <password>\n", argv[0]); return 1; } const char *password = argv[3];
2. 用PBKDF2派生密钥,生成随机IV
替换硬编码的密钥和IV,确保安全性:
// 生成随机IV unsigned char iv_buf[IV_LENGTH]; if (gnutls_rnd(GNUTLS_RND_NONCE, iv_buf, sizeof(iv_buf)) < 0) { fprintf(stderr, "Failed to generate IV\n"); gnutls_global_deinit(); return 1; } gnutls_datum_t iv = { iv_buf, sizeof(iv_buf) }; // 通过PBKDF2从密码派生密钥 unsigned char key_buf[KEY_LENGTH]; if (gnutls_pbkdf2(GNUTLS_MAC_SHA256, password, strlen(password), iv_buf, sizeof(iv_buf), 10000, KEY_LENGTH, key_buf) < 0) { fprintf(stderr, "Failed to derive key\n"); gnutls_global_deinit(); return 1; } gnutls_datum_t key = { key_buf, sizeof(key_buf) };
注意:IV需要写入输出文件开头,解密时需读取该IV才能正确解密。
3. 添加PKCS#7填充处理
为明文补充填充字节,确保长度符合块大小要求:
size_t block_size = gnutls_cipher_get_block_size(cipher_alg); // 计算填充字节数 size_t padding = block_size - (read_bytes % block_size); // 执行PKCS#7填充 memset(plaintext + read_bytes, padding, padding); size_t padded_len = read_bytes + padding; // 加密并写入完整密文长度 ret = gnutls_cipher_encrypt2(handle, plaintext, padded_len, ciphertext, sizeof(ciphertext)); if (ret < 0) { fprintf(stderr, "Error encrypting: %s\n", gnutls_strerror(ret)); // 此处需添加资源清理逻辑 return 1; } fwrite(ciphertext, 1, padded_len, output_file);
4. 完整修正代码
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <gnutls/gnutls.h> #include <gnutls/crypto.h> #define KEY_LENGTH 32 #define IV_LENGTH 16 #define PBKDF2_ITERATIONS 10000 int main(int argc, char *argv[]) { if (argc != 4) { fprintf(stderr, "Usage: %s <input_file> <output_file> <password>\n", argv[0]); return 1; } int ret; const char *input_filename = argv[1]; const char *output_filename = argv[2]; const char *password = argv[3]; gnutls_global_init(); gnutls_cipher_hd_t handle; gnutls_cipher_algorithm_t cipher_alg = GNUTLS_CIPHER_AES_256_CBC; size_t block_size = gnutls_cipher_get_block_size(cipher_alg); // 生成随机IV unsigned char iv_buf[IV_LENGTH]; if (gnutls_rnd(GNUTLS_RND_NONCE, iv_buf, sizeof(iv_buf)) < 0) { fprintf(stderr, "Failed to generate IV\n"); gnutls_global_deinit(); return 1; } gnutls_datum_t iv = { iv_buf, sizeof(iv_buf) }; // PBKDF2派生密钥 unsigned char key_buf[KEY_LENGTH]; if (gnutls_pbkdf2(GNUTLS_MAC_SHA256, password, strlen(password), iv_buf, sizeof(iv_buf), PBKDF2_ITERATIONS, KEY_LENGTH, key_buf) < 0) { fprintf(stderr, "Failed to derive key from password\n"); gnutls_global_deinit(); return 1; } gnutls_datum_t key = { key_buf, sizeof(key_buf) }; // 初始化加密句柄 ret = gnutls_cipher_init(&handle, cipher_alg, &key, &iv); if (ret < 0) { fprintf(stderr, "Error initializing cipher: %s\n", gnutls_strerror(ret)); gnutls_global_deinit(); return 1; } // 打开文件 FILE *input_file = fopen(input_filename, "rb"); FILE *output_file = fopen(output_filename, "wb"); if (!input_file || !output_file) { perror("Error opening files"); gnutls_cipher_deinit(handle); gnutls_global_deinit(); return 1; } // 先写入IV到输出文件(解密时需要读取) fwrite(iv_buf, 1, sizeof(iv_buf), output_file); // 加密循环 unsigned char plaintext[4096 + block_size]; // 预留填充空间 unsigned char ciphertext[4096 + block_size]; size_t read_bytes; while ((read_bytes = fread(plaintext, 1, sizeof(plaintext) - block_size, input_file)) > 0) { // PKCS#7填充 size_t padding = block_size - (read_bytes % block_size); memset(plaintext + read_bytes, padding, padding); size_t padded_len = read_bytes + padding; // 加密 ret = gnutls_cipher_encrypt2(handle, plaintext, padded_len, ciphertext, sizeof(ciphertext)); if (ret < 0) { fprintf(stderr, "Error encrypting: %s\n", gnutls_strerror(ret)); fclose(input_file); fclose(output_file); gnutls_cipher_deinit(handle); gnutls_global_deinit(); return 1; } // 写入密文 fwrite(ciphertext, 1, padded_len, output_file); } // 清理资源 fclose(input_file); fclose(output_file); gnutls_cipher_deinit(handle); gnutls_global_deinit(); printf("Encryption completed successfully\n"); return 0; }
编译命令
编译时需链接GnuTLS库:
gcc -o file_encrypt file_encrypt.c -lgnutls
内容的提问来源于stack exchange,提问作者David Dudas
相关产品推荐
相关产品推荐

