I'm implementing a Pseudo-Random Number Generator (PRNG) using AES-CTR mode through OpenSSL for a project aimed at secure data wiping (nwipe). My goal is to achieve consistent random numbers when wiping a disk with the same seed across multiple runs. However, I encounter unexpected behavior on the 4th run where the random numbers start to differ despite identical input parameters. Additionally, I'm struggling with a potential memory leak that I can't pinpoint.
Initialization Call:
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)); // Using calloc for memory allocation and initialization
if (*state == NULL) {
nwipe_log(NWIPE_LOG_FATAL, "Failed to allocate memory for AES CTR PRNG state.");
return -1; // Return an error code indicating a problem
}
}
aes_ctr_prng_init((aes_ctr_state_t*)*state, (unsigned long*)(seed->s), seed->length / sizeof(unsigned long));
return 0; // Success
}
Random Number Generation Call:
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; // Move to the next block
}
// Handle remaining bytes if count is not a multiple of SIZE_OF_AES_CTR_PRNG
const size_t remain = count % SIZE_OF_AES_CTR_PRNG;
if(remain > 0) {
unsigned char temp_output[16]; // Temporary buffer for the last block
aes_ctr_prng_genrand_uint128_to_buf((aes_ctr_state_t*) *state, temp_output);
memcpy(bufpos, temp_output, remain);
}
return 0; // Success
}
Struct Definition:
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;
The PRNG itself.
Random Number Generation Function:
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 internally calls CRYPTO_ctr128_encrypt_ctr32
}
And it's init function i implemented.
Initializing Function:
void aes_ctr_prng_init(aes_ctr_state_t* state, unsigned long init_key[], unsigned long key_length) {
unsigned char key[32]; // Platz für einen 256-Bit Schlüssel
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); // Generiere den endgültigen Schlüssel
state->ctx = EVP_CIPHER_CTX_new();
if (state->ctx != NULL) {
EVP_EncryptInit_ex(state->ctx, EVP_aes_256_ctr(), NULL, key, state->ivec);
}
}
Key Initialization and Random Generation Implementation:
The initialization zeroes out IV, counter, and ecount before use. The key is derived using SHA256 on the provided seed, and AES-256-CTR mode is initialized. Random numbers are generated by encrypting a buffer in place.
Issue Encountered:
Despite ensuring identical seed and parameters for each run, the 4th iteration produces different random numbers. Valgrind reports a conditional jump based on uninitialized values within the OpenSSL CRYPTO_ctr128_encrypt_ctr32 function, suggesting a potential issue with how the state or buffer is initialized or used.
Valgrind Log:
==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)
...
Question:
- How can I ensure consistent random numbers across multiple runs with the same seed?
- How do I address the uninitialized value(s) that Valgrind is reporting? Is there a step I'm missing in initializing my AES-CTR PRNG state? I can't figure out any issues with my approach of initializing the memory like i did. But obviously it causes errors, always after the 3rd run, the 4th produces different data.
Any insights or suggestions to debug these issues would be greatly appreciated. Thank you!
Thanks a lot folks for your help. I was able to fix the issue. What caused the issue, was likely a buffer overflow of bufpos, corrupting the memory of the other parameters. I've modified the code now, providing a temporary buffer, and writing 4x 32-Bit instead of one-time 128-Bit, and now it's performing properly!
Before:
After: