Wiederverwendung einer Nonce oder eines Schlüsselpaars bei der Verschlüsselung

Beschreibung

Wiederverwendung einer Nonce oder eines Schlüsselpaars bei der Verschlüsselung ist eine Schwachstelle, die auftritt, wenn dieselbe Nonce (Number used once - einmalig verwendete Zahl) mit demselben Schlüssel für mehrere Verschlüsselungsoperationen verwendet wird. Nonces sind so konzipiert, dass sie für jede Verschlüsselungsoperation einzigartig sind, um sicherzustellen, dass selbst identische Klartexte unterschiedliche Geheimtexte erzeugen. Wenn eine Nonce mit demselben Schlüssel wiederverwendet wird, kann dies die Sicherheit des Verschlüsselungsschemas kompromittieren und möglicherweise Angreifern ermöglichen, Klartext wiederherzustellen, Nachrichten zu fälschen oder Replay-Angriffe durchzuführen. Diese Schwäche ist besonders kritisch bei Stromchiffren und authentifizierten Verschlüsselungsmodi wie AES-GCM, wo Nonce-Wiederverwendung katastrophal sein kann.

Risiko

Nonce-Wiederverwendung stellt eine kritische kryptografische Schwachstelle dar, deren Konsequenzen je nach Verschlüsselungsmodus variieren. Bei Stromchiffren und Counter-Modi ermöglicht die Wiederverwendung einer Nonce das XOR-Verknüpfen zweier Geheimtexte, um das XOR der Klartexte zu offenbaren, was statistische Analyseangriffe ermöglicht. Bei AES-GCM ermöglicht Nonce-Wiederverwendung die Fälschung von Authentifizierungs-Tags und kann das Verschlüsselungsschema vollständig kompromittieren. Über kryptanalytische Angriffe hinaus ermöglicht Nonce-Wiederverwendung Replay-Angriffe, bei denen Angreifer zuvor erfasste verschlüsselte Nachrichten erneut senden können, die als gültig akzeptiert werden. Das Risiko wird in Systemen verstärkt, die hohe Nachrichtenvolumen verarbeiten, wo die Wahrscheinlichkeit von Nonce-Kollisionen steigt. Reale Konsequenzen umfassten die vollständige Kompromittierung verschlüsselter Kommunikation und die Fähigkeit, authentifizierte Nachrichten zu fälschen.

Lösung

Stellen Sie sicher, dass Nonces niemals mit demselben Schlüssel wiederverwendet werden durch ordnungsgemäße Nonce-Generierung und -Verwaltung. Verwenden Sie kryptografisch sichere Zufallszahlengeneratoren für die Nonce-Generierung, wenn das Nachrichtenvolumen niedrig genug ist, dass die Kollisionswahrscheinlichkeit vernachlässigbar ist. Implementieren Sie inkrementierende Zähler für Nonces, wenn die Nachrichtenreihenfolge beibehalten wird, und stellen Sie sicher, dass Zähler niemals überlaufen oder zurückgesetzt werden. Verwenden Sie zeitstempelbasierte Nonces kombiniert mit Zufallskomponenten für verteilte Systeme. Verfolgen Sie verwendete Nonces, um Duplikate zu erkennen und abzulehnen, wenn möglich. Erwägen Sie Nonce-Missbrauchs-resistente Verschlüsselungsschemata wie AES-GCM-SIV für Anwendungen, bei denen Nonce-Einzigartigkeit nicht garantiert werden kann. Implementieren Sie Schlüsselrotationsrichtlinien, die die Anzahl der mit einem einzelnen Schlüssel verschlüsselten Nachrichten begrenzen. Verwenden Sie separate Schlüssel für verschiedene Kontexte, um Nonce-Räume zu isolieren.

Häufige Auswirkungen

AuswirkungDetails
ZugriffskontrolleUmfang: Zugriffskontrolle

Nonce-Wiederverwendung kann Replay-Angriffe ermöglichen, bei denen Angreifer zuvor erfasste verschlüsselte Daten erneut senden, um sich als legitime Benutzer auszugeben oder Transaktionen zu wiederholen.
Vertraulichkeit, IntegritätUmfang: Vertraulichkeit, Integrität

In vielen Verschlüsselungsmodi ermöglicht Nonce-Wiederverwendung kryptanalytische Angriffe, die Klartext wiederherstellen oder gültige Geheimtexte fälschen, was sowohl die Vertraulichkeit als auch die Nachrichtenauthentizität kompromittiert.

Beispielcode

Anfälliger Code (C/Python)

