-
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:
And it's init function i implemented. Initializing Function:
Key Initialization and Random Generation Implementation: Issue Encountered: Valgrind Log:
Question:
Any insights or suggestions to debug these issues would be greatly appreciated. Thank you! |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments
-
First observation is that you aren't checking the return codes on any of your OpenSSL Init/Update/Final calls, you're just assuming that you have passed the right parameters in, and that they are successful. I can't tell from what you've posted here what the problem might be, but my first guess is that you've probably got a keylength mismatch somewhere thats resulting in your cipher being improperly initalized. I would suggest that you check the return code on each of the SHA and EVP calls, and in the event of an error, print the error stack to the console or a file (see ERR_print_errors_fp). That will give you a good deal more visibility into what if anything is going wrong during your initalization and encrypt process |
Beta Was this translation helpful? Give feedback.
-
Ahhoy! I've edited the code accordingly, unfortunately no error output at all. The return codes are as expected. void aes_ctr_prng_init(aes_ctr_state_t* state, unsigned long init_key[], unsigned long key_length) {
unsigned char key[32]; // Space for a 256-bit key
// Initialize IV and counter before use
memset(state->ivec, 0, AES_BLOCK_SIZE);
state->num = 0;
memset(state->ecount, 0, AES_BLOCK_SIZE);
// Initialize SHA256 context and generate the key
SHA256_CTX sha256;
if (!SHA256_Init(&sha256)) {
printf("Error at SHA256_Init\n");
ERR_print_errors_fp(stderr); // Print the error stack
}
if (!SHA256_Update(&sha256, (unsigned char*)init_key, key_length * sizeof(unsigned long))) {
printf("Error at SHA256_Update\n");
ERR_print_errors_fp(stderr); // Print the error stack
}
// Optional: Add salt to increase uniqueness
// const unsigned char salt[] = "optional salt value";
// if (!SHA256_Update(&sha256, salt, sizeof(salt))) {
// printf("Error at SHA256_Update with salt\n");
// ERR_print_errors_fp(stderr); // Print the error stack
// }
if (!SHA256_Final(key, &sha256)) { // Generate the final key
printf("Error at SHA256_Final\n");
ERR_print_errors_fp(stderr); // Print the error stack
}
state->ctx = EVP_CIPHER_CTX_new();
if (state->ctx == NULL) {
printf("Error at EVP_CIPHER_CTX_new\n");
ERR_print_errors_fp(stderr); // Print the error stack
} else {
if (EVP_EncryptInit_ex(state->ctx, EVP_aes_256_ctr(), NULL, key, state->ivec) != 1) {
printf("Error at EVP_EncryptInit_ex\n");
ERR_print_errors_fp(stderr); // Print the error stack
}
}
}
void aes_ctr_prng_genrand_uint128_to_buf(aes_ctr_state_t* state, unsigned char* bufpos) {
int outlen;
// Generate random numbers directly into bufpos
if (EVP_EncryptUpdate(state->ctx, bufpos, &outlen, bufpos, 16) != 1) {
printf("Error at EVP_EncryptUpdate\n");
ERR_print_errors_fp(stderr); // Print the error stack
}
// Ensure EVP_EncryptFinal_ex is not needed since we're in streaming mode
} |
Beta Was this translation helpful? Give feedback.
-
Thanks a lot folks for your help. I was able to fix the issue. Before: 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
} After: /* Generates pseudorandom numbers and writes them to a buffer.
- state: Pointer to the initialized AES CTR PRNG state.
- bufpos: Target buffer where the pseudorandom numbers will be written. */
void aes_ctr_prng_genrand_uint128_to_buf( aes_ctr_state_t* state, unsigned char* bufpos )
{
unsigned char temp_buffer[16]; /* Intermediate buffer for 128 bits */
int outlen;
/* Generate pseudorandom numbers in the intermediate buffer */
EVP_EncryptUpdate( state->ctx, temp_buffer, &outlen, temp_buffer, sizeof( temp_buffer ) );
/* Write the data from the intermediate buffer to bufpos in four 32-bit steps.
This process is crucial to prevent a buffer overflow of bufpos, as it ensures
that exactly 16 bytes (128 bits) of pseudorandom data are safely transferred
into bufpos. Copying the data in controlled 32-bit segments allows for precise
management of the memory space allocated to bufpos, mitigating the risk of
writing beyond its allocated size. */
int i = 0;
while( i < 4 )
{
/* Copy each 32-bit segment from the intermediate buffer to the target buffer.
This step-by-step approach is essential for maintaining the integrity of
the buffer and ensuring that only the intended amount of data is written.
The ternary operator is used here for illustrative purposes and does not
alter the functionality. */
memcpy( bufpos + ( i * 4 ), temp_buffer + ( i * 4 ), 4 );
i = ( i < 4 ) ? i + 1 : 4; // Ternary operator to increment i or keep it at 4
}
} |
Beta Was this translation helpful? Give feedback.
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: