Passwort vergessen Funktion – „Challenge-Response“-Verfahren (Teil 1)

Man sieht sie überall und fragt sich wie sowas wohl funktioniert. Man klickt auf einen Link, gibt seine E-Mail-Adresse und bekommt ein neues Passwort zugeschickt.

Vor allem jetzt, wo die Passwörter möglichst lang und kompliziert sein müssen, verlieren oder vergessen immer mehr Benutzer ihre Zugangsdaten. Sie werden beim ersten Login einfach im Passwort-Tresor des Browsers abgespeichert und vergessen. Das Dilemma ist dann besonders groß, wenn die Passwörter durch ein Versehen, oder nach einem Systemcrash gelöscht wurden. In diesem Fall ist eine solche „Passwort vergessen“-Funktion Gold wert.

Doch wie funktionieren solche Funktionen und wie sollte man sie umsetzten?

Es sei gleich gesagt, dass der Ansatz, den Benutzer durch eine Frage in der Art von „Wie heißt Ihr Hund?“ zu identifizieren, falsch ist. Diese Fragen lassen sich sehr leicht beantworten.

Sollte ein Benutzer sein Passwort vergessen haben, sollte er immer eine Mail an die bei Ihnen gespeicherte E-Mail Adresse bekommen. Hierzu müsste der Benutzer seinen Benutzernamen oder gleich seine E-Mail Adresse in ein Formularfeld eingeben. Sie müssen jedoch sicher sein, dass die E-Mail Adresse auch wirklich nur von einem Benutzer verwendet werden kann.

Bitte vermeiden Sie das Zusenden von Passwörtern direkt per E-Mail. Der Benutzer sollte weder das alte noch ein neues Passwort unverschlüsselt per Mail bekommen! Sie fragen sich sicherlich: Wieso nicht?“. Der Grund ist recht einfach. Die E-Mails werden fast immer unverschlüsselt übertragen und bieten so einem Angreifer, der die Leitung abhört, freien Zugang zum Account.

Außerdem könnten „Spaßvögel“ die User ärgern, indem sie ständig neue Passwörter zuschicken und somit die alten Passwörter unbrauchbar machen. Somit kann sich der Benutzer vorerst nicht mehr einloggen. Und nicht jeder guckt ständig nach neuen E-Mails.

Meiner Meinung nach ist das sogenannte „Challenge-Response“-Verfahren die beste Lösung für solche Probleme.

Das Verfahren läuft mehrstufig ab und ist trotzdem relativ einfach:

  • Der Benutzer fordert ein neues Passwort an
  • In die Datenbank wird eine Challenge-ID, zusammen mit der User-ID abgelegt
  • Es wird eine E-Mail mit einem Aktivierungslink an den Benutzer verschickt
  • Der Benutzer hat 24 Stunden Zeit auf den Aktivierungslink zu klicken
  • Auf der Verlinkten Seite kann der Benutzer ein neues Passwort eingeben

Die Umsetzung dieser Funktion gestaltet sind mit PHP gar nicht so schwer. Man benötigt lediglich zwei Dateien, passvergessen.php und challenge.php. Die erste Datei soll lediglich die eingegebene E-Mail Adresse entgegennehmen, überprüfen, ob diese E-Mail Adresse auch wirklich in Ihrer Datenbank vorhanden ist, trägt die Challenge-ID in die Datenbank ein und verschickt eine E-Mail mit dem Aktivierungslink an den Benutzer. Die Datei challenge.php besteht aus zwei Funktionen, die eine soll das Formular zum ändern des Passwortes ausgeben und die andere die Challenge-ID ungültig machen.

Die Tabelle müsste dann folgendermaßen aussehen:

Field Type Null Key Default Extra
userid int(11) YES NULL
challenge varchar(50) YES MUL NULL
valid tinyint(1) YES NULL
timestamp int(11) YES NULL

Das Script passvergessen.php erledigt folgendes:

  • Nimmt die E-Mail Adresse entgegen
  • Überprüft diese, ob sie syntaktisch gültig und in der Datenbank vorhanden ist.
  • Wenn ja, erstellt es eine Challenge-ID und fügt diese in die Datenbank hinzu
  • Sendet eine E-Mail mit den URL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
< ?php
 
include_once 'config.php';
 
