You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 11:55:00