PHP-Entwickler in Bochum

PHP-Entwicklung für Unternehmen in Bochum mit Schwerpunkt Anmeldung: Passwort-Hashing mit password_hash, sichere Sitzungscookies, Begrenzung von Fehlversuchen und Zwei-Faktor-Anmeldung nach RFC 6238. Ein Einzelentwickler, remote und schriftlich.

PHP-Entwickler

Mein Platz in der WordPress-Community

  • Mein wordpress.org-Konto besteht seit 2006.
  • Ich habe zum WordPress-Core in den Versionen 7.0 und 7.1 beigetragen.
  • Ich betreue 30 Plugins auf wordpress.org.
  • Ich organisiere WordCamps und trete dort als Speaker auf.
  • Ich war bei jedem WordCamp Europe von 2017 bis 2026 dabei.

Mein WordPress.org-Profil

#Anmeldung in PHP: worum es auf dieser Seite geht

Ich bin Mariusz Szatkowski, schreibe PHP, seit 2006 auch für WordPress, und arbeite als einzelner Entwickler von Gdynia aus. Für Unternehmen in Bochum übernehme ich eigene PHP-Anwendungen, Kundenportale, Laravel- und Symfony-Backends sowie Schnittstellen. Diese Seite hat einen Schwerpunkt: die Anmeldung. Wie Passwörter gespeichert werden, wie die Sitzung danach geschützt ist, was bei falschen Passwörtern passiert und wie ein zweiter Faktor dazukommt.

Der Grund für diese Auswahl ist praktisch. Bei übernommenen Anwendungen ist die Anmeldung oft der älteste Teil des Codes, geschrieben, bevor PHP eigene Funktionen dafür hatte, und seitdem nie wieder angefasst. Hier liegen die Fehler, die bei einer Datenpanne den größten Schaden anrichten: Passwörter als MD5-Hash, Sitzungs-IDs in der URL, eine Fehlermeldung, die verrät, ob ein Konto existiert.

Ich arbeite ausschließlich remote und schriftlich, ohne Termine und ohne Telefonrunden. Reine WordPress-Projekte haben eine eigene Seite: WordPress-Entwickler Bochum.

#Passwörter speichern mit password_hash

PHP hat für Passwörter drei Funktionen, die zusammengehören: password_hash(), password_verify() und password_needs_rehash(). Laut php.net erzeugt password_hash() einen Hash mit einem starken Einweg-Algorithmus. Algorithmus, Cost und Salt stehen im Ergebnis selbst, deshalb braucht password_verify() keine zusätzliche Spalte für das Salt.

Einige Details aus der Dokumentation, die ich in alten Anwendungen regelmäßig falsch umgesetzt sehe:

  • PASSWORD_DEFAULT nutzt bcrypt. Die Konstante soll sich mit der Zeit ändern, wenn PHP stärkere Algorithmen aufnimmt. php.net empfiehlt deshalb eine Datenbankspalte, die über 60 Bytes hinaus wachsen kann, 255 Bytes nennt das Handbuch als gute Wahl. Eine Spalte CHAR(60) ist eine Falle für das nächste Upgrade.
  • Der Cost von bcrypt ist seit PHP 8.4 standardmäßig 12, vorher war er 10. Das Handbuch nennt 12 eine gute Grundlage, die an die Hardware angepasst werden sollte.
  • Ein eigenes Salt ist überholt. Seit PHP 8.0 wird ein explizit übergebenes Salt ignoriert. Code, der noch ein Salt aus uniqid() baut, ist ein Hinweis auf eine sehr alte Anmeldung.
  • bcrypt kürzt das Passwort auf 72 Bytes. Bei Umlauten und anderen Mehrbyte-Zeichen ist das früher erreicht, als die Zeichenzahl vermuten lässt.
  • Argon2id ist nur verfügbar, wenn PHP mit Argon2-Unterstützung gebaut wurde. Ob das auf Ihrem Server der Fall ist, ist eine Frage des Builds, nicht des Codes.

Das Muster für die Anmeldung sieht so aus. Nach einer erfolgreichen Prüfung wird der Hash neu berechnet, wenn sich Algorithmus oder Cost geändert haben:

<?php
declare(strict_types=1);

function login(PDO $db, array $user, string $password): bool
{
    if (!password_verify($password, $user['password_hash'])) {
        return false;
    }

    if (password_needs_rehash($user['password_hash'], PASSWORD_DEFAULT)) {
        $stmt = $db->prepare('UPDATE users SET password_hash = ? WHERE id = ?');
        $stmt->execute([password_hash($password, PASSWORD_DEFAULT), $user['id']]);
    }

    session_regenerate_id(true);
    $_SESSION['user_id'] = $user['id'];

    return true;
}

password_needs_rehash() prüft laut php.net, ob der gespeicherte Hash zum angegebenen Algorithmus und zu den Optionen passt. So wandern Konten beim nächsten Login still auf die neuen Werte, ohne dass jemand sein Passwort ändern muss.

#Was die OWASP zu Algorithmen sagt

Das Password Storage Cheat Sheet der OWASP nennt eine klare Reihenfolge. Zuerst Argon2id mit mindestens 19 MiB Speicher, 2 Iterationen und Parallelität 1. Für Altsysteme mit bcrypt einen Work Factor von mindestens 10 und eine Passwortgrenze von 72 Bytes. PBKDF2 nur, wenn FIPS-140 verlangt ist, dann mit mindestens 600.000 Iterationen und HMAC-SHA-256.

Bleibt eine Anwendung bei bcrypt, setze ich eine sichtbare Höchstlänge mit einer verständlichen Meldung. Das Authentication Cheat Sheet sagt ausdrücklich, dass Passwörter nicht still gekürzt werden sollen.

#Alte Hashes ablösen

Finde ich in einer Anwendung MD5 oder SHA-1, ist die Umstellung ein eigenes Arbeitspaket. Die OWASP beschreibt den Weg: Beim nächsten Login wird das eingegebene Passwort mit dem neuen Verfahren gehasht. Für Konten, die sich lange nicht anmelden, gibt es zwei Möglichkeiten. Entweder werden ihre Hashes nach einer Frist gelöscht und die Nutzer setzen ihr Passwort neu. Oder der alte Hash wird sofort als Eingabe für ein starkes Verfahren genutzt, also etwa bcrypt über dem MD5-Wert, und beim nächsten Login durch einen direkten Hash ersetzt. Die OWASP weist darauf hin, dass diese Schichtung die Hashes leichter knackbar machen kann. Welcher Weg passt, entscheiden Sie, ich schreibe die Folgen beider Varianten auf.

#Welche Passwortregeln heute gelten

Viele Anwendungen erzwingen noch Großbuchstaben, Ziffern, Sonderzeichen und einen Wechsel alle drei Monate. Das Authentication Cheat Sheet der OWASP sieht das unter Verweis auf NIST SP 800-63B anders:

  • Ohne Mehrfaktor-Anmeldung gelten Passwörter unter 15 Zeichen als schwach, mit Mehrfaktor-Anmeldung solche unter 8 Zeichen.
  • Die Höchstlänge sollte mindestens 64 Zeichen betragen, damit Passphrasen möglich sind.
  • Alle Zeichen sind erlaubt, auch Unicode und Leerzeichen. Vorschriften zur Zusammensetzung soll es nicht geben.
  • Periodische Pflichtwechsel sind zu vermeiden. Gewechselt wird, wenn ein Passwort abgeflossen ist.
  • Häufige und bereits geleakte Passwörter werden gesperrt, zum Beispiel über den Dienst Pwned Passwords oder eine selbst gehostete Kopie seiner Datenbank.

Wer diese Regeln umsetzt, ändert meist nur Validierung und Formulartexte. Der Aufwand ist klein, der Effekt für die Nutzer sofort spürbar.

#Sitzungen nach der Anmeldung absichern

Ein sauberer Hash nützt wenig, wenn die Sitzung danach leicht zu stehlen ist. PHP steuert das Sitzungscookie über ini-Direktiven. Die folgenden Werte sind laut php.net dokumentiert, ihre Standardwerte sind teilweise unsicher:

