Secure Coding: Passwort-Hashing zum Schutz vor Brute-Force und Rainbow-Tabellen

Warum sichere Passwörter wichtig sind und welche Hashing-Algorithmen Java-Anwendung sowie Benutzerkonten zuverlässig vor Hackern schützen.

vorlesen Druckansicht 37 Kommentare lesen
Mann zeigt auf VerschlĂĽsselungsmaschine

(Bild: erstellt mit Chat-GPT / DALL-E)

Lesezeit: 18 Min.
Von
  • Sven Ruppert
Inhaltsverzeichnis

Passwortsicherheit ist ein häufig unterschätztes, aber kritisches Thema in der Softwareentwicklung. Immer wieder werden Datenbanken kompromittiert, bei denen es Millionen von Benutzeranmeldungen gibt – und erschreckend häufig stellt sich heraus, dass Passwörter im Klartext gespeichert wurden. Dies ermöglicht Angreifern direkten Zugriff auf sensible Kontodaten und öffnet Tür und Tor für Identitätsdiebstahl, Kontoübernahmen und weitergehende Angriffe.

Secure Coding – Sven Ruppert
Sven Ruppert

Seit 1996 programmiert Sven Java in Industrieprojekten und seit über 15 Jahren weltweit in Branchen wie Automobil, Raumfahrt, Versicherungen, Banken, UN und Weltbank. Seit über 10 Jahren ist er von Amerika bis nach Neuseeland als Speaker auf Konferenzen und Community Events, arbeitete als Developer Advocate für JFrog und Vaadin und schreibt regelmäßig Beiträge für IT-Zeitschriften und Technologieportale. Neben seinem Hauptthema Core Java beschäftigt er sich mit TDD und Secure Coding Practices.

In diesem Beitrag schauen wir uns an, warum Passwörter immer gehasht gespeichert werden sollten, welche Angriffsmethoden es gibt und wie Entwicklerinnen und Entwickler in ihren Anwendungen eine erste sichere Implementierung mit Java umsetzen können. Dabei werfen wir unter anderem einen Blick auf Angriffsvektoren wie Brute-Force- und Rainbow-Tabellen-Attacken, die Unterschiede zwischen den Hashing-Methoden PBKDF2, BCrypt sowie Argon2 und erläutern Best Practices für den Umgang mit Passwörtern in der Softwareentwicklung.

Passwörter sollten niemals im Klartext sondern stets gehasht gespeichert werden, um die Sicherheit der Benutzerdaten zu gewährleisten und Missbrauch zu verhindern. Wird eine Datenbank kompromittiert und die Passwörter sind nur im Klartext gespeichert, haben Angreifer direkten Zugriff auf die sensiblen Zugangsdaten der Benutzer. Dies kann gravierende Folgen haben, da noch immer viele Menschen dazu neigen, dasselbe Passwort für mehrere Dienste zu verwenden. Durch das Hashen der Passwörter lässt sich dieses Risiko erheblich reduzieren, da Angreifer dann lediglich die Hash-Werte und nicht die eigentlichen Passwörter sehen.

Videos by heise

Ein zentraler Vorteil des Hashings liegt in seiner Einweg-Funktion. Ein Hash-Wert kann aus einem Passwort erzeugt werden, doch der Rückschluss auf das ursprüngliche Passwort ist nahezu unmöglich. Dieser Schutzmechanismus greift nicht nur bei externen Angriffen, sondern auch bei internen Sicherheitsrisiken. Denn im Klartext gespeicherte Passwörter, könnten beispielsweise auch nichtautorisierten internen Mitarbeitern den Zugriff auf die Informationen in der Datenbank ermöglichen. Gehashte Passwörter schließen solche Insider-Bedrohungen weitgehend aus.

Darüber hinaus fordern gesetzliche Vorgaben und Sicherheitsstandards wie die Datenschutz-Grundverordnung (DSGVO) oder der Payment Card Industry Data Security Standard (PCI-DSS) den Schutz von Passwörtern und vergleichbaren Secrets. Das Hashen von Passwörtern ist eine wichtige Maßnahme, um die einschlägigen Standards zu erfüllen und rechtliche Konsequenzen zu vermeiden.