Die folgenden Beispiele demonstrieren Nonce-Wiederverwendungs-Schwachstellen:

// Anfällig: Hartcodierte Nonce für alle Verschlüsselungen wiederverwendet
#include <openssl/evp.h>
#include <string.h>

// Anfällig: Statische Nonce für alle Verschlüsselungen verwendet
static const unsigned char NONCE[12] = {
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
    0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b
};

static const unsigned char KEY[32] = { /* ... */ };

int vulnerable_encrypt_password(const char *password,
                                unsigned char *ciphertext) {
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();

    // Anfällig: Dieselbe Nonce wird jedes Mal verwendet!
    EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, KEY, NONCE);

    int len;
    EVP_EncryptUpdate(ctx, ciphertext, &len,
                      (unsigned char*)password, strlen(password));

    EVP_EncryptFinal_ex(ctx, ciphertext + len, &len);

    EVP_CIPHER_CTX_free(ctx);

    // Alle Passwörter mit derselben Nonce verschlüsselt - können XOR-verknüpft werden!
    return 0;
}

// Anfällig: Nonce für Remote-Befehle wiederverwendet
int vulnerable_send_command(const char *command,
                            unsigned char *encrypted_command) {

    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();

    // Anfällig: Statische Nonce ermöglicht Replay-Angriffe
    EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, KEY, NONCE);

    int len;
    EVP_EncryptUpdate(ctx, encrypted_command, &len,
                      (unsigned char*)command, strlen(command));

    // Angreifer kann jeden Befehl erfassen und wiedergeben!

    EVP_CIPHER_CTX_free(ctx);
    return len;
}
# Anfällig: Nonce-Wiederverwendung in Python-Verschlüsselung
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os

class VulnerableEncryption:
    def __init__(self, key):
        self.aesgcm = AESGCM(key)
        # Anfällig: Dieselbe Nonce für alle Operationen
        self.nonce = b'\x00' * 12

    def encrypt(self, plaintext):
        # Anfällig: Nonce wird wiederverwendet!
        return self.aesgcm.encrypt(self.nonce, plaintext, None)

    def decrypt(self, ciphertext):
        return self.aesgcm.decrypt(self.nonce, ciphertext, None)

# Anfällig: Zähler der überläuft
class VulnerableCounter:
    def __init__(self, key):
        self.aesgcm = AESGCM(key)
        self.counter = 0  # Wird irgendwann überlaufen und umschlagen

    def encrypt(self, plaintext):
        # Anfällig: Zähler kann nach 2^32 Verschlüsselungen umschlagen
        nonce = self.counter.to_bytes(12, 'big')
        self.counter = (self.counter + 1) % (2**32)  # Schlägt um!

        return self.aesgcm.encrypt(nonce, plaintext, None)

# Anfällig: Vorhersagbare Nonce basierend auf Zeitstempel
import time

class VulnerablePredictableNonce:
    def __init__(self, key):
        self.aesgcm = AESGCM(key)

    def encrypt(self, plaintext):
        # Anfällig: Nur Zeitstempel-Nonce kann kollidieren
        # Mehrere Verschlüsselungen in derselben Sekunde verwenden dieselbe Nonce!
        nonce = int(time.time()).to_bytes(12, 'big')

        return self.aesgcm.encrypt(nonce, plaintext, None)
// Anfällig: Java Nonce-Wiederverwendung
import javax.crypto.*;
import javax.crypto.spec.*;
import java.util.Arrays;

public class VulnerableEncryption {

    private final SecretKey key;
    // Anfällig: Statische Nonce
    private static final byte[] STATIC_NONCE = new byte[12];

    public VulnerableEncryption(SecretKey key) {
        this.key = key;
    }

    // Anfällig: Dieselbe Nonce für jede Verschlüsselung
    public byte[] encrypt(byte[] plaintext) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        GCMParameterSpec parameterSpec = new GCMParameterSpec(128, STATIC_NONCE);

        cipher.init(Cipher.ENCRYPT_MODE, key, parameterSpec);
        return cipher.doFinal(plaintext);
    }

    // Anfällig: Zähler beim Neustart auf denselben Wert initialisiert
    private int counter = 0;

    public byte[] encryptWithCounter(byte[] plaintext) throws Exception {
        // Anfällig: Zähler wird beim Anwendungsneustart zurückgesetzt
        byte[] nonce = new byte[12];
        nonce[0] = (byte) (counter >> 24);
        nonce[1] = (byte) (counter >> 16);
        nonce[2] = (byte) (counter >> 8);
        nonce[3] = (byte) counter;
        counter++;

        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(128, nonce));
        return cipher.doFinal(plaintext);
        // Nach Neustart beginnt Zähler wieder bei 0 - Nonce-Wiederverwendung!
    }
}

