Aktuelle Lage: PHP2Shell. Zwei kritische PHP-Lücken (CVSS 9.2) erlauben Schadcode ohne Anmeldung. Patches seit 21.10.2026.Was jetzt zu tun ist →

Passwörter in PHP sicher speichern: password_hash richtig einsetzen

Wie Entwickler Passwörter in PHP mit password_hash und password_verify speichern, das passende Verfahren wählen und alte MD5- oder SHA-1-Hashes ohne Passwort-Reset für alle Nutzer ablösen.

Stand 5 Min. LesezeitSichere Entwicklung

Kurz gesagt: Passwörter speichern Sie in PHP mit password_hash() und prüfen sie mit password_verify(). Der Standard PASSWORD_DEFAULT nutzt derzeit bcrypt, seit PHP 8.4 mit Kostenfaktor 12; wo PHP mit Argon2 kompiliert ist, ist PASSWORD_ARGON2ID eine gute Alternative. Alte MD5- oder SHA-1-Hashes stellen Sie um, indem Sie sie sofort in password_hash einbetten und beim nächsten Login durch einen direkten Hash ersetzen.

Was ein Datenbank-Abfluss mit Ihren Hashes zu tun hat

Nach einem Einbruch in einen Webserver ist die Datenbank eines der ersten Ziele. Wer, wie bei PHP2Shell möglich, Code auf dem Server ausführen kann, liest auch die Zugangsdaten der Anwendung und damit die Tabelle mit den Benutzerkonten. Ob die Passwörter darin nach Stunden oder nach Jahren lesbar werden, entscheidet sich nicht am Tag des Angriffs, sondern bei der Programmierung.

Erstaunlich viele Anwendungen speichern Passwörter noch mit Verfahren, die für diesen Zweck nie gedacht waren: MD5, SHA-1, ein einfaches SHA-256 oder sogar umkehrbar verschlüsselt. Diese Verfahren sind schnell, und genau das ist das Problem. Angreifer können damit sehr viele Kandidaten pro Sekunde durchprobieren.

PHP bringt seit Langem eine Lösung mit, die langsam rechnet, für jedes Passwort automatisch ein Salt erzeugt und Verfahren und Parameter im Hash selbst speichert. Sie müssen sie nur konsequent nutzen.

password_hash in PHP: bcrypt oder Argon2id?

password_hash() kennt mehrere Verfahren. Die Auswahl laut PHP-Handbuch:

Konstante Verfahren Hinweis
PASSWORD_DEFAULT derzeit bcrypt Standard, kann sich in künftigen Versionen ändern
PASSWORD_BCRYPT bcrypt Kostenfaktor seit PHP 8.4 standardmäßig 12, vorher 10; nur die ersten 72 Bytes zählen
PASSWORD_ARGON2I Argon2i seit PHP 7.2, nur wenn PHP mit Argon2-Unterstützung gebaut ist
PASSWORD_ARGON2ID Argon2id seit PHP 7.3, gleiche Voraussetzung

OWASP empfiehlt Argon2id als erste Wahl, mit mindestens 19 MiB Speicher, zwei Durchläufen und einem Thread, und bcrypt vor allem für bestehende Systeme. Für die meisten PHP-Anwendungen ist der Unterschied in der Praxis kleiner als der Unterschied zwischen irgendeinem dieser Verfahren und MD5. Wichtiger ist, dass Sie überhaupt eines davon nutzen und die Parameter regelmäßig anpassen.

Ob Argon2id verfügbar ist, zeigt ein kurzer Test:

<?php
var_dump(defined('PASSWORD_ARGON2ID'));
var_dump(password_algos());

Speichern und prüfen: ein vollständiges Muster

Das folgende Beispiel zeigt Registrierung und Anmeldung mit automatischer Aktualisierung veralteter Hashes. Die Längenprüfung schützt vor der 72-Byte-Grenze von bcrypt:

<?php
declare(strict_types=1);

const PASSWORT_ALGO = PASSWORD_DEFAULT;

function passwortSpeichern(string $passwort): string
{
    if (strlen($passwort) > 72) {
        throw new InvalidArgumentException('Passwort zu lang für das gewählte Verfahren.');
    }
    return password_hash($passwort, PASSWORT_ALGO);
}

function passwortPruefen(string $eingabe, string $gespeicherterHash, callable $hashAktualisieren): bool
{
    if (!password_verify($eingabe, $gespeicherterHash)) {
        return false;
    }
    if (password_needs_rehash($gespeicherterHash, PASSWORT_ALGO)) {
        $hashAktualisieren(password_hash($eingabe, PASSWORT_ALGO));
    }
    return true;
}

strlen() zählt Bytes, nicht Zeichen. Das ist hier gewollt, denn die bcrypt-Grenze bezieht sich auf Bytes. Die Spalte in der Datenbank sollte laut Handbuch mehr als 60 Zeichen fassen, 255 sind eine gute Wahl. Setzen Sie nie ein eigenes Salt: Die Option ist veraltet und wird seit PHP 8.0 ignoriert.

Mit dem Wechsel auf PHP 8.4 steigt der Standardkostenfaktor für bcrypt von 10 auf 12. Bestehende Hashes werden dank password_needs_rehash() beim nächsten Login automatisch angehoben. Auf stark ausgelasteten Systemen lohnt vor dem Umstieg ein kurzer Lasttest der Anmeldung.

Tipp

Messen Sie den Kostenfaktor auf Ihrem Produktivserver. Eine einzelne Prüfung sollte spürbar Rechenzeit kosten, aber deutlich unter einer Sekunde bleiben, damit Anmeldungen bei Last nicht zum Engpass werden.

Alte MD5- und SHA-1-Hashes ablösen

Die eigentliche Herausforderung ist meist der Bestand. Die Passwörter selbst kennen Sie nicht, also können Sie sie nicht einfach neu hashen. Warten, bis sich jeder Nutzer wieder anmeldet, lässt inaktive Konten ungeschützt. OWASP beschreibt deshalb ein zweistufiges Vorgehen:

  1. Sofort einbetten: Jeden vorhandenen MD5- oder SHA-1-Wert per Skript mit password_hash() umhüllen und mit einer Markierung speichern, etwa in einer Spalte hash_typ.
  2. Beim Login prüfen: Für markierte Konten die Eingabe zuerst mit dem alten Verfahren umrechnen und dann mit password_verify() gegen den umhüllten Wert prüfen.
  3. Direkt ersetzen: Nach erfolgreicher Anmeldung einen normalen Hash aus dem Klartext erzeugen und die Markierung entfernen.
  4. Restbestand bereinigen: Konten, die nach einer festgelegten Frist noch umhüllte Hashes haben, auf Passwort-Reset setzen.
<?php
// Stufe 1 (einmalig per Skript): $neu = password_hash($alterMd5Wert, PASSWORD_DEFAULT);
// Stufe 2 und 3 beim Login:
if ($konto['hash_typ'] === 'md5-umhuellt') {
    $ok = password_verify(md5($eingabe), $konto['passwort_hash']);
    if ($ok) {
        $speichere(password_hash($eingabe, PASSWORD_DEFAULT), 'direkt');
    }
}

Umhüllte Hashes sind besser als nackte MD5-Werte, aber schwächer als direkte Hashes. Sie sind eine Übergangslösung, kein Ziel. Nach einem Vorfall, bei dem die Datenbank betroffen sein könnte, ersetzt diese Umstellung nicht die Aufforderung an die Nutzer, ihre Passwörter zu ändern. Welche Spuren auf einen solchen Vorfall hinweisen, beschreibt Bin ich schon kompromittiert?.

Pepper, Verschlüsselung und andere Irrwege

Einige Ideen klingen nach mehr Sicherheit, bringen aber wenig oder schaden:

  • Eigene Hash-Konstruktionen wie mehrfaches SHA-256 mit selbst erzeugtem Salt sind schneller als bcrypt und deshalb schwächer.
  • Verschlüsselte Passwörter sind umkehrbar. Liegt der Schlüssel auf demselben Server, schützt die Verschlüsselung nach einem Einbruch kaum.
  • Ein Pepper, also ein geheimer Zusatzwert für alle Passwörter, kann eine zusätzliche Schicht sein, wenn er getrennt von der Datenbank aufbewahrt wird. Er ersetzt aber kein gutes Hash-Verfahren und erschwert spätere Wechsel.
  • Vorab-Hashing vor bcrypt, um die 72-Byte-Grenze zu umgehen, birgt laut OWASP eigene Risiken. Eine klare Längengrenze oder Argon2id ist einfacher.

