Unzureichende Entropie in PRNG

Beschreibung

Unzureichende Entropie in PRNG ist eine Schwachstelle, die auftritt, wenn einem Pseudo-Zufallszahlengenerator (PRNG) beim Seeding oder Betrieb ausreichende Entropie fehlt, was sowohl Stabilitäts- als auch Sicherheitsbedrohungen erzeugt. PRNGs benötigen Entropie aus externen Quellen, um unvorhersagbare Ausgaben zu erzeugen - ohne ausreichende Entropie wird die PRNG-Ausgabe vorhersagbar. Diese Schwäche kann sich auf zwei Arten manifestieren: Wenn der PRNG bei Entropieerschöpfung geschlossen ausfällt, kann die Anwendung hängen bleiben oder abstürzen (Verfügbarkeitsauswirkung); wenn er offen ausfällt, indem er Ausgaben ohne ausreichende Entropie erzeugt, werden die generierten Werte vorhersagbar (Sicherheitsauswirkung). Der letztere Fall ist besonders gefährlich, da er kryptografische Operationen stillschweigend schwächt, während er normal zu funktionieren scheint.

Risiko

Unzureichende PRNG-Entropie erzeugt kritische Sicherheitslücken in mehreren Dimensionen. Wenn PRNGs während der Schlüsselgenerierung keine Entropie haben, können die resultierenden kryptografischen Schlüssel vorhergesagt oder per Brute-Force geknackt werden. Bitcoin-Wallets, die mit unzureichender Entropie generiert wurden, wurden ausgenutzt, um Kryptowährung zu stehlen. Session-Tokens und Authentifizierungs-Cookies werden vorhersagbar, was Session-Hijacking ermöglicht. SSL/TLS-Verbindungen, die schwache Entropie für den Schlüsselaustausch verwenden, können von Angreifern gebrochen werden, die die Session-Schlüssel rekonstruieren können. Das Risiko wird erhöht, weil diese Ausfälle oft still sind - Anwendungen produzieren weiterhin zufällig aussehende Ausgaben, die oberflächliche Inspektion bestehen, aber unter statistischer Analyse scheitern. Systeme, die Entropie erschöpfen und auf schwächere Quellen zurückgreifen (wie Math.random() anstelle eines CSPRNG), erzeugen besonders gefährliche Bedingungen, bei denen sich die Sicherheit stillschweigend verschlechtert.

Lösung

Verwenden Sie PRNGs, die für kryptografische Zwecke konzipiert und ordnungsgemäß mit hochwertiger Entropie gespeist werden. Wählen Sie FIPS 140-2-konforme Zufallszahlengeneratoren, wenn Compliance erforderlich ist. Implementieren Sie PRNGs, die automatisch aus Hardware-Entropiequellen neu seeden, wenn verfügbar. Verwenden Sie vom Betriebssystem bereitgestellte CSPRNGs wie /dev/urandom unter Unix oder CryptGenRandom unter Windows, die Entropiepools angemessen verwalten. Fallen Sie niemals auf schwache Zufallsquellen zurück, wenn die sichere Quelle ausfällt - stattdessen die Operation vollständig fehlschlagen lassen. Überwachen Sie Entropieniveaus in kritischen Systemen und alarmieren Sie, wenn die Entropie niedrig wird. Erwägen Sie Hardware-Zufallszahlengeneratoren (HRNGs) für Hochsicherheitsanwendungen. Entwerfen Sie Anwendungen so, dass sie Entropieerschöpfung elegant handhaben, ohne die Sicherheit zu beeinträchtigen.

Häufige Auswirkungen

AuswirkungDetails
VerfügbarkeitUmfang: Verfügbarkeit

Wenn ein PRNG seine Entropiequelle erschöpft und geschlossen ausfällt, können Anwendungen pausieren, blockieren oder abstürzen, während sie auf mehr Entropie warten, was Denial-of-Service verursacht.
ZugriffskontrolleUmfang: Zugriffskontrolle

Wenn der PRNG offen ausfällt, wenn die Entropie erschöpft ist, erzeugt er vorhersagbare Werte, die Authentifizierungs- und Autorisierungsmechanismen schwächen und unbefugten Zugriff ermöglichen.
VertraulichkeitUmfang: Vertraulichkeit

Mit unzureichender PRNG-Entropie generierte Verschlüsselungsschlüssel können vorhergesagt oder per Brute-Force geknackt werden, was die Vertraulichkeit verschlüsselter Daten kompromittiert.

