heiko-barth.de

// Login absichern mit Hashing und Zwei-Faktor-Authentifizierung

Ein normaler Login besteht in der Regel aus einer HTML-Form und einem Server-Skript zur Validierung. Ist die Verbindung nicht zusätzlich über HTTPS abgesichert, kann ein Angreifer durch das Abhören des Netzwerkverkehrs die Zugangsdaten erlangen. Selbst wenn die Verbindung gesichert ist, gibt es andere (nicht technische) Möglichkeiten für einen Angreifer, z.B. die Eingabe der Zugangsdaten zu beobachten.

Einen HTTP Login absichern:

  1. Sichere Übertragung des Kennworts (per „Salted-Hash“)
  2. Zusätzliche Authentifizierung über ein Token (Smartphone App)

Grundgerüst

Um einen einfachen Login zu realisieren benötigt man eine HTML-Form mit Text- und Kennwortfeldern, sowie ein Server-Skript (in diesem Beispiel PHP) für die Validierung der Benutzerdaten:

login.html
<html>
<head>
<title>Login</title>
</head>
<body>
<form action="login.php" method="POST">
	Benutzername: <input type="TEXT" name="username"><br>
	Kennwort:     <input type="PASSWORD"name="password"><br>
	<input type="SUBMIT" name="Login" value="Login">
</form>
</body>
</html>
login.php
<?php
if ($_POST["username"] == "foo" && $_POST["password"] == "bar") {
	echo "Login ok";
else {
	echo "Login fehlgeschlagen";
}

Ablauf

  1. Ein Benutzer ruft http://example.com/login.html auf.
  2. In die Eingabemaske gibt er seine Zugangsdaten ein und sendet sie ab.
  3. Das Server-Skript login.php überprüft die Benutzerdaten und gibt eine entsprechende Meldung aus.

In diesem Beispiel wird kein HTTPS verwendet und die Benutzerdaten unverschlüsselt übertragen. In nicht vertrauenswürdigen Umgebungen (Internet-Cafe, öffentliches WLAN etc.) sollte man daher auf Logins dieser Art komplett verzichten.

Ein Mitschnitt des HTTP-Request (mit Hilfe eines Sniffer) sieht so aus:

POST /login.php HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 37

username=foo&password=bar&Login=Login

Hier sieht man in der letzten Zeile, dass als Benutzername foo und als Kennwort bar übertragen wurde.

Hashing mit SHA1

Oft wird im Zusammenhang mit hashen auch von Verschlüsselung gesprochen. Das ist schlichtweg falsch. Bei einer Verschlüsselung gibt es immer eine entsprechende Entschlüsselung. Hashen hingegen ist eine Einbahnstraße. Aus dem Ergebnis einer Hashfunktion kann man nicht auf den ursprünglichen Klartext schließen.

Mit Hilfe von clientseitigem JavaScript kann man das Kennwort vor dem Absenden hashen. Aus dem Kennwort „bar“ liefert die Hashfunktion SHA1 immer 62cdb7020ff920e5aa642c3d4066950dd1f01f4d zurück.

Ein potentieller Angreifer würde jetzt zwar nicht mehr das Kennwort im Klartext sehen, es würde ihm aber auch nicht weiter Kopfzerbrechen bereiten. Es reicht ihm völlig, den abgefangenen Hash erneut an den Server zu senden und sich so zu authentifizieren (Replay-Angriff). Sollte ihn das Kennwort trotzdem interessieren, könnte er mit Rainbow-Tabellen oder Brute-Force arbeiten..

Hieraus ergeben sich zwei Probleme:

  1. der Hash eines Kennworts ist immer gleich.
  2. je nach Länge und Komplexität des Kennworts kann durch eine Brute-Force Attacke oder mit Rainbow-Tabellen das ursprüngliche Kennwort1) aus dem Hash ermittelt werden.

Um dies zu vermeiden, kann man dem Angreifer die Suppe „versalzen“ 8-)

Salt (engl. ‚Salz‘) bezeichnet in der Kryptographie eine zufällig gewählte Zeichenfolge, die an einen gegebenen Klartext vor der Verwendung als Eingabe einer Hashfunktion angehängt wird, um die Entropie der Eingabe zu erhöhen. 2)

Nochmal zur Erinnerung: Hash = Hashfunktion(Kennwort).

Ein anderes Kennwort bzw. Kennwort + Salt ergibt folglich einen anderen Hash.

Das Server-Skript generiert zwei zufällige Zeichenkette (je ein Salt für das Kennwort und den Token), speichert diese in einer PHP-Session ab und fügt sie der Login-Form als „Hidden Value“ hinzu. Später beim Login wird dann mit JavaScript ein Hash aus dem Kennwort und dem Kennwort-Salt erzeugt. Das selbe passiert mit dem Token.

bcrypt

Die Benutzung von SHA1 ist nur beispielhaft. Mehr Sicherheit bietet bcrypt, dass im Vergleich zu SHA1 speziell für Kennwort-Hashing entwickelt wurde. Mehr Infos dazu gibt es hier. Ich habe mich im Beispiel für SHA1 entschieden, da ich keine vernünftige bcrypt Implementierung in JavaScript gefunden habe.

Zusätzlicher Authentifizierungsfaktor (Token)

Als zusätzlicher Authentifizierungsfaktor bietet sich die Benutzung eines Token in Form einer Smartphone-App an. Ähnlich dem TAN-Verfahren wird nach Eingabe einer PIN in der App eine 6-stellige Zeichenkette erzeugt. Diese muss beim Login zusätzlich zu Benutzername und Kennwort eingegeben werden. Die Zeichenkette ist wie eine TAN nur einmal gültig.

Hierfür wurde Mobile-OTP entwickelt. Zur Einrichtung muss ein gemeinsames Secret, sowie eine PIN definiert werden. Beides wird im Login-Skript, sowie in der App hinterlegt.

mOTP Smartphone Apps

Demo

Benutzername: foo
Kennwort: bar
Secret: topsecret123456!
PIN: 1234
URL: http://www.heiko-barth.de/stuff/login-demo/

Download

1)
oder eine andere passende Zeichenfolge ⇒ Stichwort Kollision

Leave a comment…



  _   __   __ __   ____   ___   __  __   ___   ____ 
 | | / /  / //_/  / __/  / _ \ / / / /  / _ \ / __ \
 | |/ /  / ,<    _\ \   / ___// /_/ /  / ___// /_/ /
 |___/  /_/|_|  /___/  /_/    \____/  /_/    \___\_\
  • E-Mail address will not be published.
  • Formatting:
    //italic//  __underlined__
    **bold**  ''preformatted''
  • Links:
    [[http://example.com]]
    [[http://example.com|Link Text]]
  • Quotation:
    > This is a quote. Don't forget the space in front of the text: "> "
  • Code:
    <code>This is unspecific source code</code>
    <code [lang]>This is specifc [lang] code</code>
    <code php><?php echo 'example'; ?></code>
    Available: html, css, javascript, bash, cpp, …
  • Lists:
    Indent your text by two spaces and use a * for
    each unordered list item or a - for ordered ones.
Web 2.0



RSS   RSS abonieren

Github   Github
QR Code