Unzureichende Entropie

Beschreibung

Unzureichende Entropie ist eine Schwachstelle, die auftritt, wenn ein Produkt einen Algorithmus oder ein Schema verwendet, das Werte mit unzureichender Zufälligkeit erzeugt, wodurch Muster oder Cluster von Werten entstehen, die wahrscheinlicher auftreten als ändere. Entropie ist ein Maß für Unvorhersagbarkeit oder Zufälligkeit in einem System. Wenn Sicherheitsmechanismen auf unvorhersagbare Werte angewiesen sind, diese Werte aber mit unzureichender Entropie generiert werden, können Angreifer die Muster ausnutzen, um mögliche Werte vorherzusagen oder einzugrenzen. Dies betrifft kryptografische Schlüssel, Session-Identifikatoren, Authentifizierungs-Tokens und alle sicherheitsrelevanten Werte, die von Zufälligkeit abhängen. Selbst bei Verwendung eines ordnungsgemäßen PRNG-Algorithmus kann unzureichende Entropie im Seed oder Input die Ausgabe vorhersagbar machen.

Risiko

Unzureichende Entropie erzeugt vorhersagbare Muster, die Angreifer ausnutzen können, um Sicherheitsmechanismen zu kompromittieren. Wenn Session-IDs nur 16 Bits effektive Entropie statt der beabsichtigten 128 Bits haben, können Angreifer alle möglichen Werte in Sekunden statt Jahrtausenden durchprobieren. Kryptografische Schlüssel mit niedriger Entropie können per Brute-Force geknackt werden, selbst bei Verwendung stärker Algorithmen. Passwort-Reset-Tokens, CSRF-Tokens und API-Schlüssel werden erratbar. Das Risiko wird oft unterschätzt, da die Werte bei oberflächlicher Betrachtung zufällig erscheinen können, statistische Analyse jedoch ausnutzbare Muster offenbart. Reale Angriffe haben ganze kryptografische Systeme durch Entropie-Schwächen kompromittiert - der Debian-OpenSSL-Bug, der den Schlüsselraum auf 65.536 Möglichkeiten reduzierte, betraf tausende Systeme. Zeitbasierte Seeds, zählerbasierte Generierung und andere unzureichende Entropiequellen haben Session-Hijacking, Zertifikatsfälschung und Authentifizierungs-Bypass ermöglicht.

Lösung

Ermitteln Sie die Entropieanforderungen für Ihren Sicherheitskontext und stellen Sie sicher, dass Ihre Zufallswertgenerierung diese Anforderungen erfüllt. Verwenden Sie kryptografisch sichere Zufallszahlengeneratoren, die aus hochwertigen Entropiequellen gespeist werden, wie Hardware-Zufallszahlengeneratoren oder Betriebssystem-Entropiepools (/dev/urandom unter Linux, CryptGenRandom unter Windows). Für kryptografische Schlüssel verwenden Sie mindestens 128 Bits Entropie (256 Bits empfohlen für langfristige Sicherheit). Für Session-IDs und Tokens verwenden Sie mindestens 128 Bits Entropie. Leiten Sie Entropie niemals ausschließlich aus vorhersagbaren Quellen wie Zeitstempeln, Prozess-IDs oder benutzergesteuerten Daten ab. Validieren Sie Entropiequellen während des Testens mit statistischen Analysewerkzeugen. Erwägen Sie die Verwendung von Entropieschätzung, um ausreichende Zufälligkeit vor der Verwendung generierter Werte zu verifizieren.

Häufige Auswirkungen

AuswirkungDetails
ZugriffskontrolleUmfang: Zugriffskontrolle

Angreifer können Authentifizierungs-Tokens, Session-Identifikatoren oder Zugangsdaten vorhersagen und so Sicherheitskontrollen umgehen, um unbefugten Zugriff auf Systeme und Daten zu erlangen.
VertraulichkeitUmfang: Vertraulichkeit

Kryptografische Schlüssel mit unzureichender Entropie können per Brute-Force geknackt oder vorhergesagt werden, was die Vertraulichkeit verschlüsselter Daten und Kommunikation kompromittiert.
IntegritätUmfang: Integrität

Vorhersagbare Werte in Integritätsmechanismen wie CSRF-Tokens oder Message-Authentication-Codes ermöglichen Angreifern, gültige Tokens zu fälschen und Integritätsprüfungen zu umgehen.

Beispielcode

Anfälliger Code (Python/Java)

Die folgenden Beispiele demonstrieren Schwachstellen durch unzureichende Entropie:

# Anfällig: Unzureichende Entropie bei Zufallswertgenerierung
import random
import time
import os