DirektiveStandard laut php.netWas ich einstelle und warum
session.use_strict_mode01. Das Handbuch nennt den strikten Modus für die Sitzungssicherheit zwingend, er weist nicht initialisierte Sitzungs-IDs ab und schützt so vor Session Fixation.
session.use_only_cookies11 lassen. Verhindert Angriffe über Sitzungs-IDs in der URL; das Abschalten ist seit PHP 8.4 veraltet.
session.cookie_secure01. Das Cookie geht nur über HTTPS.
session.cookie_httponly01. JavaScript kann das Cookie nicht lesen, was Diebstahl über XSS erschwert.
session.cookie_samesiteleerLax oder Strict. Bei beiden wird das Cookie bei domainübergreifenden POST-Anfragen nicht mitgeschickt; Strict schickt es auch bei domainübergreifenden GET-Anfragen nicht.

Als ini-Block, etwa in der Pool-Konfiguration von PHP-FPM oder in einer php.ini, sieht das so aus:

session.use_strict_mode = 1
session.use_only_cookies = 1
session.cookie_secure = 1
session.cookie_httponly = 1
session.cookie_samesite = "Lax"

Ein Hinweis aus dem Handbuch, der bei eigenen Session-Handlern oft übersehen wird: Implementiert ein über session_set_save_handler() registrierter Handler keine Validierung der Sitzungs-ID, ist der strikte Modus wirkungslos, egal was in der Konfiguration steht.

#Die Sitzungs-ID nach dem Login erneuern

Das Kapitel zur Sitzungsverwaltung auf php.net ist eindeutig: Steigen die Rechte eines Nutzers, etwa nach der Anmeldung, muss die Sitzungs-ID neu erzeugt werden. session_regenerate_id() wird aufgerufen, bevor die Anmeldedaten in $_SESSION geschrieben werden, damit nur die neue Sitzung als angemeldet gilt. Für sicherheitsrelevante Inhalte empfiehlt das Handbuch zusätzlich eine regelmäßige Erneuerung, als Beispiel nennt es alle 15 Minuten.

In übernommenen Anwendungen fehlt dieser Aufruf häufig. Er steht im Beispiel oben an der richtigen Stelle.

#Fehlversuche begrenzen, ohne Kunden auszusperren

Gegen das Durchprobieren von Passwörtern hilft eine Grenze für Fehlversuche. Die OWASP beschreibt im Authentication Cheat Sheet mehrere Punkte, die ich bei jeder Anmeldung prüfe:

  • Den Zähler am Konto führen, nicht an der IP-Adresse. Sonst probiert ein Angreifer einfach von vielen Adressen aus.
  • Die Wartezeit wachsen lassen. Statt einer festen Sperre von zehn Minuten nennt die OWASP eine exponentielle Sperre, die sehr kurz beginnt und sich nach jedem Fehlversuch verdoppelt.
  • Die Sperre nicht zur Waffe machen. Wer fremde Konten absichtlich sperren kann, legt Ihren Kundenbereich lahm. Eine Möglichkeit ist, die Anmeldung über das Zurücksetzen des Passworts trotz Sperre zuzulassen.
  • Allgemeine Fehlermeldungen. Bei Login, Passwort-Reset und Wiederherstellung soll die Antwort gleich lauten, ob das Konto existiert oder nicht. Das gilt auch für den HTTP-Statuscode: Liefert die Anwendung bei bekannten und unbekannten Konten unterschiedliche Codes, verrät sie trotz gleichem Text, welche Konten es gibt.
  • Sperren protokollieren und auswerten.

Die großen Frameworks bringen dafür Werkzeuge mit. In Symfony aktiviert die Einstellung login_throttling den Schutz, sie setzt das Paket symfony/rate-limiter voraus. Laut symfony.com begrenzt Symfony standardmäßig auf 5 Fehlversuche je Kombination aus IP-Adresse und Benutzername und auf das Fünffache je IP-Adresse. Die zweite Grenze soll verhindern, dass jemand mit vielen Benutzernamen die erste umgeht, ohne normale Nutzer in großen Büronetzen zu stören.

# config/packages/security.yaml
security:
    firewalls:
        main:
            login_throttling:
                max_attempts: 5
                interval: '15 minutes'

