Die Herausforderung

undefinedVor einiger Zeit stand ich bei der Arbeit vor der Herausforderung, eine Erkennung der Passwort-Sicherheit zu entwickeln. Es ging darum zu bewerten, ob ein vom Nutzer eingegebenes Kennwort für uns sicher genug ist oder eben nicht. Als erstes habe ich die gängigen Verfahren angesehen. Meist geht es nach dem selben Schema:

Mindestens N Zeichen, davon mindestens Groß und Kleinbuchstaben, Zahlen, Sonderzeichen...

Genaugenommen variiert meist nur die minimum Anzahl geforderter Zeichen und die gewünschte Auswahl und Kombination der anderen Kriterien. Manchmal wird getrickst und es müssen mindestens 2 Zahlen verwendet werden etc.

Ich selbst fand diese Variante nicht ausreichend und gleichzeitig zu unsicher. Was ist mit diesem Passwort?

Donald+1

Es erfüllt schon alle Kriterien wenn N = 8 der oben vorgeschriebenen Regeln. Das ist sicher nicht im Sinne des Erfinders. Macht man die Regeln noch strenger, so kann der Nutzer kaum ein Passwort wiederverwenden. Ob die Nutzung des selben Passworts an verschiedenen Stellen jetzt gut oder schlecht ist, ist ein ganz anderes Thema. Aber der Nutzer ist auf jeden Fall schnell gefrustet, wenn er ein Passwort eintippt und der Algorithmus immer NEIN sagt.

Weiter lesen? Klicke die Headline um den ganzen Artikel zu sehen!

Alternative Herangehensweise

Also machte ich mir Gedanken über eine passendere Bewertung und kam zu dem Schluss, dass ein gutes Passwort doch auch an der Varianz der Zeichen gemessen werden könnte. Also 1aB ist viel besser als 123 oder abc oder ABC etc. Warum? Weil die Zeichenklassen (Groß/Klein/Numerisch) abwechseln. Dazu kommen noch andere Kriterien wie die Länge und der Prozentuale Anteil Zahlen zu Buchstaben. Mit diesen Kriterien habe ich einen eigenen Algorithmus erstellt, der inzwischen sehr gut zu funktionieren scheint. Außerdem lässt er viele der meisten Lieblings-Passwörter nicht durch.

Wie mein Ansatz funktioniert

Hier beschreibe ich kurz, wie das Funktioniert:

  1. Die initiale Stärke wird mit Länge^2/6 berechnet. Durch den Exponent wird das Punkte-Ergebnis umso stärker je länger das Passwort ist und kürzere Passwörter werden mehr bestraft:
    undefined
  2. Jetzt wird vorab, weil der folgende Loop direkt beim zweiten Zeichen beginnt, die erste Stelle auf numerisch geprüft. Wenn ja, wird das gezählt (numCount zählt die Anzahl numerischer Stellen).
  3. Im Loop get es jetzt ab Zeichen 2 bis zum Ende durch.
    1. Ist das aktuelle Zeichen nicht das selbe wie das vorangehende, gibt es zwei Punkte plus, ansonsten ein Punkt minus.
    2. Jetzt wird geprüft, ob das vorige und das aktuelle Zeichen uppercase oder lowercase sind. Wenn Groß/Kleinschreibung dabei abweicht, gibt es zwei Punkte plus.
    3. Wenn jetzt das aktuelle Zeichen in Summe mehr als drei Mal im Passwort vorkommt, gibt es wieder 2 Punkte minus. Das passiert in jedem Durchlauf, daher zählt das mehrfach. Zu Oft das selbe Zeichen macht das Passwort unbrauchbar!
    4. Ist das Zeichen numerisch, wird das in numCount mitgezählt (wird später verwendet).
  4. Am Ende des Loops gibt es noch extra Bewertungen:
    1. Ist die Anzahl numerischer Zeichen zwischen 20% und 70%, gibt es 5 extra Punkte.
    2. Ist die Anzahl numerischer Zeichen über 70%, gibt es hingegen 5 Punkte Abzug.
    3. Jetzt wird noch Sichergestellt, dass kein Wert kleiner Null (0) zurückgegeben wird.

Wie schlägt sich die Funktion?

Wie schlägt sich die Funktion bei verschiedenen Passwörtern? Wenn man mindestens 30 Punkte für ein sicheres Passwort verlangt, schaut es so aus:

abcdefgh -> 25
Password -> 24
Donald+1 -> 29
123456789 -> 25
aB46Z2w -> 35 OK
iloveyou -> 25
sunshine -> 25
princess -> 22
qwertyuiop -> 35 OK
asdfasdf -> 25
1q2w3e4r -> 44 OK
oERF4884 -> 31 OK

