DeutschEnglish

Untermenü

 - - - By CrazyStat - - -
Letzte Änderung:
28.08.2017 - 10:19:54

Hinweis: Dieser Artikel von 2009 und ist veraltet. MD5 ist gebrochen. Verwenden Sie https, es ist heute sogar kostenlos möglich, z.B. durch letsencrypt.

Sicherer Login ohne https / SSL

Unzählige Webseiten und Webapplikationen sind durch einen Login vor unbefugtem Zugriff geschützt. Aber die wenigsten dieser Login-Daten werden über eine verschlüsselte https-Verbindung übertragen, sodass Passwörter im Klartext gesendet werden. Der Grund dafür ist einfach, dass die Einrichtung eines SSL-Zertifikats mit einem gewissen (regelmäßigen) Aufwand verbunden ist und außerdem ein „richtiges“ Zertifikat mit laufenden Kosten verbunden ist.
Hier möchte ich eine recht sichere Methode für die Übertragung des Passworts beim Login vorstellen, die ohne Zertifikat auskommt.
Natürlich wird hier nur das Passwort geschützt, keine weiteren übertragenen Daten wie bei https-Verbindungen, weshalb das Verfahren nicht als Ersatz für https anzusehen ist.

MD5-Hash

Der MD5-Hash eines Strings ist eine eindeutig* zugeordnete Zeichenkette von 32 Zeichen. Der Witz bei der Sache ist, dass man aus einem String leicht seinen MD5-Hash, aber aus dem MD5-Hash nicht den String berechnen kann**. Näheres zu MD5 hier und auf Wikipedia.

* Ja, es gibt Kollisionen (s. Wikipedia), aber die sind in der Praxis nicht relevant.
** Man kann nur häufig verwendete Werte in Datenbanken nachschlagen oder Brute Force anwenden siehe ***

Wenn man jetzt den MD5-Hash des Passworts statt des Passworts selbst überträgt, lässt sich also nur der Hash „mithören“, das Passwort selbst bleibt geheim.
Dazu muss das Passwort natürlich vor der Übertragung, also auf dem Clienten (im Browser) in MD5 umgerechnet werden.
Dazu verwendet man am besten JavaScript. Zwar hat JavaScript keine MD5-Funktion, allerdings kann man einfach diese Implementierung einbinden.
Unterstützt der Client kein JavaScript muss man das Passwort im Klartext übertragen und weist am besten per <noscript>-Warnung darauf hin.
Auf Server-Seite kann z.B. PHP von Haus aus md5(), sodass der Abgleich kein Problem ist.
Noch besser: Man speichert auf dem Server das Passwort gar nicht, sondern gleich den Hash-Wert, da dieser zum Abgleich ausreicht.

Wechselnde MD5-Hashs

Das Problem bei der Sache: Wenn auf dem Server nur noch der Hash abgeglichen wird, kann es dem Angreifer eigentlich egal sein, wie das Passwort aussieht. Er kann an den Server einfach den mitgeschnittenen Hash senden um Zugriff zu erhalten.
Diesem Problem kann man wie folgt begegnen:
Beim Generieren des Login-Formulars generiert der Server eine Zufallszahl. Diese wird in einer JavaScript-Variable an den Clienten übergeben und auf dem Server in der Session gespeichert.
Der Client überträgt nicht mehr den Hash des Passworts, sondern den MD5-Hash von Passwort+Zufallszahl.
Der Server hat beim Überprüfen die Zufallszahl in der Session und kann mit ihr auch den Hash von Passwort+Zufallszahl berechnen und diesen mit dem vom Clienten gesendeten vergleichen und die Zufallszahl dann aus der Session löschen.
Ein Angreifer kann nur noch den Hash von Passwort+Zufallszahl mitschneiden. Dieser Wert ist aber für ihn gar nicht mehr gültig, denn seine Session hat eine andere Zufallszahl.

MD5-Hash auf dem Server speichern