In Laravel stellt die Fassade RateLimiter laut laravel.com Methoden wie tooManyAttempts(), increment(), availableIn() und clear() bereit. Ein Schlüssel je Konto setzt die Empfehlung der OWASP um:

$key = 'login:' . mb_strtolower($email);

if (RateLimiter::tooManyAttempts($key, 5)) {
    $seconds = RateLimiter::availableIn($key);
    // allgemeine Meldung mit Wartezeit zurückgeben
}

RateLimiter::increment($key);
// nach erfolgreicher Anmeldung: RateLimiter::clear($key);

#Zwei-Faktor-Anmeldung mit TOTP

Die Codes aus Authenticator-Apps folgen fast immer RFC 6238, dem Verfahren für zeitbasierte Einmalpasswörter (TOTP) aus dem Mai 2011. Der RFC ist als informativ eingestuft und baut auf HOTP mit HMAC-SHA-1 auf. Für die Umsetzung in PHP zählen vor allem diese Vorgaben:

  • Zeitschritt. Der RFC empfiehlt 30 Sekunden als Standard, als Abwägung zwischen Sicherheit und Bedienbarkeit.
  • Toleranz. Wegen der Übertragungszeit darf der Server auch den vorigen Zeitschritt prüfen. Der RFC empfiehlt, höchstens einen Zeitschritt als Netzwerkverzögerung zuzulassen. Ein größeres Fenster vergrößert die Angriffsfläche.
  • Kein zweites Mal. Nach einer erfolgreichen Prüfung darf derselbe Code kein zweites Mal akzeptiert werden. Die Anwendung muss sich also den zuletzt genutzten Zeitschritt je Konto merken.
  • Ein Geheimnis je Person. Jede Person braucht einen eigenen Schlüssel, zufällig erzeugt und vor unbefugtem Zugriff geschützt. In PHP erzeuge ich ihn mit random_bytes(), das laut php.net kryptografisch sichere Zufallsbytes liefert.
  • Vergleich in konstanter Zeit. Den eingegebenen und den berechneten Code vergleiche ich mit hash_equals(), das laut php.net gegen Timing-Angriffe schützt.

Zur Zwei-Faktor-Anmeldung gehören auch die Wege drumherum: Wiederherstellungscodes für ein verlorenes Telefon, ein Ablauf zum Zurücksetzen, der nicht einfacher ist als die Anmeldung selbst, und eine erneute Abfrage des Passworts vor heiklen Änderungen. Die OWASP verlangt diese erneute Anmeldung ausdrücklich, bevor E-Mail-Adresse oder Passwort geändert werden oder eine Bestellung an eine neue Adresse geht.

#Was das Datenschutzrecht in NRW dazu sagt

Art. 32 Abs. 1 der DSGVO verpflichtet Verantwortliche und Auftragsverarbeiter, geeignete technische und organisatorische Maßnahmen zu treffen, um ein dem Risiko angemessenes Schutzniveau zu gewährleisten. Der Stand der Technik ist dabei ausdrücklich zu berücksichtigen. Genannt werden unter anderem Pseudonymisierung und Verschlüsselung sowie ein Verfahren zur regelmäßigen Überprüfung der Wirksamkeit dieser Maßnahmen.

Für Unternehmen in Bochum ist die Landesbeauftragte für Datenschutz und Informationsfreiheit Nordrhein-Westfalen zuständig. Auf ihrer Seite zu technischen und organisatorischen Maßnahmen nennt die LDI NRW als typische technische Maßnahmen unter anderem den Einsatz sicherer Authentifizierungsverfahren, ein Rollen- und Berechtigungskonzept und das Einspielen von Sicherheitsupdates. Die DSGVO sieht nach Darstellung der LDI NRW eine Dokumentation aller Maßnahmen vor, als Nachweis, dass angemessen geschützt wurde.

Für die Anmeldung heißt das praktisch: Was ich umsetze, schreibe ich so auf, dass es in Ihre Dokumentation der Maßnahmen übernommen werden kann. Algorithmus und Parameter des Hashings, Sitzungseinstellungen, Grenzen für Fehlversuche, Zwei-Faktor-Pflicht für welche Rollen. Eine Rechtsberatung ist das nicht, die Bewertung des Risikos liegt bei Ihnen und Ihrer Datenschutzberatung.