# Anfällig: Nur Zeitstempel für Seed verwendet
def anfällige_session_id():
    # Anfällig: time.time() hat niedrige Entropie (Sekunden-Präzision)
    # Angreifer, der ungefähre Zeit kennt, kann Suchraum eingrenzen
    random.seed(int(time.time()))
    return hex(random.getrandbits(128))[2:]

# Anfällig: Kombination schwacher Entropiequellen
def anfälliges_token(user_id):
    # Anfällig: Benutzer-ID + Zeitstempel = vorhersagbar
    # Beide Werte sind für Angreifer ermittelbar
    seed = user_id ^ int(time.time())
    random.seed(seed)
    return format(random.getrandbits(64), '016x')

# Anfällig: Zählerbasierte Entropie
class AnfälligerZähler:
    zähler = 0

    @classmethod
    def generieren(cls):
        # Anfällig: Zähler bietet keine Entropie
        cls.zähler += 1
        return format(cls.zähler, '032x')

# Anfällig: PID + Zeit - sehr begrenzte Entropie
def anfällige_nonce():
    # Anfällig: PID-Bereich ist typischerweise 1-65535
    # Kombiniert mit Zeit ergibt < 32 Bits effektive Entropie
    pid = os.getpid()
    zeitstempel = int(time.time())
    return f"{pid:05d}{zeitstempel:010d}"

# Anfällig: Kurzer Seed aus Passwort
def anfälliger_schlüssel_aus_passwort(passwort):
    # Anfällig: Nur erste 4 Zeichen des Passworts verwendet
    # Reduziert Entropie drastisch
    seed = sum(ord(c) for c in passwort[:4])
    random.seed(seed)
    return bytes([random.randint(0, 255) for _ in range(32)])

# Anfällig: Unzureichende Bits
def anfälliges_kurzes_token():
    # Anfällig: Nur 16 Bits - 65536 Möglichkeiten
    random.seed(os.urandom(16))  # Guter Seed, aber...
    return format(random.getrandbits(16), '04x')  # Zu kurz!
// Anfällig: Unzureichende Entropie in Java
import java.util.Random;

public class AnfälligeEntropie {

    // Anfällig: Nur zeitbasierter Seed
    public String anfälligeSessionId() {
        // Anfällig: System.currentTimeMillis() hat begrenzte Entropie
        // Typischerweise 1000 Werte pro Sekunde
        Random rand = new Random(System.currentTimeMillis());
        return Long.toHexString(rand.nextLong());
    }

    // Anfällig: Kombination schwacher Quellen
    public String anfälligesToken(int userId) {
        // Anfällig: Beide Werte vorhersagbar
        long seed = userId ^ System.currentTimeMillis();
        Random rand = new Random(seed);
        return Long.toHexString(rand.nextLong());
    }

    // Anfällig: Unzureichende Seed-Bits
    public byte[] anfälligerSchlüssel() {
        // Anfällig: Random() Standard verwendet nur 48 Bits
        Random rand = new Random();
        byte[] key = new byte[32];
        rand.nextBytes(key);
        return key;  // Nur 48 Bits Entropie trotz 256-Bit-Schlüssel!
    }

    // Anfällig: Hash vorhersagbarer Daten
    public String anfälligerHash(String benutzername) throws Exception {
        // Anfällig: Hash fügt keine Entropie hinzu
        String data = benutzername + System.currentTimeMillis();
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] hash = md.digest(data.getBytes());
        return bytesToHex(hash);  // Sieht zufällig aus, ist aber vorhersagbar!
    }

    // Anfällig: Sequentiell mit Verschleierung
    private long zähler = 0;

    public String anfälligeSequenz() {
        // Anfällig: XOR fügt keine Entropie hinzu
        zähler++;
        return Long.toHexString(zähler ^ 0xDEADBEEF);
    }
}
// Anfällig: Unzureichende Entropie in C
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

// Anfällig: Nur Zeit als Seed
void anfällige_init_random() {
    // Anfällig: Nur ~32 Bits Entropie
    srand(time(NULL));
}

// Anfällig: PID-basierte Entropie
unsigned int anfälliger_seed() {
    // Anfällig: PID hat < 16 Bits Entropie
    // Zeit fügt vielleicht 20 Bits hinzu
    return getpid() ^ time(NULL);
}

// Anfällig: Stack-Adress-Entropie
char* anfällige_adress_seed() {
    // Anfällig: ASLR bietet begrenzte Entropie
    int stack_var;
    srand((unsigned int)&stack_var);

    static char buffer[33];
    for (int i = 0; i < 32; i++) {
        buffer[i] = "0123456789abcdef"[rand() % 16];
    }
    buffer[32] = '\0';
    return buffer;
}

// Anfällig: Zähler mit schwacher Mischung
static unsigned long zähler = 0;

unsigned long anfällige_zähler_mischung() {
    // Anfällig: Mathematische Operationen fügen keine Entropie hinzu
    zähler++;
    return (zähler * 6364136223846793005ULL + 1442695040888963407ULL);
}

