logo

Database

Insecure generation of random numbers - Static IV

Need

Generation of a unique random nonce for every AEAD encryption

Context

• Usage of Rust 1.75+ for building application services

• Usage of the openssl crate for authenticated encryption

Description

1. Non compliant code

use openssl::error::ErrorStack;
use openssl::symm::{encrypt_aead, Cipher};

fn encrypt(key: &[u8; 32], plaintext: &[u8], aad: &[u8]) -> Result<Vec<u8>, ErrorStack> {
    // The same nonce is reused for every message encrypted with this key
    let nonce = [0u8; 12];
    let mut tag = [0u8; 16];
    let mut out = encrypt_aead(Cipher::aes_256_gcm(), key, Some(&nonce), aad, plaintext, &mut tag)?;...

The `encrypt` function below encrypts messages with AES-256-GCM through `openssl::symm::encrypt_aead`, but it passes the same all-zero 12-byte nonce on every call. GCM is only secure when a nonce is never used twice with the same key. With a fixed nonce, every message is encrypted with the same keystream, so an attacker who captures two ciphertexts can XOR them and obtain the XOR of the two plaintexts, which often reveals both. Worse, nonce reuse also exposes the internal authentication key of GCM, which lets the attacker forge ciphertexts with valid tags: the integrity protection that made AEAD the right choice is lost as well. The same problem affects ChaCha20-Poly1305 and every other AEAD construction, and it appears whenever the nonce comes from a constant, a literal such as `b"000000000000"`, or a counter that restarts with the process.

2. Steps

• Never pass constant, literal or zero-filled nonces to `openssl::symm::encrypt_aead` or to any other AEAD encryption function.

• Generate a new 12-byte nonce for every message with `openssl::rand::rand_bytes` or `rand::rngs::OsRng`.

• Store or transmit the nonce together with the ciphertext and the tag, since it is required for decryption and does not need to be secret.

• Rotate the key before it encrypts about 2^32 messages with random nonces.

• Prefer AES-GCM-SIV or XChaCha20-Poly1305 when nonce uniqueness cannot be guaranteed.

3. Secure code example

use openssl::error::ErrorStack;
use openssl::rand::rand_bytes;
use openssl::symm::{encrypt_aead, Cipher};

const NONCE_LEN: usize = 12;
const TAG_LEN: usize = 16;

fn encrypt(key: &[u8; 32], plaintext: &[u8], aad: &[u8]) -> Result<Vec<u8>, ErrorStack> {...

The corrected function draws a new 96-bit nonce for every message with `openssl::rand::rand_bytes`, which reads from the cryptographically secure generator of OpenSSL. The probability that two messages share a nonce under the same key is negligible, so the confidentiality and integrity guarantees of GCM hold. The nonce is not secret. The function returns it at the start of the output, followed by the ciphertext and the 16-byte authentication tag, so the receiver can split the three parts and call `decrypt_aead` with the same nonce and associated data. Random 96-bit nonces are safe for up to about 2^32 messages per key. Services that encrypt more than that with a single key should rotate keys, or use a nonce-misuse-resistant construction such as AES-GCM-SIV or XChaCha20-Poly1305 with its larger nonce.