Verwendung von Passwort-Hash mit unzureichendem Rechenaufwand

Beschreibung

Die Verwendung von Passwort-Hash mit unzureichendem Rechenaufwand tritt auf, wenn Software einen Passwort-Hashing-Algorithmus verwendet, der keine signifikanten Rechenressourcen zur Berechnung erfordert. Schnelle Hash-Funktionen wie MD5, SHA-1 oder sogar SHA-256 ohne Key-Stretching können milliardenfach pro Sekunde auf modernen GPUs berechnet werden. Dies ermöglicht es Angreifern, Brute-Force-Angriffe effizient durchzuführen und riesige Mengen an Passwortkombinationen in angemessener Zeit zu testen.

Risiko

Moderne GPUs können Milliarden von MD5- oder SHA-256-Hashes pro Sekunde berechnen. Eine mit schnellen Algorithmen gehashte Passwortdatenbank kann offline ohne Rate-Limiting angegriffen werden. Der LinkedIn-Breach von 2012 zeigte, wie schnell ungesalzene SHA-1-Hashes geknackt werden könnten. Selbst mit Salts ermöglichen schnelle Algorithmen systematische Brute-Force-Angriffe. Passwort-Cracking-Hardware und Cloud-Dienste machen diese Angriffe für jeden zugänglich. Kritische Anwendungen, die schwaches Hashing verwenden, sind bei einem Breach unvermeidlich einer Kompromittierung von Anmeldedaten ausgesetzt.

Lösung

Verwenden Sie Passwort-Hashing-Algorithmen, die speziell dafür entwickelt wurden, rechenintensiv zu sein: bcrypt, scrypt oder Argon2. Konfigurieren Sie Work-Faktoren angemessen - der Hash sollte mindestens 100ms zur Berechnung benötigen. Erhöhen Sie die Work-Faktoren im Laufe der Zeit, wenn die Hardware besser wird. Argon2 (Gewinner des Password Hashing Competition) ist die aktuelle Best Practice. Verwenden Sie speicherintensive Funktionen, die GPU/ASIC-Beschleunigung widerstehen. Implementieren Sie automatisches Rehashing bei der Benutzerauthentifizierung, um alte Hashes zu aktualisieren.

Häufige Auswirkungen

AuswirkungDetails
VertraulichkeitBereich: Passwort-Cracking

Schnelle Hash-Funktionen ermöglichen Milliarden von Passwort-Vermutungen pro Sekunde.
AuthentifizierungBereich: Kompromittierung von Anmeldedaten

Geknackte Passwörter führen zur Übernahme von Konten.
ComplianceBereich: Regulatorische Verstöße

Viele Vorschriften erfordern starke Passwortspeicherung (DSGVO, PCI-DSS).

Beispielcode + Lösungscode

Anfälliger Code

# ANFÄLLIG: Schnelle Hash-Funktion (SHA-256)
import hashlib
import os

def hash_password_vulnerable(password):
    # SHA-256 ist schnell - Milliarden pro Sekunde auf GPU!
    salt = os.urandom(16)
    hash_value = hashlib.sha256(salt + password.encode()).hexdigest()
    return f"{salt.hex()}:{hash_value}"

# ANFÄLLIG: Einzelne Iteration
def hash_password_single_iteration(password, salt):
    return hashlib.pbkdf2_hmac('sha256', password.encode(), salt, 1)

# ANFÄLLIG: Niedrige Iterationszahl
def hash_password_low_iterations(password):
    salt = os.urandom(16)
    # 1000 Iterationen sind viel zu wenig!
    key = hashlib.pbkdf2_hmac('sha256', password.encode(), salt, 1000)
    return f"{salt.hex()}:{key.hex()}"

# ANFÄLLIG: MD5 - extrem schnell
def hash_password_md5(password):
    return hashlib.md5(password.encode()).hexdigest()
// ANFÄLLIG: Schnelle Hash-Funktion
import java.security.MessageDigest;
import java.util.Base64;

public class VulnerablePasswordHash {

    // SHA-256 ohne Key-Stretching - viel zu schnell!
    public String hashPassword(String password, byte[] salt) throws Exception {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        md.update(salt);
        byte[] hash = md.digest(password.getBytes("UTF-8"));
        return Base64.getEncoder().encodeToString(hash);
    }

    // ANFÄLLIG: Niedrige PBKDF2-Iterationen
    public String hashPasswordWeakPBKDF2(String password) throws Exception {
        byte[] salt = new byte[16];
        new SecureRandom().nextBytes(salt);

        // 1000 Iterationen sind unzureichend!
        PBEKeySpec spec = new PBEKeySpec(
            password.toCharArray(),
            salt,
            1000,  // ZU NIEDRIG!
            256
        );

        SecretKeyFactory factory =
            SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        byte[] hash = factory.generateSecret(spec).getEncoded();

        return Base64.getEncoder().encodeToString(salt) + ":" +
               Base64.getEncoder().encodeToString(hash);
    }
}

Korrigierter Code

# SICHER: Verwendung von bcrypt mit angemessenem Work-Faktor
import bcrypt