#Bochum und die IT-Sicherheit im Herbst 2026

Die städtische Wirtschaftsförderung Bochum Wirtschaftsentwicklung stellt IT-Sicherheit als einen ihrer Schwerpunkte heraus. Zwei Ereignisse aus diesem Herbst zeigen, warum das Thema in der Stadt präsent ist. Vom 12. bis 16. Oktober 2026 kamen laut Bochum Wirtschaft mehr als 400 junge IT-Sicherheitstalente aus 42 Nationen zur European Cybersecurity Challenge im RuhrCongress zusammen. Am 5. November 2026 wurde in Bochum der elfte Deutsche IT-Sicherheitspreis der Horst Görtz Stiftung verliehen. Aus 48 Einreichungen kamen acht Teams ins Finale, den ersten Platz belegte das Bochumer Scaleup Edgeless Systems (Bochum Wirtschaft).

RuhrCongress am Stadionring in Bochum

Foto: Frank Vincentz, CC BY-SA 3.0, Wikimedia Commons.

Für ein Unternehmen mit einem Kundenportal ändert die Nachbarschaft zur Forschung nichts an den eigenen Pflichten. Sie verschiebt aber die Erwartung: Wer in Bochum IT-Fachleute einstellt oder mit ihnen zusammenarbeitet, bekommt eine Anmeldung mit MD5-Hashes schneller zurückgemeldet als anderswo.

#Checkliste für Ihre Anmeldung

Diese Fragen können Sie oder Ihr Entwicklungsteam selbst beantworten, auch ohne mich:

  • Wird jedes Passwort mit password_hash() gespeichert, und ist die Spalte breiter als 60 Bytes?
  • Ruft die Anmeldung password_needs_rehash() auf?
  • Gibt es noch Konten mit MD5- oder SHA-1-Hashes, und wie viele davon haben sich seit Jahren nicht angemeldet?
  • Sind session.use_strict_mode, session.cookie_secure und session.cookie_httponly aktiv, und ist session.cookie_samesite gesetzt?
  • Wird session_regenerate_id() vor dem Schreiben der Anmeldedaten aufgerufen?
  • Lauten Text und HTTP-Statuscode bei unbekanntem Konto und falschem Passwort gleich?
  • Werden Fehlversuche je Konto gezählt, wächst die Wartezeit, und werden Sperren protokolliert?
  • Verlangt die Anwendung eine Mindestlänge, erlaubt sie lange Passphrasen und prüft sie gegen geleakte Passwörter?
  • Gibt es eine Zwei-Faktor-Anmeldung mindestens für Rollen mit Verwaltungsrechten, und wird ein TOTP-Code nur einmal akzeptiert?
  • Ist all das in Ihrer Dokumentation der technischen und organisatorischen Maßnahmen beschrieben?

#Wie ein Auftrag mit mir abläuft

  1. Schriftliche Anfrage. Sie beschreiben die Anwendung und die Frage, zum Beispiel eine übernommene Anmeldung mit alten Hashes oder ein neues Portal mit Zwei-Faktor-Pflicht.
  2. Rückfragen per E-Mail. Ich frage nach, was ich für eine Einschätzung brauche, etwa PHP-Version, Framework und Datenbank.
  3. Lesen des Codes. Mit Zugang zum Repository und zu einer Staging-Kopie prüfe ich die Anmeldung gegen die Checkliste oben und halte jeden Befund schriftlich fest.
  4. Umfang und Annahmen. Sie bekommen eine schriftliche Liste, was ich ändere, was nicht und welche Entscheidungen bei Ihnen liegen, etwa der Umgang mit inaktiven Konten.
  5. Umsetzung auf Staging. Änderungen laufen zuerst auf der Staging-Kopie, mit Tests für Login, Rehash, Sperre und Zwei-Faktor-Anmeldung.
  6. Abnahme und Übergabe. Nach Ihrer Freigabe geht die Änderung live. Sie erhalten eine Beschreibung der Einstellungen für Ihre Dokumentation.