Brute-Force- und Rainbow-Tabellen-Angriffe sind zwei Methoden, die Angreifer nutzen, um Passwörter oder andere Geheimnisse zu entschlüsseln. Ein Brute-Force-Angriff funktioniert, indem systematisch jede mögliche Kombination eines Passworts ausprobiert wird, bis das richtige gefunden ist. Dabei beginnen Angreifer in der Regel mit einfachen Kombinationen wie "aaa" oder "1234" und prüfen im Verlauf immer komplexere mögliche Variante. Obwohl Brute-Force-Angriffe theoretisch immer erfolgreich sein können, hängt ihre Effektivität in der Praxis stark von der Komplexität des Passworts und der für die Attacke eingesetzten Rechenleistung ab. Ein kurzes Passwort lässt sich häufig in wenigen Sekunden knacken, während der Aufwand mit der Länge und Komplexität des Passworts immer weiter steigt. Maßnahmen wie längere Passwörter, Begrenzung der Anmeldeversuche pro Zeiteinheit (zum Beispiel eine Sperrung nach mehreren Fehlversuchen) und der Einsatz von Hashing-Algorithmen mit vielen Iterationen machen Brute-Force-Angriffe deutlich schwieriger und zeitaufwändiger.

Im Unterschied dazu nutzen Rainbow-Tabellen-Angriffe vorgefertigte Tabellen, die eine große Anzahl von Hashwerten mit den zugehörigen Klartext-Passwörtern enthalten. Angreifer vergleichen den gespeicherten Hash eines Passworts mit denen in der Tabelle, um das ursprüngliche Passwort zu finden. Diese Methode arbeitet deutlich schneller als Brute-Force, da die Passwörter nicht jedes Mal neu gehasht werden müssen. Allerdings funktionieren Rainbow-Tabellen nur, wenn die Hashing-Methode keinen Salt-Wert verwendet. Der Salt-Wert ist ein zufälliger Wert, der jedem Passwort-Hash hinzugefügt wird, sodass selbst identische Passwörter unterschiedliche Hashes erzeugen. Ohne Salt könnten Angreifer dieselbe Tabelle für viele verschiedene Systeme verwenden, was mit Salt jedoch nicht mehr möglich ist.

Um sich vor beiden Angriffsmethoden zu schützen, ist der Einsatz moderner Hashing-Algorithmen wie PBKDF2, BCrypt oder Argon2 entscheidend. Diese Algorithmen kombinieren Salts und mehrere Iterationen, um den Aufwand für Angreifer maßgeblich zu erhöhen. Darüber hinaus machen lange und komplexe Passwörter Brute-Force-Angriffe praktisch unmöglich, da die Anzahl der möglichen Kombinationen exponentiell steigt. Zusammenfassend lässt sich sagen, dass die Kombination aus starken (möglichst langen) Passwörtern, Salts und sicheren Hashing-Algorithmen eine effektive Verteidigung gegen Brute-Force- und Rainbow-Tabellen-Angriffe bietet.

Um ein Passwort in Java sicher zu hashen, empfiehlt es sich, eine spezialisierte Hash-Funktion wie PBKDF2, BCrypt oder Argon2 zu verwenden. Diese Algorithmen wurden speziell für das sichere Hashing von Passwörtern entwickelt, um Angriffe wie Brute-Force oder Rainbow-Tabelle-Angriffe zu erschweren. Eine Möglichkeit, dies mit der Java-Standardbibliothek zu implementieren, ist die Nutzung von PBKDF2.

Zunächst wird ein Salt generiert, eine zufällige Sequenz von Bytes, die für jedes Passwort individuell erzeugt wird, um sicherzustellen, dass zwei identische Passwörter unterschiedliche Hashes haben. Mit der Klasse SecureRandom lässt sich ein 16-Byte-Salt erzeugen. Anschließend generiert man mit der Klasse PBEKeySpec einen Schlüssel basierend auf dem Passwort, dem Salt, der gewünschten Anzahl von Iterationen (z. B. 65.536) und der Schlüssellänge (z. B. 256 Bit). Mithilfe von SecretKeyFactory und der Algorithmus-Spezifikation "PBKDF2WithHmacSHA256" wird dann der Passwort-Hash erstellt. Der resultierende Hash sollte schließlich noch in Base64 kodiert werden, um ihn in einer lesbaren Form speichern zu können, die unter anderem nicht druckbare Zeichen oder Werte vermeidet.