Beispielcode

Anfälliger Code (Python/Java)

Die folgenden Beispiele demonstrieren unzureichende PRNG-Entropie:

# Anfällig: PRNG mit unzureichender Entropie-Behandlung
import random
import time

# Anfällig: Fallback auf schwachen Zufall bei Fehler
def anfällige_schlüssel_generierung():
    try:
        import secrets
        return secrets.token_bytes(32)
    except Exception:
        # Anfällig: Fallback auf schwachen Zufall!
        random.seed(time.time())
        return bytes([random.randint(0, 255) for _ in range(32)])

# Anfällig: random-Modul für Krypto verwenden
def anfälliges_session_token():
    # Anfällig: random-Modul ist nicht kryptografisch sicher
    # Verwendet Mersenne Twister, der vorhersagbar ist
    return format(random.getrandbits(128), '032x')

# Anfällig: Seeding mit unzureichender Entropie
def anfällige_prng_init():
    # Anfällig: Nur 32 Bits Entropie aus Zeit
    seed = int(time.time())
    random.seed(seed)
    return random

# Anfällig: PRNG ohne Reseeding
class AnfälligerStatischerPRNG:
    def __init__(self):
        # Anfällig: Einmal beim Start geseedet
        self.rng = random.Random()
        self.rng.seed(time.time())

    def generieren(self, lange):
        # Anfällig: Seedet nie neu, Entropie erschöpft sich über Zeit
        return bytes([self.rng.randint(0, 255) for _ in range(lange)])

# Anfällig: Benutzerdefinierter PRNG mit schwacher Entropie
class AnfälligerLCG:
    def __init__(self, seed=None):
        # Anfällig: Linearer Kongruenzgenerator
        # mit vorhersagbaren Parametern
        self.state = seed or int(time.time())

    def next(self):
        # Anfällig: LCG ist vollständig vorhersagbar
        self.state = (self.state * 1103515245 + 12345) & 0x7FFFFFFF
        return self.state
// Anfällig: Unzureichende PRNG-Entropie in Java
import java.util.Random;

public class AnfälligerPRNG {

    // Anfällig: java.util.Random für Sicherheit verwenden
    private Random schwacherRandom = new Random();

    // Anfällig: Fallback auf schwachen Zufall
    public byte[] anfälligeSchlüsselGenerierung(int lange) {
        byte[] key = new byte[lange];
        try {
            java.security.SecureRandom.getInstanceStrong().nextBytes(key);
        } catch (Exception e) {
            // Anfällig: Fallback auf schwachen Zufall!
            schwacherRandom.nextBytes(key);
        }
        return key;
    }

    // Anfällig: Zeitbasiert geseedeter Random für Tokens
    public String anfälligesToken() {
        // Anfällig: java.util.Random hat nur 48 Bits Seed
        Random r = new Random(System.currentTimeMillis());
        return Long.toHexString(r.nextLong());
    }

    // Anfällig: Gemeinsame Random-Instanz
    private static Random geteilterRandom = new Random();

    public byte[] anfälligerGeteilterRandom(int lange) {
        // Anfällig: Geteilte Instanz, vorhersagbar nach Beobachtung
        byte[] result = new byte[lange];
        geteilterRandom.nextBytes(result);
        return result;
    }

    // Anfällig: Math.random() für Sicherheit
    public String anfälligesMathRandom() {
        // Anfällig: Math.random() ist nicht kryptografisch
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 32; i++) {
            sb.append(String.format("%02x", (int)(Math.random() * 256)));
        }
        return sb.toString();
    }
}
// Anfällig: Unzureichende PRNG-Entropie in JavaScript
// Dieses Muster wurde in echten Kryptowährungsbibliotheken gefunden

// Anfällig: Math.random() für Krypto verwenden
function anfälligeSchlüsselGenerierung(lange) {
    const key = new Uint8Array(lange);
    for (let i = 0; i < lange; i++) {
        // Anfällig: Math.random() ist nicht kryptografisch sicher
        key[i] = Math.floor(Math.random() * 256);
    }
    return key;
}

// Anfällig: Fallback auf Math.random()
function anfälligerSichererZufall(lange) {
    try {
        const crypto = require('crypto');
        return crypto.randomBytes(lange);
    } catch (e) {
        // Anfällig: Stiller Fallback auf schwachen Zufall!
        console.warn('Verwende Fallback-Zufall');
        const result = Buffer.alloc(lange);
        for (let i = 0; i < lange; i++) {
            result[i] = Math.floor(Math.random() * 256);
        }
        return result;
    }
}