Korrigierter Code (C/Python)

// Korrigiert: Einzigartige Nonce-Generierung
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <string.h>
#include <stdint.h>

typedef struct {
    unsigned char key[32];
    uint64_t counter;
    unsigned char instance_id[4];  // Zufällige Pro-Instanz-Kennung
} SecureContext;

int init_secure_context(SecureContext *ctx, const unsigned char *key) {
    memcpy(ctx->key, key, 32);
    ctx->counter = 0;

    // Korrigiert: Zufällige Instanz-ID generieren um Instanzübergreifende Kollision zu verhindern
    if (RAND_bytes(ctx->instance_id, 4) != 1) {
        return -1;
    }

    return 0;
}

int secure_encrypt(SecureContext *ctx, const unsigned char *plaintext,
                   size_t plaintext_len, unsigned char *ciphertext) {

    unsigned char nonce[12];

    // Korrigiert: Einzigartige Nonce aus instance_id + Zähler konstruieren
    memcpy(nonce, ctx->instance_id, 4);
    nonce[4] = (ctx->counter >> 56) & 0xFF;
    nonce[5] = (ctx->counter >> 48) & 0xFF;
    nonce[6] = (ctx->counter >> 40) & 0xFF;
    nonce[7] = (ctx->counter >> 32) & 0xFF;
    nonce[8] = (ctx->counter >> 24) & 0xFF;
    nonce[9] = (ctx->counter >> 16) & 0xFF;
    nonce[10] = (ctx->counter >> 8) & 0xFF;
    nonce[11] = ctx->counter & 0xFF;

    // Korrigiert: Zähler für nächste Verwendung inkrementieren
    ctx->counter++;

    // Korrigiert: Auf Zählerüberlauf prüfen - Zeit für Schlüsselrotation!
    if (ctx->counter == 0) {
        return -1;  // Zähler übergelaufen - neuer Schlüssel nötig
    }

    EVP_CIPHER_CTX *cipher_ctx = EVP_CIPHER_CTX_new();
    EVP_EncryptInit_ex(cipher_ctx, EVP_aes_256_gcm(), NULL, ctx->key, nonce);

    // Nonce dem Geheimtext voranstellen
    memcpy(ciphertext, nonce, 12);

    int len;
    EVP_EncryptUpdate(cipher_ctx, ciphertext + 12, &len,
                      plaintext, plaintext_len);

    int ciphertext_len = len;
    EVP_EncryptFinal_ex(cipher_ctx, ciphertext + 12 + len, &len);
    ciphertext_len += len;

    // Authentifizierungs-Tag holen
    unsigned char tag[16];
    EVP_CIPHER_CTX_ctrl(cipher_ctx, EVP_CTRL_GCM_GET_TAG, 16, tag);
    memcpy(ciphertext + 12 + ciphertext_len, tag, 16);

    EVP_CIPHER_CTX_free(cipher_ctx);

    return 12 + ciphertext_len + 16;  // nonce + geheimtext + tag
}
# Korrigiert: Einzigartige Nonce-Generierung in Python
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
import struct
import threading

class SecureEncryption:
    def __init__(self, key):
        self.aesgcm = AESGCM(key)
        self.lock = threading.Lock()
        self.counter = 0
        # Korrigiert: Zufällige Instanz-Kennung
        self.instance_id = os.urandom(4)

    def encrypt(self, plaintext, associated_data=None):
        with self.lock:
            # Korrigiert: Einzigartige Nonce aus instance_id + Zähler konstruieren
            nonce = self.instance_id + struct.pack('>Q', self.counter)

            # Korrigiert: Zähler inkrementieren
            self.counter += 1

            # Korrigiert: Auf Zählerüberlauf prüfen
            if self.counter >= 2**64:
                raise OverflowError("Zählerüberlauf - Schlüssel rotieren!")

        ciphertext = self.aesgcm.encrypt(nonce, plaintext, associated_data)

        # Korrigiert: Nonce dem Geheimtext für Entschlüsselung voranstellen
        return nonce + ciphertext

    def decrypt(self, nonce_and_ciphertext, associated_data=None):
        # Korrigiert: Nonce aus Nachricht extrahieren
        nonce = nonce_and_ciphertext[:12]
        ciphertext = nonce_and_ciphertext[12:]

        return self.aesgcm.decrypt(nonce, ciphertext, associated_data)