Das gehashte Passwort wird häufig zusammen mit dem Salt gespeichert, getrennt durch einen Doppelpunkt (:). Das erleichtert später die Überprüfung, indem das Salt wieder extrahiert und für die Hash-Berechnung verwendet wird. Darüber hinaus gibt es auch externe Bibliotheken wie Spring Security oder BouncyCastle, die eine einfachere Integration von Algorithmen wie BCrypt oder Argon2 ermöglichen. Die beiden bieten im Vergleich noch mehr Sicherheit und Flexibilität.

Codebeispiel

import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Base64;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;

public class PasswordHasher {
    private static final int ITERATIONS = 65536;
    private static final int KEY_LENGTH = 256;
    private static final String ALGORITHM = "PBKDF2WithHmacSHA256";

    public static String hashPassword(String password, String salt) {
        try {
            KeySpec spec = new PBEKeySpec(
password.toCharArray(), 
salt.getBytes(), 
ITERATIONS, 
KEY_LENGTH);
            SecretKeyFactory factory = SecretKeyFactory.getInstance(ALGORITHM);
            byte[] hash = factory.generateSecret(spec).getEncoded();
            return Base64.getEncoder().encodeToString(hash);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            throw new RuntimeException("Error while hashing password", e);
        }
    }

    public static void main(String[] args) {
        String password = "meinSicheresPasswort";
        String salt = "zufaelligerSaltWert";

        String hashedPassword = hashPassword(password, salt);
        System.out.println("Gehashtes Passwort: " + hashedPassword);
    }
}

Hierbei gilt es, einige Aspekte zu berĂĽcksichtigen:

PBKDF2 ist ein bewährter und sicherer Algorithmus für das Passwort-Hashing, dessen Sicherheit in der Praxis jedoch stark von der richtigen Implementierung und den verwendeten Parametern abhängt. Die folgenden Faktoren beeinflussen die Sicherheit von PBKDF2: Ein wesentlicher Aspekt ist die Anzahl der Iterationen, die den Work-Faktor darstellen. Je höher die Iterationsanzahl ist, desto mehr Rechenleistung erfordert das Hashing. Experten empfehlen in der Regel mindestens 100.000 Iterationen, wobei für neuere Anwendungen oft noch höhere Werte gewählt werden, um Brute-Force-Angriffe zu erschweren. Ein weiterer entscheidender Faktor ist der Salt-Wert, zudem lässt sich PBKDF2 mit einer variablen Schlüssellänge wie beispielsweise 256 Bit konfigurieren, um die Sicherheit weiter zu erhöhen. Bei neueren Anwendungen kommen häufig Hash-Funktionen wie SHA-256 oder SHA-512 zum Einsatz.

PBKDF2 zeichnet sich durch mehrere Stärken aus. Der Algorithmus ist bewährt und wird seit Jahren in sicherheitskritischen Anwendungen wie WPA2 (Wi-Fi Protected Access 2) sowie verschlüsselten Speichersystemen eingesetzt. Er ist einfach zu implementieren, da es in vielen Programmiersprachen und Bibliotheken umfassende Unterstützung gibt, und er basiert auf einem anerkannten Standard, der in RFC 8018 definiert ist. Beim Einsatz von PBKDF2 sind allerdings auch Schwächen zu berücksichtigen. Der Algorithmus ist nicht speziell gegen GPU- oder ASIC-basierte Angriffe optimiert, wodurch Angreifer mit spezialisierter Hardware Brute-Force-Angriffe effizient durchführen können. Im Vergleich zu moderneren Algorithmen wie Argon2 erfordert PBKDF2 eine höhere Iterationsanzahl, um ein vergleichbares Sicherheitsniveau zu erreichen. Zudem schreitet die Weiterentwicklung von PBKDF2 langsamer voran, weshalb er gegenüber Alternativen wie Argon2 ins Hintertreffen gerät und inzwischen als weniger gut an aktuelle Bedrohungen angepasst gilt.

Dennoch lässt sich auch PBKDF2 weiterhin sicher einsetzen, wenn einige wesentliche Bedingungen erfüllt sind: Es muss ein einzigartiger Salt-Wert verwendet werden, die Iterationsanzahl sollte mindestens 100.000 betragen (idealerweise mehr) und eine moderne Hash-Funktion wie SHA-256 zum Einsatz kommen. Außerdem sollten starke Passwort-Richtlinien die Sicherheit ergänzen, beispielsweise eine ausreichende Mindestlänge.