def hash_password_bcrypt(password):
    # Work-Faktor 12 = 2^12 Iterationen
    # Anpassen basierend auf Ihren Performance-Anforderungen
    salt = bcrypt.gensalt(rounds=12)
    hashed = bcrypt.hashpw(password.encode(), salt)
    return hashed.decode()

def verify_password_bcrypt(password, stored_hash):
    return bcrypt.checkpw(password.encode(), stored_hash.encode())

# SICHER: Verwendung von Argon2 (empfohlen)
from argon2 import PasswordHasher, Type
from argon2.exceptions import VerifyMismatchError

# Argon2id ist die empfohlene Variante
ph = PasswordHasher(
    time_cost=3,          # Anzahl der Iterationen
    memory_cost=65536,    # 64 MB Speicher
    parallelism=4,        # 4 parallele Threads
    hash_len=32,          # Ausgabelange
    salt_len=16,          # Salt-Länge
    type=Type.ID          # Argon2id
)

def hash_password_argon2(password):
    return ph.hash(password)

def verify_password_argon2(password, stored_hash):
    try:
        # Verify prüft auch, ob Rehashing erforderlich ist
        ph.verify(stored_hash, password)

        # Prüfen, ob Parameter aktualisiert werden müssen
        if ph.check_needs_rehash(stored_hash):
            return True, hash_password_argon2(password)  # Neuen Hash zurückgeben

        return True, None
    except VerifyMismatchError:
        return False, None

# SICHER: PBKDF2 mit hoher Iterationszahl
import hashlib
import os

# OWASP 2023 Empfehlungen für PBKDF2
PBKDF2_ITERATIONS = 600000  # Für SHA-256

def hash_password_pbkdf2(password):
    salt = os.urandom(32)  # 32 Bytes = 256 Bits

    key = hashlib.pbkdf2_hmac(
        'sha256',
        password.encode(),
        salt,
        PBKDF2_ITERATIONS,
        dklen=32
    )

    # Iterationszahl für Zukunftssicherheit speichern
    return f"pbkdf2:sha256:{PBKDF2_ITERATIONS}:{salt.hex()}:{key.hex()}"
// SICHER: Java Passwort-Hashing mit bcrypt
import org.mindrot.jbcrypt.BCrypt;

public class SecurePasswordHash {

    // Work-Faktor 12 ist ein guter Ausgangspunkt
    // Mit der Zeit erhöhen, wenn Hardware besser wird
    private static final int WORK_FACTOR = 12;

    public String hashPassword(String password) {
        return BCrypt.hashpw(password, BCrypt.gensalt(WORK_FACTOR));
    }

    public boolean verifyPassword(String password, String storedHash) {
        return BCrypt.checkpw(password, storedHash);
    }

    // Prüfen, ob Rehashing erforderlich ist (Work-Faktor erhöht)
    public boolean needsRehash(String storedHash) {
        // Work-Faktor aus bcrypt-Hash extrahieren
        String[] parts = storedHash.split("\\$");
        if (parts.length >= 4) {
            int currentWorkFactor = Integer.parseInt(parts[2]);
            return currentWorkFactor < WORK_FACTOR;
        }
        return true;
    }
}

// SICHER: Verwendung von Argon2 in Java
import de.mkammerer.argon2.Argon2;
import de.mkammerer.argon2.Argon2Factory;

public class Argon2PasswordHash {

    private final Argon2 argon2;

    public Argon2PasswordHash() {
        // Argon2id verwenden (empfohlen)
        this.argon2 = Argon2Factory.create(Argon2Factory.Argon2Types.ARGON2id);
    }

    public String hashPassword(String password) {
        // Iterationen, Speicher (KB), Parallelismus
        return argon2.hash(
            3,      // Iterationen
            65536,  // 64 MB Speicher
            4,      // Parallelismus
            password.toCharArray()
        );
    }

    public boolean verifyPassword(String password, String storedHash) {
        try {
            return argon2.verify(storedHash, password.toCharArray());
        } finally {
            // Passwort aus Speicher löschen
            argon2.wipeArray(password.toCharArray());
        }
    }
}

Ausgenutzt in der Praxis

RockYou-Breach (2009)

32 Millionen Passwörter, die im Klartext gespeichert wurden, wurden gestohlen, was die Bedeutung eines ordnungsgemäßen Passwort-Hashings demonstrierte. Dieser Breach lieferte Passwortlisten, die noch heute verwendet werden.

LinkedIn-Breach (2012)

6,5 Millionen SHA-1-gehashte Passwörter (ohne Salt) wurden innerhalb von Stunden mit GPU-Beschleunigung geknackt, was viele weitere im vollständigen Breach von 117 Millionen Konten betraf, der 2016 enthüllt wurde.

Ashley Madison (2015)

37 Millionen Konten wurden gehackt. Obwohl sie bcrypt verwendeten, hatten ältere Konten MD5-Hashes, die schnell geknackt wurden.


Tools zum Testen/Ausnutzen


CVE-Beispiele


Referenzen

  1. MITRE. "CWE-916: Use of Password Hash With Insufficient Computational Effort." https://cwe.mitre.org/data/definitions/916.html

  2. OWASP. "Password Storage Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html