Gestaltung, Layout und Wireframes für neue Formulare kommen von Ihnen. Das Entwerfen bleibt bei Ihnen, die technische Umsetzung übernehme ich.

#Was ich von Ihnen brauche

  • Lesezugriff auf das Repository oder ein Archiv des Codes.
  • Eine Staging-Umgebung oder die Möglichkeit, eine einzurichten.
  • Die PHP-Version und die Ausgabe von php -i oder phpinfo() vom Server, damit ich Sitzungswerte und Argon2-Unterstützung sehe.
  • Eine Ansprechperson, die über Datenschutzfragen entscheidet.
  • Falls vorhanden: Ihre bisherige Dokumentation der technischen und organisatorischen Maßnahmen.

#Häufige Fragen

#Reicht es, alte MD5-Hashes beim nächsten Login zu ersetzen?

Nur für Konten, die sich wieder anmelden. Alle anderen behalten den alten Hash. Die OWASP beschreibt zwei Auswege: inaktive Konten nach einer Frist zurücksetzen oder den alten Hash sofort in einen neuen Hash einpacken und beim nächsten Login durch einen direkten Hash ersetzen.

#Soll ich Argon2id statt bcrypt verwenden?

Wenn Ihr PHP mit Argon2-Unterstützung gebaut ist, ja, mit mindestens den Werten der OWASP (19 MiB Speicher, 2 Iterationen, Parallelität 1). Ob das der Fall ist, prüfe ich auf Ihrem Server. Bleibt es bei bcrypt, begrenze ich die Passwortlänge sichtbar, weil bcrypt nach 72 Bytes abschneidet.

#Müssen Mitarbeitende ihr Passwort alle 90 Tage ändern?

Nein. Die OWASP rät unter Verweis auf NIST von erzwungenen periodischen Wechseln ab. Ein Wechsel ist nötig, wenn ein Passwort abgeflossen ist. Sinnvoller sind eine Mindestlänge, eine Sperrliste bekannter Passwörter und eine Zwei-Faktor-Anmeldung.

#Sperren Sie ein Konto nach drei falschen Passwörtern?

Eine starre Sperre lädt dazu ein, fremde Konten absichtlich zu sperren. Ich zähle Fehlversuche je Konto, verlängere die Wartezeit schrittweise und lasse den Weg über das Zurücksetzen des Passworts offen. Jede Sperre wird protokolliert.

#Gilt das auch für eine WordPress-Seite?

WordPress bringt eine eigene Anmeldung mit, die ich nicht nachbaue. Diese Seite betrifft eigene PHP-Anwendungen, Kundenportale und Schnittstellen. Für WordPress gibt es einen eigenen Beitrag zum Absichern der Login-Seite.

#Welche Angaben genügen für eine erste Antwort?

Eine kurze Beschreibung der Anwendung, die PHP-Version, das Framework und die Frage, die Sie klären wollen. Zugänge oder Code sind für die erste Antwort nicht nötig.

#Schreiben Sie mir

Wenn Ihre Anwendung Passwörter speichert, die Sie nicht selbst geprüft haben, schicken Sie mir über die Kontaktseite ein paar Zeilen: die Adresse der Anwendung oder eine kurze Beschreibung, Ihr Ziel, eine Frist, falls es eine gibt, und den Stack mit PHP-Version und Framework. Ich antworte schriftlich mit einer ersten Einschätzung und den Fragen, die danach offen sind. Diese erste Einschätzung ist kostenlos.

Einen Überblick über meine übrigen PHP-Leistungen finden Sie unter PHP-Entwickler. Wie ich PHP-Projekte in der Nachbarschaft angehe, zeigen die Seiten für Köln und Dortmund. Für die Wartung einer bestehenden Seite in Bochum gibt es die WordPress-Wartung in Bochum.

Brauchen Sie die Leistung: PHP-Entwickler in Bochum?

Beschreiben Sie Ihr Projekt per E-Mail. Antwort innerhalb von 48 Stunden.

Projekt beschreiben

Weitere WordPress-Dienste und Wissensbasis entdecken