Als Alternative zu PBKDF2 wird zunehmend Argon2 empfohlen. Der 2015 im Rahmen der Password Hashing Competition (PHC) entwickelte Algorithmus punktet insbesondere durch besseren Schutz vor GPU- und ASIC-Angriffen und bietet flexible Konfigurationsmöglichkeiten hinsichtlich Work-Faktor, Speicherbedarf und Parallelität.

Zusammenfassend lässt sich sagen, dass PBKDF2 bei richtiger Implementierung sicher ist, jedoch Schwächen gegenüber spezialisierter Hardware aufweist. Für neue Anwendungen ist Argon2 vorzuziehen, da er besser auf die Anforderungen moderner Sicherheit abgestimmt ist.

Argon2 ist ein moderner Algorithmus zum sicheren Passwort-Hashing, der im Jahr 2015 als Gewinner aus der Password Hashing Competition (PHC) hervorgegangen ist. Er gilt als einer der derzeit sichersten Ansätze zur Speicherung von Passwörtern und bietet effektiven Schutz vor Brute-Force-Angriffen sowie vor Attacken mit GPUs oder spezialisierter Hardware wie ASICs. Argon2 erreicht dies dadurch, dass der Algorithmus sowohl speicher- als auch rechenintensiv ist. Angreifer sind somit erhebliche Hardwareressourcen angewiesen.

Argon2 existiert in drei Varianten, die jeweils für unterschiedliche Anwendungsfälle optimiert sind. Die erste Variante, Argon2i, ist darauf ausgelegt, besonders speichersicher zu sein. Sie führt speicherintensive Operationen unabhängig von den Eingabedaten durch und ist daher widerstandsfähig gegen Seitenkanalangriffe wie Timing-Angriffe. Dies macht Argon2i ideal für Anwendungen, bei denen Datenschutz höchste Priorität hat.

Argon2d ist hingegen speziell für Angriffsresistenz optimiert. Diese Variante arbeitet datenabhängig, was sie gegenüber GPU-basierten Angriffen besonders robust macht. Allerdings ist Argon2d anfälliger für Seitenkanalangriffe und daher weniger geeignet, wenn es um die Sicherung sensibler Daten geht.

Eine ausgewogene Kombination beider Ansätze bietet die dritte Variante, Argon2id. Sie arbeitet zweistufig und beginnt zunächst mit einem speichersicheren Ansatz, wie er bei Argon2i verwendet wird. Anschließend wechselt sie zu einem datenabhängigen Prozess, der für Angriffsresistenz sorgt. Diese Mischung macht Argon2id zur bevorzugten Wahl für die meisten Anwendungsfälle.

Eine der wesentlichen Stärken von Argon2 ist seine Anpassbarkeit. Der Algorithmus erlaubt es Entwicklern, drei Hauptparameter zu konfigurieren: Speicherverbrauch, Rechenaufwand und Parallelität. Der Speicherverbrauch definiert, wie viel Speicher während der Hashing-Operation verwendet wird, und sorgt dafür, dass Angriffe mit parallelisierter Hardware teuer werden. Der Rechenaufwand gibt die Anzahl der Iterationen an, die der Algorithmus durchläuft, während die Parallelität die Anzahl der Threads bestimmt, die gleichzeitig arbeiten. Durch Anpassen dieser Parameter lässt sich der Algorithmus auf die spezifischen Anforderungen einer Anwendung oder die verfügbare Hardware abstimmen.

Ein weiterer Vorteil von Argon2 ist seine Widerstandsfähigkeit gegen moderne Angriffe. Durch die Kombination aus speicher- und rechenintensiven Prozessen wird es für Angreifer schwierig, Passwörter mit Brute-Force-Methoden oder durch den Einsatz spezialisierter Hardware zu knacken. Dadurch eignet sich Argon2 ideal für den Einsatz in sicherheitskritischen Anwendungen.

In der Praxis kommt Argon2 in vielen neueren Kryptografie- und Passwortmanagement-Bibliotheken zum Einsatz. Entwickler können den Algorithmus in verschiedenen Programmiersprachen wie Java, Python oder C implementieren.

Argon2 lässt sich daher als einer der sichersten Algorithmen für Passwort-Hashing einstufen. Besonders die Variante Argon2id empfiehlt sich als Standard für neue Projekte, da sie sowohl Schutz vor Seitenkanalangriffen als auch hohe Angriffsresistenz bietet.