// Anfällig: Geseedeter PRNG für Wallet-Generierung
function anfälligerWalletSeed() {
    // Anfällig: Zeitstempel als einzige Entropiequelle verwenden
    const seed = Date.now();
    return customPRNG(seed);
}

// Anfällig: XorShift ohne ordnungsgemäßes Seeding
class AnfälligerXorShift {
    constructor(seed) {
        // Anfällig: Schwaches Seeding
        this.state = seed || Date.now();
    }

    next() {
        // Anfällig: Vorhersagbar bei bekanntem Zustand
        let x = this.state;
        x ^= x << 13;
        x ^= x >> 17;
        x ^= x << 5;
        this.state = x;
        return x >>> 0;
    }
}

Korrigierter Code (Python/Java)

# Korrigiert: Ordnungsgemäße PRNG-Entropie-Behandlung
import os
import secrets
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

# Korrigiert: Immer sicheren Zufall verwenden, bei Fehler abbrechen
def sichere_schlüssel_generierung(lange=32):
    # Korrigiert: secrets-Modul verwendet System-CSPRNG
    # Wirft Exception, wenn Entropie nicht verfügbar
    return secrets.token_bytes(lange)

# Korrigiert: Ordnungsgemäße Session-Token-Generierung
def sicheres_session_token():
    # Korrigiert: secrets.token_hex verwendet CSPRNG
    return secrets.token_hex(32)

# Korrigiert: Kein Fallback auf schwachen Zufall
def sichere_zufalls_bytes(lange):
    # Korrigiert: os.urandom ist der System-CSPRNG
    # Wirft Exception anstatt Sicherheit zu degradieren
    return os.urandom(lange)

# Korrigiert: PRNG mit ordnungsgemäßem Seeding und Reseeding
class SichererPRNG:
    def __init__(self):
        # Korrigiert: Aus System-Entropie initialisieren
        self.reseed()

    def reseed(self):
        # Korrigiert: Frische Entropie vom System holen
        self._state = os.urandom(32)

    def generieren(self, lange):
        # Korrigiert: AES-CTR für DRBG-Verhalten verwenden
        nonce = os.urandom(16)
        cipher = Cipher(algorithms.AES(self._state), modes.CTR(nonce))
        encryptor = cipher.encryptor()

        # Zufällige Bytes generieren
        result = encryptor.update(bytes(lange))

        # Korrigiert: Regelmäßig reseeden
        self.reseed()

        return result

# Korrigiert: Entropie-Überwachung
def sicher_mit_entropie_prüfung():
    # Korrigiert: Unter Linux kann Entropiepool geprüft werden
    try:
        with open('/proc/sys/kernel/random/entropy_avail', 'r') as f:
            entropie = int(f.read().strip())
            if entropie < 256:
                # Warten oder alarmieren, nicht mit schwacher Entropie fortfahren
                raise RuntimeError(f"Unzureichende Entropie: {entropie}")
    except FileNotFoundError:
        pass  # Nicht unter Linux, auf System verlassen

    return os.urandom(32)
// Korrigiert: Ordnungsgemäße PRNG-Entropie-Behandlung in Java
import java.security.SecureRandom;
import java.security.NoSuchAlgorithmException;

public class SichererPRNG {

    private SecureRandom secureRandom;

    public SichererPRNG() throws NoSuchAlgorithmException {
        // Korrigiert: Stärken SecureRandom verwenden
        this.secureRandom = SecureRandom.getInstanceStrong();
    }

    // Korrigiert: Immer SecureRandom verwenden, bei Fehler abbrechen
    public byte[] sichereSchlüsselGenerierung(int lange) throws NoSuchAlgorithmException {
        byte[] key = new byte[lange];
        // Korrigiert: Kein Fallback - fehlschlagen wenn Entropie nicht verfügbar
        secureRandom.nextBytes(key);
        return key;
    }

    // Korrigiert: Sichere Token-Generierung
    public String sicheresToken() {
        byte[] bytes = new byte[32];
        secureRandom.nextBytes(bytes);
        return bytesToHex(bytes);
    }

    // Korrigiert: Reseeding für langlebige Anwendungen erzwingen
    public void reseed() {
        // Korrigiert: generateSeed holt frische System-Entropie
        byte[] seed = secureRandom.generateSeed(32);
        secureRandom.setSeed(seed);
    }