// Anfällig: Datei mit unzureichender Entropie
void anfälliger_schlüssel_aus_datei(unsigned char *key, int len) {
    // Anfällig: /dev/random hat möglicherweise nicht genug Entropie
    // Kann blockieren oder unzureichende Daten zurückgeben
    FILE *f = fopen("/dev/random", "rb");  // Sollte /dev/urandom verwenden
    int read = fread(key, 1, len, f);
    // Prüft nicht, ob read < len!
    fclose(f);
}

Korrigierter Code (Python/Java)

# Korrigiert: Ordnungsgemäße Entropie bei Zufallswertgenerierung
import secrets
import os
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes

# Korrigiert: System-Entropiequelle
def sichere_session_id():
    # Korrigiert: secrets verwendet OS-Entropie
    # 256 Bits Entropie
    return secrets.token_hex(32)

# Korrigiert: Ausreichende Entropie für Tokens
def sicheres_token():
    # Korrigiert: Mindestens 128 Bits, 256 zur Sicherheit
    return secrets.token_urlsafe(32)

# Korrigiert: Kryptografischer Schlüssel mit ordnungsgemäßer Entropie
def sicherer_schlüssel_generieren(lange=32):
    # Korrigiert: os.urandom liest von /dev/urandom
    return os.urandom(lange)

# Korrigiert: Abgeleiteter Schlüssel mit ordnungsgemäßer Entropie
def sicherer_abgeleiteter_schlüssel(master_key, info, lange=32):
    # Korrigiert: HKDF mit ausreichender Master-Key-Entropie verwenden
    hkdf = HKDF(
        algorithm=hashes.SHA256(),
        length=lange,
        salt=os.urandom(16),  # Zufälliger Salt
        info=info.encode()
    )
    return hkdf.derive(master_key)

# Korrigiert: Verifizierungscode mit begrenzter Entropie
def sicherer_verifizierungscode(ziffern=6):
    # Korrigiert: secrets.randbelow verwenden
    max_wert = 10 ** ziffern
    return format(secrets.randbelow(max_wert), f'0{ziffern}d')

# Korrigiert: Sichere Nonce mit ausreichenden Bits
def sichere_nonce(bits=128):
    # Korrigiert: Explizite Entropieanforderung
    byte_lange = bits // 8
    return os.urandom(byte_lange)

# Korrigiert: Passwortbasierter Schlüssel mit ordnungsgemäßer Entropie-Streckung
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC

def sicherer_schlüssel_aus_passwort(passwort, salt=None):
    # Korrigiert: PBKDF2 streckt begrenzte Passwort-Entropie
    if salt is None:
        salt = os.urandom(16)

    kdf = PBKDF2HMAC(
        algorithm=hashes.SHA256(),
        length=32,
        salt=salt,
        iterations=600000
    )
    return kdf.derive(passwort.encode()), salt
// Korrigiert: Ordnungsgemäße Entropie in Java
import java.security.SecureRandom;
import java.util.Base64;
import javax.crypto.*;
import javax.crypto.spec.*;

public class SichereEntropie {

    private SecureRandom secureRandom;

    public SichereEntropie() throws Exception {
        // Korrigiert: Stärke SecureRandom-Instanz
        this.secureRandom = SecureRandom.getInstanceStrong();
    }

    // Korrigiert: Session-ID mit ausreichender Entropie
    public String sichereSessionId() {
        // Korrigiert: 256 Bits Entropie
        byte[] bytes = new byte[32];
        secureRandom.nextBytes(bytes);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
    }

    // Korrigiert: Token mit spezifizierter Entropie
    public String sicheresToken(int entropieBits) {
        // Korrigiert: Explizite Entropieanforderung
        int bytes = entropieBits / 8;
        byte[] tokenBytes = new byte[bytes];
        secureRandom.nextBytes(tokenBytes);
        return bytesToHex(tokenBytes);
    }

    // Korrigiert: Kryptografischer Schlüssel mit voller Entropie
    public byte[] sichererSchlüssel(int keySize) {
        // Korrigiert: SecureRandom bietet volle Entropie
        byte[] key = new byte[keySize];
        secureRandom.nextBytes(key);
        return key;
    }

    // Korrigiert: Nonce mit expliziter Entropie
    public byte[] sichereNonce(int bits) {
        byte[] nonce = new byte[bits / 8];
        secureRandom.nextBytes(nonce);
        return nonce;
    }