Auch bei den Regeln für neue Passwörter lohnt ein Blick auf den Stand der Technik: Länge schützt mehr als komplizierte Vorgaben zu Sonderzeichen, und sehr häufige oder bereits in Datenlecks aufgetauchte Passwörter sollten abgelehnt werden. Erzwungene Wechsel in festen Abständen ohne Anlass führen erfahrungsgemäß eher zu vorhersehbaren Mustern als zu stärkeren Passwörtern.

Für Anmeldesysteme gehören Passwörter zudem nie allein: Zwei-Faktor-Verfahren für Admins und eine Bremse gegen massenhafte Anmeldeversuche ergänzen die Speicherung. Beides behandelt der Ratgeber Admin-Logins gegen Brute Force schützen. Für Reset-Links und Einmalcodes gelten andere Regeln, siehe Sichere Zufallszahlen und Tokens in PHP. Einen Überblick über weitere Funktionen gibt Eingebaute Sicherheitsfunktionen von PHP.

Wie hilft PHP SOS konkret?

  • Wir prüfen, wie Ihre Anwendung und Ihre Plugins Passwörter speichern, und finden veraltete Verfahren auch in Nebenfunktionen.
  • Wir planen die Umstellung des Bestands mit Umhüllung, Login-Migration und Frist für Restkonten.
  • Wir stimmen Kostenfaktor oder Argon2-Parameter auf Ihre Server ab.
  • Nach einem Vorfall unterstützen wir bei der Entscheidung über Passwort-Resets und deren technische Umsetzung.

Eine Prüfung eigener PHP-Software bieten wir als Sicherheitsaudit und Penetrationstest an.

Häufige Fragen

Welchen Algorithmus nutzt password_hash mit PASSWORD_DEFAULT?

Derzeit bcrypt. Das PHP-Handbuch weist darauf hin, dass sich der Standard ändern kann, wenn stärkere Verfahren hinzukommen. Deshalb sollte die Datenbankspalte mehr als 60 Zeichen fassen; das Handbuch nennt 255 als gute Wahl.

Sollte ich Passwörter verschlüsseln statt hashen?

Nein. Verschlüsselte Passwörter lassen sich mit dem Schlüssel wieder lesen, und dieser Schlüssel liegt meist auf demselben Server. Ein Passwort-Hash ist eine Einbahnstraße: Die Anwendung kann nur prüfen, ob eine Eingabe passt.

Wie hoch sollte der Kostenfaktor bei bcrypt sein?

Seit PHP 8.4 liegt der Standard bei 12, vorher bei 10. OWASP nennt 10 als Untergrenze und empfiehlt, so hoch zu gehen, wie Ihre Server es vertragen. Messen Sie auf der eigenen Hardware, wie lange eine Prüfung dauert.

Was bedeutet die 72-Byte-Grenze bei bcrypt?

bcrypt berücksichtigt nur die ersten 72 Bytes eines Passworts, alles danach wird abgeschnitten. Bei Umlauten zählt ein Zeichen oft zwei Bytes. Begrenzen Sie die Länge deshalb ausdrücklich oder nutzen Sie Argon2id, wenn Ihr PHP damit kompiliert ist.

Müssen Nutzer nach einem Einbruch ihre Passwörter ändern?

Wenn nicht ausgeschlossen werden kann, dass die Datenbank abgeflossen ist, sollten Sie das veranlassen. Gute Hashes verlangsamen das Knacken erheblich, schwache Passwörter bleiben aber angreifbar. Ob zusätzlich eine Meldepflicht besteht, klären Sie mit Ihrem Datenschutzbeauftragten.

Quellen

Sofort-Kontakt

Jede Stunde zählt. Schreiben Sie uns jetzt.

Schildern Sie kurz System, PHP-Version und Beobachtung. Wir melden uns, klären die Lage und arbeiten remote, bis der Vorfall abgeschlossen und dokumentiert ist.

Sofort-Hilfe