Dieser Artikel beschreibt eine Implementierung von Zwei-Faktor-Authentifizierung mit PHP auf der Basis des zeitbasierten Einmalpasswort-Algorithmus (time-based one-time password, TOTP). Diese Methode ist im RFC 6238 standardisiert, wird hier beschrieben und ist als zusätzliche Sicherheitsmaßnahme für Logins weit verbreitet. Zur Nutzung wird ein zweiter Faktor benötigt, in diesem Fall eine Smartphone-App, die einen Code erzeugt, der beim Login zusätzlich zum Passwort eingegeben werden muss. Dieser Code ist einmalig in dem Sinne, dass er nur während eines Zeitfensters von (üblicherweise) 30 Sekunden gültig ist. Es gibt verschiedene Apps, die man dafür nutzen kann. Am besten bekannt dürfte der Google Authenticator sein. Eine Liste möglicher Apps ist am Ende dieses Artikels zu finden, es existieren aber noch viele mehr.
Zur Implementierung habe ich die quelloffene PHP-Bibliothek GoogleAuthenticator (GitHub) genutzt, die trotz ihres Namens mit jeder App zusammenarbeitet, die den Standard umsetzt. Aus Sicherheitsgründen habe ich auch die Bibliothek PHP QR Code verwendet, die zusätzlich die GD-Bibliothek benötigt.
Initialisierung
Um die Authentifizierung zu initialisieren, muss zuerst mit jedem Benutzer eine geheime Information vereinbart und zusammen mit den anderen Benutzerdaten auf dem Server gespeichert werden. Sie besteht aus einer Zeichenkette von Großbuchstaben und Ziffern beliebiger Länge und wird als Ausgangswert für die Berechnung des Login-Codes verwendet.
Um eine solche geheime Information zu erzeugen kann der folgende Code verwendet werden:
$auth = new SonataGoogleAuthenticatorGoogleAuthenticator();
$secret = $auth->generateSecret();
Man kann aber auch selbst eine geheime Information für einen Benutzer erzeugen: eine Zeichenkette, die aus mindestens 16 zufällig erzeugten Großbuchstaben von A bis Z und Ziffern von 2 bis einschließlich 7 besteht. Dies wird Base32-Codierung genannt und ist im RFC 4684 und in der Wikipedia beschrieben.
Mit der zusätzlichen Information eines Herausgebers (z.B. dem Namen einer Website oder einer Firma) und einem Benutzernamen wird eine URL erzeugt, um die geheime Information zur Authentifizierungs-App zu übertragen. Herausgeber und Benutzername werden nicht zur Erzeugung des Login-Codes benutzt, sondern nur als Zusatzinformation zur Anzeige in der App und um den Account zuordnen zu können. Die URL kann auf die folgende Weise erzeugt werden:
$secret = 'XVQ2UIGO75XRUKJO';
$issuer = "d3v.one";
$username = "Alice";
$url = 'otpauth://totp/'.$issuer.':'.$username.'?secret='.$secret.'&issuer='.$issuer;
Aus dieser URL kann ein QR-Code erzeugt werden, der von der Authentifizierungs-App gescannt werden kann. In diesem Beispiel wird die geheime Information “XVQ2UIGO75XRUKJO”, der Herausgeber “d3v.one” und der Benutzername “Alice” genutzt, um den folgenden QR-Code zu erzeugen:
In der Programmbibliothek GoogleAuthenticator wird ein externer Internetdienst genutzt, um das Bild des QR-Codes zu erzeugen. Ich halte dies für eine Sicherheitslücke, da die geheime Information an einen externen Server übermittelt werden muss, der nicht unter der eigenen Kontrolle steht.
Stattdessen habe ich die Bibliothek PHP QR Code mit dem folgenden PHP-Programmcode genutzt, um das Bild für den QR-Code zu erzeugen:
ob_start();
QRCode::png($url, null, QR_ECLEVEL_M, 5, 0, false);
$png = base64_encode(ob_get_contents());
ob_end_clean();
echo '<img src="data:image/png;base64,'.$png.'">';
Authentifizierung
Um den Beispielcode zu testen, kann man eine Authentifizierungs-App installieren und den QR-Code scannen. Ab diesem Zeitpunkt sollte nach jeweils 30 Sekunden ein neuer Login-Code in der App erzeugt werden, gültig jeweils für die nächsten 30 Sekunden.
Der jeweils gültige Login-Code kann zu jedem Zeitpunkt mit dem folgenden PHP-Code
$auth = new SonataGoogleAuthenticatorGoogleAuthenticator();
$code = $auth->getCode($secret);
erzeugt und mit der Benutzereingabe zur zusätzlichen Bestätigung des Logins verglichen werden. Als Livebeispiel wird der jeweilige gültige Login-Code, der auf den Beispieldaten basiert, hier angezeigt und etwa alle fünf Sekunden aktualisiert. Dies sollte mehr oder weniger synchron zur Anzeige in der App sein.
Anmerkungen
Die geteilte geheime Information muss zu jedem Zeitpunkt geheim gehalten werden. Durch Kenntnis dieser Information könnte ein Angreifer für jeden Zeitpunkt gültige Codes erzeugen.
Ein weiteres Problem könnte dadurch auftreten, dass die Uhrzeit auf dem Server nicht synchron zur Uhrzeit auf dem Smartphone ist. Da aber heutzutage bei jedem vernetzten Gerät die Uhrzeit automatisch synchronisiert wird, sollte dies eigentlich kein allgemeines Problem mehr sein.
Als Implementierungsdetail vergleicht man üblicherweise den übermittelten Code nicht nur mit dem derzeit gültigen Code, sondern auch mit dem vorherigen. Die Eingabe und Übertragung des Codes benötigen ja eine gewisse Zeit, in der er möglicherweise schon ungültig geworden sein kann.
Bei der Frage nach der besten Authentifizierungs-App sollte man auch berücksichtigen, wie schwierig es ist, die Accountdaten mit der geheimen Information auf ein neues Smartphone oder ein zusätzliches Gerät zu übertragen, idealerweise auf eine sichere Art. Noch wichtiger ist aber wohl die Frage, ob die App eine Sicherung der Daten erlaubt.
Alles in allem ist der hier beschriebene TOTP-Algorithmus jedenfalls eine schnelle und einfache Möglichkeit, einen Login-Vorgang zusätzlich abzusichern.
Authentifizierungs-Apps
Google Authenticator (Android, iOS)
Microsoft Authenticator (Android, iOS)
Twilio Authy (Android, iOS, Account mit Angabe von Telefonnummer und E-Mail erforderlich)
Aegis Authenticator (Android)
FreeOTP Authenticator (Android, iOS)
Sophos Authenticator (Android, iOS)
Authenticator Pro (Android)