    // Korrigiert: Thread-sicherer sicherer Zufall
    public byte[] threadSichererZufall(int lange) {
        // Korrigiert: ThreadLocalRandom für SecureRandom
        byte[] result = new byte[lange];
        SecureRandom.getInstanceStrong().nextBytes(result);
        return result;
    }

    // Korrigiert: DRBG-basierter SecureRandom (Java 9+)
    public SecureRandom getDRBGRandom() throws NoSuchAlgorithmException {
        // Korrigiert: DRBG-Mechanismus mit Reseeding verwenden
        return SecureRandom.getInstance("DRBG",
            DrbgParameters.instantiation(256,
                DrbgParameters.Capability.RESEED_ONLY,
                null));
    }

    private String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }
}
// Korrigiert: Ordnungsgemäße PRNG-Entropie in JavaScript
const crypto = require('crypto');

// Korrigiert: crypto.randomBytes verwenden, bei Nichtverfügbarkeit fehlschlagen
function sichereSchlüsselGenerierung(lange) {
    // Korrigiert: crypto.randomBytes verwendet System-CSPRNG
    // Wirft Fehler, wenn Entropie nicht verfügbar
    return crypto.randomBytes(lange);
}

// Korrigiert: Kein Fallback auf Math.random()
function sichererZufall(lange) {
    // Korrigiert: Vollständig fehlschlagen anstatt zu degradieren
    if (!crypto || !crypto.randomBytes) {
        throw new Error('Sicherer Zufall nicht verfügbar');
    }
    return crypto.randomBytes(lange);
}

// Korrigiert: Browser-kompatibler sicherer Zufall
function browserSichererZufall(lange) {
    // Korrigiert: Web Crypto API verwenden
    if (typeof window !== 'undefined' && window.crypto) {
        const array = new Uint8Array(lange);
        window.crypto.getRandomValues(array);
        return array;
    }
    throw new Error('Sicherer Zufall in dieser Umgebung nicht verfügbar');
}

// Korrigiert: Sichere Wallet-Seed-Generierung
function sichererWalletSeed() {
    // Korrigiert: Ordnungsgemäße Entropiequelle verwenden
    const entropie = crypto.randomBytes(32);
    // Zusätzliche Entropie vom System wenn verfügbar
    const zeitstempel = Buffer.from(Date.now().toString());
    const kombiniert = crypto.createHash('sha256')
        .update(entropie)
        .update(zeitstempel)
        .digest();
    return kombiniert;
}

// Korrigiert: Asynchroner sicherer Zufall mit expliziter Entropie-Prüfung
async function sichererZufallAsync(lange) {
    return new Promise((resolve, reject) => {
        crypto.randomBytes(lange, (err, buffer) => {
            if (err) {
                reject(new Error('Entropiegenerierung fehlgeschlagen: ' + err.message));
            } else {
                resolve(buffer);
            }
        });
    });
}

Die Korrektur verwendet kryptografisch sichere PRNGs mit ordnungsgemäßen Entropiequellen und schlägt sicher fehl, wenn Entropie unzureichend ist.


Ausgenutzt in der Praxis

JavaScript-Kryptowährungsbibliothek (CVE-2019-1715)

Eine JavaScript-Kryptowährungsbibliothek fiel auf unsicheres Math.random() zurück, anstatt einen Fehler zu melden, wenn sicherer Zufall nicht verfügbar war, was die Generierung nicht-eindeutiger Bitcoin-Wallet-Schlüssel und den Diebstahl von Geldern ermöglichte.

Android SecureRandom-Schwachstelle (2013)

Androids Java SecureRandom-Implementierung hatte auf einigen Geräten unzureichende Entropie, was zu Bitcoin-Wallet-Kompromittierungen führte, bei denen dieselben Schlüssel auf verschiedenen Geräten generiert wurden.


Tools zum Testen/Ausnutzen


CVE-Beispiele


Referenzen

  1. MITRE Corporation. "CWE-332: Insufficient Entropy in PRNG." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/332.html

  2. NIST. "Recommendation for Random Bit Generator (RBG) Constructions." SP 800-90C. https://csrc.nist.gov/publications/detail/sp/800-90c/draft

  3. FIPS 140-2. "Security Requirements for Cryptographic Modules." https://csrc.nist.gov/publications/detail/fips/140/2/final