Wie bereits erwähnt sollte man auf dem Server in keinem Fall das Passwort selbst, sondern nur dessen Hash-Wert speichern. Somit weiß der Server gar nicht, was das Passwort ist. Gelangen die gespeicherten Hash-Werte in falsche Hände, können die Passwörter daraus nicht ermittelt werden.
Wenn wir jetzt aber den Hash von Passwort+Zufallszahl zum Server senden, müssen wir zum Vergleich auf dem Server auch das Passwort kennen, der Hash vom Passwort reicht nicht mehr aus.
Dem Problem kann man leicht begegnen, indem man statt md5(Passwort+Zufallszahl) einfach md5(md5(Passwort)+Zufallszahl) überträgt. Das kann der Client leicht berechnen, und der Server kann mittels md5(gespeicherterHash+Zufallszahl) einfach den Vergleichswert berechnen.

Gestohlene Hash-Werte

Die ganze Übung hat natürlich wieder einen Haken:
Wenn dem Angreifer die gespeicherten Hash-Werte bekannt sind (z.B. weil er die Datenbank lesen konnte), kann er natürlich auch genauso wie der Server md5(gespeicherterHash+Zufallszahl) berechnen und sich einloggen, ohne das Passwort zu kennen. Trotzdem wird er nie das Passwort erfahren. (Bei Verwendung von https hat der Angreifer natürlich auch Zugriff wenn er die Datenbank lesen konnte. Von daher bietet dieses Verfahren sogar in gewisser Weise mehr Schutz, lässt sich aber natürlich mit https verbinden.)
Wird dem Seitenbetreiber bekannt, dass die Hash-Werte in falsche Hände geraten sind, kann er leicht einlenken: Sobald sich die Nutzer das nächste mal einloggen, wird statt md5(Passwort) md5(Passwort+einFesterString) in die Datenbank gespeichert. So werden mit jedem Login die Hash-Werte ausgetauscht.
Der Client sendet jetzt immer (auch) md5(md5(Passwort+einFesterString)+Zufallszahl) an den Server, sodass dieser, wenn der Hash des Nutzers bereits aktualisiert wurde, diesen Wert mit dem aktualisierten Hash vergleichen kann.
So wird mit jedem Login der vom Angreifer für diesen Nutzer bekannte Hash-Werte ungültig, ohne dass Passwörter geändert werden mussten.
(Sollten erneut Hashs in falsche Hände geraten, kann man einfach den festen String ändern.)
Das Speichern von md5(Passwort+einFesterString) hat noch einen weiteren Vorteil, weshalb man diese Methode von vorneherein einsetzen sollte:
Die gängigen Datenbanken um MD5-Hashs nachzuschlagen kennen vor allem gängige kurze Strings. Der MD5-Hash für das Passwort „12345“ wird sich in jeder dieser Datenbanken finden***. Der MD5-Hash von „12345diesistirgendei#nfesterstring“ wird dagegen eher nicht in der Datenbank sein***. Somit kann man die Hashs unsicherer Passwörter zumindest vor dem Nachschlagen schützen. (Ausprobieren ist natürlich immer möglich. Allerdings wird Brute Force bei einem so langen String auch nicht in sinnvoller Zeit enden.)

*** Probieren Sie es aus:
md5('12345')=827ccb0eea8a706c4c34a16891f84e7b
md5('12345diesistirgendei#nfesterstring')=cb60acbb3dcf19985e85a64c7ae97b6b
md5-Datenbank zum Nachschlagen

Beispiel-Implementierung

Ich habe vor hier eine PHP/JS Beispiel-Implementierung zu veröffentlichen, welche das beschriebene Verfahren einsetzt.
Wenn Sie mir diese Arbeit abnehmen möchten oder ohnehin auf Basis dieses Artikels das Verfahren implementiert haben, würde ich mich sehr freuen, wenn ich Ihre Implementierung oder einen Link darauf hier veröffentlichen dürfte.
CrazyStat wird in der nächsten Version 1.64 das hier beschriebene Verfahren verwenden (momentan wird der Hash des Passworts gesendet - keine wechselnden Hashs durch angehängte Zufallszahlen).

Wie immer freue ich mich über Feedback, seien es Anregungen, Kommentare oder Kritik.