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

基于OpenSSL的AES-CTR PRNG实现:一致性输出与内存泄漏问题

问题:nwipe中AES-CTR PRNG的一致性失效与Valgrind未初始化值警告

背景

我在为nwipe项目实现基于OpenSSL的AES-CTR模式伪随机数生成器(PRNG),要求相同种子多次运行时生成的随机数完全一致。但实际测试中,第4次运行时随机数出现差异,同时Valgrind检测到OpenSSL内部函数存在未初始化值导致的条件跳转。

代码实现

初始化调用

int nwipe_aes_ctr_prng_init(NWIPE_PRNG_INIT_SIGNATURE) {
    nwipe_log(NWIPE_LOG_NOTICE, "Initialising AES CTR PRNG");

    if (*state == NULL) {
        *state = calloc(1, sizeof(aes_ctr_state_t)); // 使用calloc分配并初始化内存
        if (*state == NULL) {
            nwipe_log(NWIPE_LOG_FATAL, "Failed to allocate memory for AES CTR PRNG state.");
            return -1; // 返回错误码表示异常
        }
    }

    aes_ctr_prng_init((aes_ctr_state_t*)*state, (unsigned long*)(seed->s), seed->length / sizeof(unsigned long));

    return 0; // 成功
}

随机数生成调用

int nwipe_aes_ctr_prng_read(NWIPE_PRNG_READ_SIGNATURE) {
    u8* restrict bufpos = buffer;
    size_t words = count / SIZE_OF_AES_CTR_PRNG;

    for(size_t ii = 0; ii < words; ++ii) {
        aes_ctr_prng_genrand_uint128_to_buf((aes_ctr_state_t*) *state, bufpos);
        bufpos += SIZE_OF_AES_CTR_PRNG;  // 移动到下一个块
    }

    // 处理count不是SIZE_OF_AES_CTR_PRNG整数倍的剩余字节
    const size_t remain = count % SIZE_OF_AES_CTR_PRNG;
    if(remain > 0) {
        unsigned char temp_output[16];  // 最后一个块的临时缓冲区
        aes_ctr_prng_genrand_uint128_to_buf((aes_ctr_state_t*) *state, temp_output);
        memcpy(bufpos, temp_output, remain);
    }

    return 0;  // 成功
}

结构体定义

typedef struct {
    EVP_CIPHER_CTX *ctx;
    unsigned char ivec[AES_BLOCK_SIZE];
    unsigned int num;
    unsigned char ecount[AES_BLOCK_SIZE];
} aes_ctr_state_t;

随机数生成函数

void aes_ctr_prng_genrand_uint128_to_buf(aes_ctr_state_t* state, unsigned char* bufpos) {
    int outlen;

    EVP_EncryptUpdate(state->ctx, bufpos, &outlen, bufpos, 16);
    // OpenSSL内部调用CRYPTO_ctr128_encrypt_ctr32
}

初始化函数

void aes_ctr_prng_init(aes_ctr_state_t* state, unsigned long init_key[], unsigned long key_length) {
    unsigned char key[32]; // 用于存储256位密钥

    memset(state->ivec, 0, AES_BLOCK_SIZE);
    state->num = 0;
    memset(state->ecount, 0, AES_BLOCK_SIZE);

    SHA256_CTX sha256;
    SHA256_Init(&sha256);
    SHA256_Update(&sha256, (unsigned char*)init_key, key_length * sizeof(unsigned long));
    SHA256_Final(key, &sha256); // 生成最终密钥

    state->ctx = EVP_CIPHER_CTX_new();
    if (state->ctx != NULL) {
        EVP_EncryptInit_ex(state->ctx, EVP_aes_256_ctr(), NULL, key, state->ivec);
    }
}

实现逻辑

初始化阶段将IV、计数器num和ecount置零,通过SHA256对种子生成256位密钥,初始化AES-256-CTR模式;随机数通过原地加密缓冲区生成。

遇到的问题

  • 相同种子多次运行时,第4次迭代生成的随机数出现差异
  • Valgrind检测到OpenSSL的CRYPTO_ctr128_encrypt_ctr32函数存在基于未初始化值的条件跳转,日志如下:
