Weakness reference
CWE-323

Reusing a Nonce, Key Pair in Encryption

This weakness occurs when software reuses a nonce a "number used once" or cryptographic key pair in encryption operations that cryptographically require unique…

01Summary

This weakness occurs when software reuses a nonce (a "number used once") or cryptographic key pair in encryption operations that cryptographically require unique values each time. Reusing these values breaks the mathematical guarantees of the encryption scheme, potentially allowing attackers to recover plaintext, forge messages, or derive keys. It is a common mistake in implementations of authenticated encryption, stream ciphers, and key derivation.

02How It Happens

Modern encryption schemes—particularly authenticated encryption modes like AES-GCM, ChaCha20-Poly1305, and stream ciphers—are designed with the assumption that each encryption operation uses a fresh, unpredictable nonce combined with a static key. The nonce ensures that identical plaintexts encrypted under the same key produce different ciphertexts. When a nonce is reused with the same key, the cryptographic construction collapses: an attacker can XOR two ciphertexts encrypted with the same nonce to cancel out the keystream, revealing the XOR of the two plaintexts. Similarly, reusing key pairs in schemes like ECDSA without a unique nonce per signature allows private key recovery. The vulnerability typically arises from poor random number generation, hardcoded nonces, or failure to track which nonces have already been used.

03Real-World Impact

Nonce reuse can lead to complete plaintext recovery without knowing the key. In authenticated encryption, it also enables forgery attacks—an attacker can craft valid ciphertexts that will decrypt and authenticate successfully. For digital signatures, nonce reuse exposes the private key itself, allowing an attacker to sign arbitrary messages. The severity depends on what is encrypted: reuse in session tokens, API credentials, or payment data can lead to account takeover or financial fraud. Even a single reuse can be catastrophic if the attacker has access to both ciphertexts.

04Vulnerable & Fixed Patterns

Vulnerable pattern
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

key = os.urandom(32)
nonce = b'\x00' * 12  # Fixed nonce — reused for every encryption

def encrypt_message(plaintext):
    cipher = AESGCM(key)
    ciphertext = cipher.encrypt(nonce, plaintext, None)
    return ciphertext

# Both calls use the same nonce with the same key
msg1_encrypted = encrypt_message(b"secret1")
msg2_encrypted = encrypt_message(b"secret2")

Why it's vulnerable:
The nonce is hardcoded and never changes. Every encryption operation uses the same nonce–key pair, allowing an attacker with both ciphertexts to recover the plaintext via XOR of the keystreams.

Fixed pattern
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

key = os.urandom(32)

def encrypt_message(plaintext):
    cipher = AESGCM(key)
    nonce = os.urandom(12)  # Generate a fresh, random nonce each time
    ciphertext = cipher.encrypt(nonce, plaintext, None)
    return nonce + ciphertext  # Prepend nonce so decryption can retrieve it

# Each call generates a unique nonce
msg1_encrypted = encrypt_message(b"secret1")
msg2_encrypted = encrypt_message(b"secret2")
Vulnerable pattern
<?php
$key = random_bytes(32);
$nonce = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; // Fixed nonce

function encrypt_data($plaintext) {
    global $key, $nonce;
    return openssl_encrypt($plaintext, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $nonce);
}

// Both calls reuse the same nonce
$encrypted1 = encrypt_data("secret1");
$encrypted2 = encrypt_data("secret2");
?>

Why it's vulnerable:
The nonce is a fixed constant and reused across multiple encryption calls with the same key, breaking the security guarantee of the GCM mode.

Fixed pattern
<?php
$key = random_bytes(32);

function encrypt_data($plaintext) {
    global $key;
    $nonce = random_bytes(12);  // Generate a fresh nonce for each encryption
    $ciphertext = openssl_encrypt($plaintext, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $nonce);
    return $nonce . $ciphertext;  // Prepend nonce for later decryption
}

// Each call generates a unique nonce
$encrypted1 = encrypt_data("secret1");
$encrypted2 = encrypt_data("secret2");
?>

05Prevention Checklist

Generate a fresh nonce for every encryption operation
using a cryptographically secure random number generator (os.urandom(), random_bytes(), or equivalent).
Never hardcode or reuse nonces.
If you must store a nonce (e.g., to transmit it with the ciphertext), ensure it is unique per message and key pair.
Use authenticated encryption modes
(AES-GCM, ChaCha20-Poly1305) that are designed to detect tampering and require proper nonce handling.
For digital signatures, use a library that handles nonce generation internally
(e.g., cryptography.hazmat.primitives.asymmetric.ed25519 for EdDSA, which does not require manual nonce management).
Document nonce requirements
in code comments and design reviews; make it explicit that each encryption call must use a unique nonce.
Test with multiple messages
to ensure nonces are not being reused in production; log or assert nonce uniqueness during development.

06Signs You May Already Be Affected

- Hardcoded or constant nonce values in encryption code, especially in configuration files or as string literals. - Encryption functions that do not generate or accept a nonce parameter, suggesting a fixed nonce is being used internally. - Ciphertexts that appear identical when encrypting the same plaintext multiple times (a sign that the nonce is not changing).

07Related Recent Vulnerabilities