    // Korrigiert: Passwortbasierte Schlüsselableitung
    public byte[] sichererSchlüsselAusPasswort(String passwort, byte[] salt)
            throws Exception {
        // Korrigiert: PBKDF2 behandelt Passwort-Entropie ordnungsgemäß
        if (salt == null) {
            salt = new byte[16];
            secureRandom.nextBytes(salt);
        }

        SecretKeyFactory factory = SecretKeyFactory.getInstance(
            "PBKDF2WithHmacSHA256");
        KeySpec spec = new PBEKeySpec(
            passwort.toCharArray(),
            salt,
            600000,  // Hohe Iterationen
            256
        );
        return factory.generateSecret(spec).getEncoded();
    }

    // Korrigiert: Verifizierungscode
    public String sichererVerifizierungscode(int ziffern) {
        // Korrigiert: Unverzerrte Zufallszahlengenerierung
        int max = (int) Math.pow(10, ziffern);
        int code = secureRandom.nextInt(max);
        return String.format("%0" + ziffern + "d", code);
    }

    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 Entropie in C
#include <openssl/rand.h>
#include <stdio.h>
#include <string.h>

// Korrigiert: OpenSSL RAND_bytes verwenden
int sichere_zufalls_bytes(unsigned char *buffer, int len) {
    // Korrigiert: RAND_bytes verwendet System-Entropie
    if (RAND_bytes(buffer, len) != 1) {
        return -1;  // Entropiegenerierung fehlgeschlagen
    }
    return 0;
}

// Korrigiert: Session-ID mit voller Entropie
int sichere_session_id(char *session_id, size_t max_len) {
    unsigned char bytes[32];  // 256 Bits

    // Korrigiert: Volle Entropie vom CSPRNG
    if (RAND_bytes(bytes, sizeof(bytes)) != 1) {
        return -1;
    }

    // In Hex konvertieren
    for (size_t i = 0; i < sizeof(bytes) && i * 2 < max_len - 1; i++) {
        sprintf(session_id + (i * 2), "%02x", bytes[i]);
    }

    return 0;
}

// Korrigiert: /dev/urandom korrekt verwenden
int sicher_aus_urandom(unsigned char *buffer, size_t len) {
    // Korrigiert: /dev/urandom ist nicht-blockierend und für Krypto geeignet
    FILE *f = fopen("/dev/urandom", "rb");
    if (!f) return -1;

    size_t read = fread(buffer, 1, len, f);
    fclose(f);

    // Korrigiert: Verifizieren, dass alle angeforderten Bytes erhalten wurden
    if (read != len) {
        return -1;
    }

    return 0;
}

// Korrigiert: Schlüsselgenerierung mit Entropie-Verifizierung
int sicheren_schlüssel_generieren(unsigned char *key, int len) {
    // Korrigiert: RAND_bytes für kryptografische Schlüssel verwenden
    if (RAND_bytes(key, len) != 1) {
        return -1;
    }

    // Korrigiert: Optionale Entropie-Statusprüfung
    if (RAND_status() != 1) {
        // Unzureichende Entropie im Pool
        return -1;
    }

    return 0;
}

// Korrigiert: getrandom() auf modernem Linux verwenden
#ifdef __linux__
#include <sys/random.h>

int sicher_getrandom(unsigned char *buffer, size_t len) {
    // Korrigiert: getrandom() ist der bevorzugte Syscall
    ssize_t result = getrandom(buffer, len, 0);
    return (result == (ssize_t)len) ? 0 : -1;
}
#endif

Die Korrektur verwendet kryptografisch sichere Quellen mit ausreichenden Entropie-Bits für den Sicherheitskontext.


Ausgenutzt in der Praxis

Debian OpenSSL schwache Schlüssel (CVE-2008-0166)

Ein Programmierfehler im Debian-OpenSSL-Paket führte dazu, dass der PRNG nur die Prozess-ID als Entropie verwendete und so nur 65.536 eindeutige Schlüssel auf allen betroffenen Systemen generierte.

Session-Token-Vorhersage (CVE-2001-0950)

Session-Tokens, die mit C rand() und zeitbasiertem Seeding generiert wurden, ermöglichten Angreifern, Tokens vorherzusagen und Sessions zu kapern.


Tools zum Testen/Ausnutzen

  • ent — Entropie-Analyseprogramm zum Testen der Zufallszahlenqualität.

  • NIST Statistical Test Suite — Umfassendes Zufälligkeits-Testing.

  • untwister — Tool zur Wiederherstellung von PRNG-Zuständen aus beobachteten Ausgaben.


CVE-Beispiele

  • CVE-2001-0950 — Session-Tokens mit rand() und unzureichender Entropie.

  • CVE-2008-2108 — Präzisionsfehler reduziert Entropie im Zufallsgenerator.


Referenzen

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

  2. NIST. "Recommendation for Random Number Generation." SP 800-90A Rev 1. https://csrc.nist.gov/publications/detail/sp/800-90a/rev-1/final

  3. Goldberg, I., Wagner, D. "Randomness and the Netscape Browser." Dr. Dobb's Journal, 1996.