==40641== Conditional jump or move depends on uninitialised value(s)
==40641==    at 0x4B9F565: CRYPTO_ctr128_encrypt_ctr32 (ctr128.c:183)
==40641==    by 0x4C655A1: ossl_cipher_hw_generic_ctr (ciphercommon_hw.c:117)
==40641==    by 0x4C60FCC: ossl_cipher_generic_stream_update (ciphercommon.c:469)
==40641==    by 0x4B61076: EVP_EncryptUpdate (evp_enc.c:643)
==40641==    by 0x414314: aes_ctr_prng_genrand_uint128_to_buf (aes_ctr_prng.c:133)
==40641==    by 0x417F5F: nwipe_aes_ctr_prng_read (prng.c:293)
...

核心疑问

  1. 如何确保相同种子多次运行时,PRNG输出完全一致?
  2. 如何解决Valgrind报告的未初始化值问题,是否遗漏了状态初始化步骤?

解决方案建议

针对一致性问题

  1. 彻底重置PRNG状态
    当前代码中,若*state非空会直接复用旧状态,但未清理OpenSSL的EVP_CIPHER_CTX内部残留数据,导致输出不一致。修改初始化逻辑:

    if (*state != NULL) {
        // 清理旧的加密上下文
        if ((*state)->ctx != NULL) {
            EVP_CIPHER_CTX_free((*state)->ctx);
            (*state)->ctx = NULL;
        }
        // 重置所有状态字段
        memset((*state)->ivec, 0, AES_BLOCK_SIZE);
        (*state)->num = 0;
        memset((*state)->ecount, 0, AES_BLOCK_SIZE);
    } else {
        *state = calloc(1, sizeof(aes_ctr_state_t));
        if (*state == NULL) {
            nwipe_log(NWIPE_LOG_FATAL, "Failed to allocate memory for AES CTR PRNG state.");
            return -1;
        }
    }
    
  2. 统一种子的字节级处理
    原代码将种子强制转换为unsigned long*,依赖平台相关的类型大小和端序,可能导致SHA256输入不一致。改为直接按字节处理种子:

    // 重写初始化函数参数
    void aes_ctr_prng_init(aes_ctr_state_t* state, const unsigned char* init_seed, size_t seed_length) {
        unsigned char key[32];
    
        memset(state->ivec, 0, AES_BLOCK_SIZE);
        state->num = 0;
        memset(state->ecount, 0, AES_BLOCK_SIZE);
    
        SHA256_CTX sha256;
        SHA256_Init(&sha256);
        SHA256_Update(&sha256, init_seed, seed_length);
        SHA256_Final(key, &sha256);
    
        state->ctx = EVP_CIPHER_CTX_new();
        if (state->ctx != NULL) {
            EVP_EncryptInit_ex(state->ctx, EVP_aes_256_ctr(), NULL, key, state->ivec);
        }
    }
    // 调用时传递原始字节流
    aes_ctr_prng_init((aes_ctr_state_t*)*state, seed->s, seed->length);
    
  3. 验证计数器初始状态
    每次初始化后打印ivec、num的值,确保全为0,避免OpenSSL内部对计数器的隐性修改导致状态不一致。

针对Valgrind未初始化值问题

  1. 修复PRNG生成逻辑
    原代码使用未初始化的缓冲区作为EVP_EncryptUpdate的输入,CTR模式下会将未初始化数据与密钥流异或,导致输出依赖未初始化值。改为输入全零缓冲区:

    void aes_ctr_prng_genrand_uint128_to_buf(aes_ctr_state_t* state, unsigned char* bufpos) {
        int outlen;
        unsigned char zero_buf[16] = {0};
        EVP_EncryptUpdate(state->ctx, bufpos, &outlen, zero_buf, 16);
    }
    
  2. 初始化OpenSSL上下文
    EVP_CIPHER_CTX_new可能不会完全初始化所有字段,添加重置操作确保状态干净:

    state->ctx = EVP_CIPHER_CTX_new();
    if (state->ctx != NULL) {
        EVP_CIPHER_CTX_reset(state->ctx);
        EVP_EncryptInit_ex(state->ctx, EVP_aes_256_ctr(), NULL, key, state->ivec);
    }
    
  3. 显式初始化栈变量
    在nwipe_aes_ctr_prng_read中,显式初始化临时缓冲区:

    unsigned char temp_output[16] = {0};
    

内容的提问来源于stack exchange,提问作者FBDIMM

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 03:54:54