# Korrigiert: Zufällige Nonce-Generierung für niedrige Volumen
class SecureRandomNonce:
    def __init__(self, key):
        self.aesgcm = AESGCM(key)

    def encrypt(self, plaintext, associated_data=None):
        # Korrigiert: Zufällige Nonce für jede Verschlüsselung
        # Sicher für < 2^32 Nachrichten mit demselben Schlüssel (Birthday Bound)
        nonce = os.urandom(12)

        ciphertext = self.aesgcm.encrypt(nonce, plaintext, associated_data)
        return nonce + ciphertext

    def decrypt(self, nonce_and_ciphertext, associated_data=None):
        nonce = nonce_and_ciphertext[:12]
        ciphertext = nonce_and_ciphertext[12:]
        return self.aesgcm.decrypt(nonce, ciphertext, associated_data)

# Korrigiert: Nonce-Missbrauchs-resistente Verschlüsselung
from cryptography.hazmat.primitives.ciphers.aead import AESGCMSIV

class MisuseResistantEncryption:
    def __init__(self, key):
        # Korrigiert: Nonce-Missbrauchs-resistenten Modus verwenden
        self.cipher = AESGCMSIV(key)

    def encrypt(self, plaintext, associated_data=None):
        # Immer noch einzigartige Nonces verwenden, aber versehentliche Wiederverwendung ist weniger katastrophal
        nonce = os.urandom(12)
        ciphertext = self.cipher.encrypt(nonce, plaintext, associated_data)
        return nonce + ciphertext
// Korrigiert: Java sichere Nonce-Handhabung
import javax.crypto.*;
import javax.crypto.spec.*;
import java.security.*;
import java.util.concurrent.atomic.AtomicLong;

public class SecureEncryption {

    private final SecretKey key;
    private final AtomicLong counter;
    private final byte[] instanceId;

    public SecureEncryption(SecretKey key) throws Exception {
        this.key = key;
        this.counter = new AtomicLong(0);

        // Korrigiert: Zufällige Instanz-Kennung
        this.instanceId = new byte[4];
        SecureRandom.getInstanceStrong().nextBytes(instanceId);
    }

    public byte[] encrypt(byte[] plaintext) throws Exception {
        // Korrigiert: Atomare Zählerinkrementierung
        long count = counter.getAndIncrement();

        // Korrigiert: Auf Überlauf prüfen
        if (count < 0) {
            throw new IllegalStateException("Zählerüberlauf - Schlüssel rotieren");
        }

        // Korrigiert: Einzigartige Nonce konstruieren
        byte[] nonce = new byte[12];
        System.arraycopy(instanceId, 0, nonce, 0, 4);
        for (int i = 0; i < 8; i++) {
            nonce[11 - i] = (byte) (count >> (i * 8));
        }

        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(128, nonce));
        byte[] ciphertext = cipher.doFinal(plaintext);

        // Korrigiert: Nonce dem Geheimtext voranstellen
        byte[] result = new byte[12 + ciphertext.length];
        System.arraycopy(nonce, 0, result, 0, 12);
        System.arraycopy(ciphertext, 0, result, 12, ciphertext.length);

        return result;
    }
}

Die Korrektur stellt einzigartige Nonces durch Zähler, Zufallsgenerierung oder eine Kombination aus beidem sicher.


Ausgenutzt in der Praxis

WPA2 KRACK-Angriff (WiFi, 2017)

Der KRACK (Key Reinstallation Attack) nutzte Nonce-Wiederverwendung im Vier-Wege-Handshake des WPA2-Protokolls aus und ermöglichte Angreifern die Entschlüsselung von WiFi-Verkehr.

Stromchiffre Nonce-Wiederverwendung (Verschiedene, Historisch)

Mehrere Implementierungen von Stromchiffren wurden durch Nonce-Wiederverwendung kompromittiert, was XOR-Angriffe zur Wiederherstellung von Klartext ermöglichte.


Tools zum Testen/Ausnutzen


CVE-Beispiele


Referenzen

  1. MITRE Corporation. "CWE-323: Reusing a Nonce, Key Pair in Encryption." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/323.html

  2. Rogaway, P. "Nonce-Based Symmetric Encryption." https://web.cs.ucdavis.edu/~rogaway/papers/nonce.pdf

  3. NIST. "Recommendation for Block Cipher Modes of Operation." SP 800-38D. https://csrc.nist.gov/publications/detail/sp/800-38d/final