Man sieht hier ein paar zugelassene Zeichenketten (OK). Am besten testet Ihr das einfach mal selbst (zum Beispiel hier). Wir haben damit bisher sehr gute Erfahrungen gemacht und es gab noch keine Klagen von Nutzern. Gleichzeitig haben wir definitiv zu einfache Kennwörter effektiv verhindert.

Wenn Ihr es brauchen konntet, würde ich mich über einen kurzen Kommentar freuen. Natürlich auch, wenn Ihr es verbessert habt :-)

Quellcode

Auf Grund einer Echtzeit-Vorschau und einer Server-Seitigen Prüfung habe ich den selben Algorithmus in JavaScript und PHP identisch implementiert. Hier der Code:

PHP

<?php
/**
* (w) 2015 Volker Schmid
* (c) free to use, copy, vary and distribute
*
* Calculate a strength value from given password.
* If Strength is > 30, the password is considered safe!
*
* KEEP THIS SIMILAR TO JS FUNCTION PassStrength()!!!!
*
* @param string Password
* @returns int Strength
*/
function PassStrength($Password) {
    // length check
    $numCount = 0;
    // initial strength = len^2/6
    $W = (strlen($Password) * strlen($Password)) / 6;
    if (is_numeric(substr($Password, 0, 1))) {
        $numCount + 1; // note first character is numeric
    }
    for ($i=1; $i<strlen($Password); $i++) {
        // if previous char was another one this is good, otherwise bad
        $t = substr($Password, $i, 1); // this
        $p = substr($Password, $i-1, 1); // previous
        if ($t != $p) {
            $W = $W + 2;
        } else {
            $W = $W - 1;
        }
        // check, if previous char was other case the current (good)
        $upper =  ($t == strtoupper($t));
        $lower =  ($t == strtolower($t));
        $pupper = ($p == strtoupper($p));
        $plower = ($p == strtolower($p));

        // good if previous case is different than current
        if ($upper != $pupper || $lower != $plower) {
            $W = $W + 2;
        }

        // check if value is used multiple times
        $occurences = explode($t, $Password);
        if (count($occurences) > 3) {
            $W = $W - 2;
        }

        // count number of numeric characters
        if (is_numeric($t)) {
            $numCount = $numCount + 1;
        }
    }

    // extra points if number of numeric characters is between 20 and 70 percent
    if ($numCount > strlen($Password) * 0.2 && $numCount < strlen($Password) * 0.7) {
        $W = $W + 5;
    }

    // not good if password is more than 70% numbers
    if ($numCount > strlen($Password) * 0.7) {
        $W = $W - 5;
    }

    // no negative results
    if ($W < 0) { $W = 0; }

    // return rounded result
    return round($W);
}
?>

JavaScript

Und hier das selbe in JavaScript:

/**
* (w) 2015 Volker Schmid
* (c) free to use, copy, vary and distribute
*
* Calculate a strength value from given password.
* If Strength is > 30, the password is considered safe!
*
* KEEP THIS SIMILAR TO PHP FUNCTION PassStrength()!!!!
*
* @param string Password
* @returns int Strength
*/
function PassStrength(Password) {
    // length check
    var numCount = 0;
    // initial strength = len^2/6
    var W = (Password.length * Password.length) / 6;
    var numeric = !isNaN(parseFloat(Password[0])) && isFinite(Password[0]);
    if (numeric) { 
		// note first character is numeric
		numCount = numCount + 1; 
	}
    for (var i=1; i<Password.length; i++) {
        // if previous char was another one this is good, otherwise bad
        if (Password[i] != Password[i-1]) {
            W = W + 2;
        } else {
            W = W - 1;
        }
        // check, if previous char was other case the current (good)
        var upper =  (Password[i]   == Password[i].toUpperCase());
        var lower =  (Password[i]   == Password[i].toLowerCase());
        var pupper = (Password[i-1] == Password[i-1].toUpperCase());
        var plower = (Password[i-1] == Password[i-1].toLowerCase());

        // good if previous case is different than current
        if (upper != pupper || lower != plower) {
            W = W + 2;
        }

        // check if value is used multiple times
        var occurences = Password.split(Password[i]);
        if (occurences.length > 3) {
            W = W - 2;
        }

        // count number of numeric characters
        var numeric = !isNaN(parseFloat(Password[i])) && isFinite(Password[i]);
        if (numeric) { numCount = numCount + 1; }

    }
	
    // extra points if number of numeric characters is between 20 and 70 percent
    if (numCount > Password.length * 0.2 && numCount < Password.length * 0.7) {
        W = W + 5;
    }
	
    // not good if password is more than 70% numbers
    if (numCount > Password.length * 0.7) {
        W = W - 5;
    }

    // no negative results
    if (W < 0) { W = 0; }

    // return rounded result
    return Math.round(W);
}

PureBasic

Eine PureBasic-Version findet Ihr im englischen PureBasic-Forum:

http://www.purebasic.fr/english/viewtopic.php?f=12&t=68654&p=508161