function validate_email($e_mail) {
 // Die Mailadresse wird zunächst nur auf äusserliche richtigkeit geprüft:
 if(!preg_match("^[_a-zA-Z0-9-]+(.[_a-zA-Z0-9-]+)*@[a-zA-Z0-9-]+.([a-zA-Z0-9-]{2,4})$",$e_mail)){
  // Wenn die äusserliche Form nicht richtig ist wird FALSE zurückgegeben:
  return FALSE;		
 } else {
  return TRUE;
 }
}
 
if(isset($_POST['do_send']) && !empty($_POST['user_email'])) {
 
 $user_email= escape_string(strip_tags(trim($_POST['user_email'])));
 if(!validate_email($user_email)) {
  echo "Keine gültige E-Mail Adresse!";
  exit();
 }
 
 $pruefung = mysql_query("
  SELECT
   user.`user_id`
  FROM
   `user`
  WHERE
   (`user_email` = '$user_email')
  LIMIT
   1") or die(mysql_error());
 
  if(mysql_num_rows($pruefung) === 1) {
   $user = mysql_fetch_assoc($pruefung);
 
   $change_challenge = md5(md5((string)mt_rand() . $_SERVER['REMOTE_ADDR']));
   $timestamp= time();
 
   $challenge_eintragen = mysql_query("
    INSERT INTO
     `changepass`
    SET
     `userid` = '".$user['user_id']."',
     `challenge` = '$change_challenge',
     `valid` = 1,
     `timestamp` = '$timestamp'") or die(mysql_error());
 
    $empfaenger = $user_email;
 
    $betreff = "Aktivierungsmail zur Passwortändeung - ".$_SERVER['SERVER_NAME']."";
    $header  = "MIME-Version: 1.0n";
    $header .= "Content-type: text/html; charset=iso-8859-1n";
    $header .= "From: ".$betreff." - ".$user_email."rn"; 
 
    $nachricht = '
    Du erhälst nun einen Aktivierungslink der 24 Stunden nach dem Beantragen 
gültig ist. Nach dem Klicken auf den Link, hast du die Möglichkeit ein neues 
Passwort zu wählen.
    '.$_SERVER["HTTP_HOST"].'?UserID='.$user["user_id"].'&challenge='.$change_challenge.'';
 
    $send = mail($empfaenger, $betreff, $nachricht, $header);
 
    if((bool)$challenge_eintragen === TRUE && (bool)$send === TRUE) {
     echo '<h1>E-Mail versendet';
     echo '<p>Es wurde eine E-Mail an Sie versendet. Diese Enthält einen Link zum Ändern 
von Ihrem Passwort. Dieser Link ist 25 Stunden ab dem Beantragen gültig. Danach muss 
ein neues Link beantragt werden.</p>';
    } else {
     echo '<h1>Fehler</h1>';
     echo '<p>Beim generieren der Challenge oder beim Verschicken der E-Mail 
ist ein Fehler aufgetreten.</p>';
    }
  } else {
   echo '<h1>Fehler</h1>';
   echo '<p>Die eingegebene E-Mail Adresse befindet sich nicht in der Datenbank.</p>';
  }
 
 } else {
  echo '
  <h1>Passwort vergessen</h1>
  <form action="" method="post">
   <dl>
    <dt><label for="user_email">E-Mail:</label></dt>
    <dd><input type="text" name="user_email" id="user_email" value="" /></dd>
   </dl>
   <dl>
    <dt>Optionen:</dt>
    <dd>
     <input class="button" type="submit" name="do_send" id="do_send" value="Jetzt ändern!" />
     <input class="button" type="reset" name="reset" id="reset" value="Doch nicht..." />
   </dd>
   </dl>
  </form>';
}
?>

Bitte beachten Sie, dass dieses Script nur als Beispiel dienen soll und somit nicht alles ausführlich behandelt!

Der zweite Teil von dem Artikel “Passwort vergessen Funktion – „Challenge-Response“-Verfahren”

ALL-INKL.COM

12 Kommentars zu dem Artikel “Passwort vergessen Funktion – „Challenge-Response“-Verfahren (Teil 1)”

  1. Avatar von Manko10
    Manko10

    Hallo,

    nur als Vorschlag: statt md5() würde ich sha1() benutzen. Das erzeugt längere Zeichenketten, die dann natürlich auch schwerer zu erraten sind.
    Für reguläre Ausdrücke sind die Funktionen der preg_*-Familie besser und nicht die veralteten ereg_*- und eregi_*-Ausdrücke.

    Jetzt nicht direkt zum Thema: könntest du den Code-Blöcken eine horizontale Scrollbar spendieren? Dann ragen die nicht so weit über den rechten Rand hinaus.
    Ansonsten: well done!

  2. Avatar von StarSt0rm
    StarSt0rm

    Hallo Manko10,

    vielen Dank für dein Kommentar. Ich werde deinen Ratschlag befolgen und den Code anpassen.

    Stimmt, das mit den Scrollbalken… Darauf bin ich einfach noch nicht gekommen. Werde es auch so schnell wie möglich einrichten.

    Liebe Grüße
    Alex

  3. Avatar von php0kid
    php0kid

    Hi!
    Sehr ausführlicher Code! Allerdings rate ich dir Codesnippets während der Erklärung nach und nach aufzubauen und am Ende entweder den vollständigen Code zusammenzufassen oder ihn als Download anzubieten. So muss man viel hin und herscrollen und sich durch unkommentierten Code wühlen *g*

    Trotzdem sehr schön zu lesen und von der Methode her ausgereift, auch wenn ich statt “challenge” in der Datenbank eher “token” sagen würde .. !

    Noch etwas zu deinem Stil: Nicht so viel Englisch mit Deutsch mischen, ansonsten gefällts mir.

    Was ist denn der Vorteil von mt_rand()? Ist das wirklich lohnenswert schneller?

    lG

  4. Avatar von StarSt0rm
    StarSt0rm

    Hallo php0kid!

    Vielen Dank für dein Kommentar. Das mit dem “Code über den ganzen Artikel verteilen” finde ich eine echt gute Idee. Werde es denke ich mal in Zukunft so machen. Dnake.

    Über die Begriffe kann man sich denk ich mal streiten, oder?

    An meinem Deutsch/Englisch arbeite ich bereits, da meine neuen Kollegen das auch nicht sooooo toll finden. :D

    Hinweis: Auf manchen Platformen (Windows z.B.) ist RAND_MAX nur 32768. Wenn sie einen größeren Wertebereich benötigen sollten so können Sie entweder einen größeren max Wert übergeben oder besser die mt_rand() Funktion anstelle von rand() einsetzen.

    Viele Zufallszahlengeneratoren, die auf älteren libc-Versionen basieren, haben seltsame oder doch zumindest unerwartete Verhaltensweisen und sind zudem recht langsam. Standardmäßig verwendet PHP den libc-Zufallszahlengenerator mit der Funktion rand(). Die Funktion mt_rand() kann jedoch als vollwertiger Ersatz verwendet werden. Sie verwendet einen Zufallszahlengenerator mit den bekannten Charakteristika der » Mersenne Twister, die Zufallszahlen viermal schneller generiert als der durchschnittliche libc-rand()-Aufruf.

  5. Avatar von StarSt0rm
    StarSt0rm

    So, ich habe mir nun die Zeit genohmen und die Code-Box
    richtig angepasst. Jetzt kommt lediglich ein horizontaler
    Scroll-Balken wenn der Code zu viel Platz braucht. Das ist
    echt viel übersichtlicher. :)

  6. Avatar von WANTED
    WANTED

    Отличная идея, но надо бы подумать о рекламе на блоге. По-моему ее слишком много :) Хотя, конечно – это не мое дело :)

  7. Avatar von StarSt0rm
    StarSt0rm

    Спасибо за комментарий. почему ты считаешь что ее слишком много? Только Google AdSense. ;) Как-то надо-же оплачивать сервер. :(

  8. Avatar von Karl
    Karl

    Hallo.
    Eine Funktion für die Validierung einer E-Mail schreiben??? Laut meinem schlauen Buch gibts in PHP da schon eine Funktion:
    filter_var($email. FILTER_VALIDATE_EMAIL, FILTER_NULL_ON_FAILURE);
    Karl

  9. Avatar von StarSt0rm
    StarSt0rm

    Frohes Neues Karl!
    Ja, die gibt es in der Tat und die soltest du auch benutzen. Jedoch ist PHP 5.2 Voraussetzung dafür.
    Als ich das Script geschrieben haben, stand mir diese Funktion leider nicht zur Verfügung. Inzwischen benutze ich auch nur “filter_var”.

    LG, Alex

  10. Avatar von Stefan Friedrichs
    Stefan Friedrichs

    Hallo, ich hab noch ne kleine Frage,
    was hat es mit der config.php auf sich ?! Warum wird die included ?
    lg Stefan

    • Avatar von Alexander Bogomolov
      Alexander Bogomolov

      Hallo Stefan,
      da wird eigentlich nur die Datenbankverbindung aufgebaut. ;)
      Gruß
      Alex

Kommentar hinterlassen