Password Strength

Überprüfen von Passwortrichtlinien

Posted on February 5, 2016 in jquery, tutorial

Inhalt

  • Einführung
  • Bedingungen
  • Regex
  • HTML - Markup
  • Function
  • Less

Einführung

In diesem Tutorial wird erklärt, wie eine Funktion zur Passwortrichtlinienprüfung erstellt mit jQuery erstellt wird. Es kommt sehr häufig vor, dass wir User haben, die sich bei uns registrieren sollen, aber deren Passwörter einfache Namen, Geburtsdaten oder Kennzeichen sind. Häufig wird der Benutzername als Passwort genommen oder einfache Folgen auf der Tastaur, wie yaq1XSW2. Sowas gilt es nun einzudämmen. Dieses Tutorial dient nur zur Verdeutlichung und möglichen Umsetzung. Es gibt diverse Dinge, die anders und besser umgesetzt werden können. Alle Regex wurden über Regex101 getestet.

Bedingungen - 1x kleiner Buchstabe - 1x großer Buchstabe - 1x Zahl - 1x Sonderzeichen - Username darf nicht im Passwort vorhanden sein - Keine 3 gleichen Zeichen nacheinander - Keine 4 gleichen Typen nacheinander - Blacklistfilter - Altes Passwort darf nicht im neuen Passwort vorhanden sein - Passwortlänge mindestens 8 Stellen

Es ist auch möglich einzelne Bedingungen abzuschalten, das wird aber später näher behandelt.

Regex

1x kleiner Buchstabe

[a-z]

Erläuterung: Finde einen kleinen Buchstaben von a bis z. Für diese Regel spielt es keine Rolle, wo der kleine Buchstabe aufläuft. Daher wird auf eine Stringbegrenzung, sowie einen Delimiter verzichtet.


1x großer Buchstabe

[A-Z]

Erläuterung: Finde einen großen Buchstaben von A bis Z. Hier gilt das gleiche, wie bei den kleinen Buchstaben.


1x Zahl

[0-9]
// Alternative Schreibweise: [\d]

Erläuterung: Finde eine Zahl von 0 bis 9. Hier gilt das gleiche, wie bei den kleinen Buchstaben. Zum besseren Verständnis, wurde diese Schreibweise gewählt.


1x Sonderzeichen

[^a-zA-Z0-9 ]

Erläuterung: Finde alles was kein kleiner und großer Buchstabe, sowie eine Zahl oder ein Freizeichen ist. Hier gilt das gleiche, wie bei den kleinen Buchstaben.


Keine 3 gleichen Zeichen

^(?:(.)(?!\1\1))*$ 

Erläuterung: Hier wird es jetzt etwas komplexer, als bei den vorherigen Beispielen.

Wir nutzen eine Stringbegrenzung, die mit dem Zeichen ^ anfängt und $ endet. Es wird dann alles in eine Gruppe zusammengefasst. ?: läutet eine “Non-Capturing Gruppe” ein, was genutzt wird, wenn mehrere Optionen zur Verfügung stehen. Mit der Gruppe (.) wird jedes Zeichen gefunden ( Wichtig für die Non-Capturing Gruppe ). Durch (?!\1\1) wird in der letzten Gruppe ein Negativ Lookahead aus ausgeführt, wo alles gefunden wird, was nicht mindestens 3x nacheinander (z.B.: ddd) auftritt. Ohne ( ?! ( Negativ Lookahead )), wird alles gefunden, was min. 3x auftritt.


Keine 4x gleichen Typen

^(?:(?![a-z]{4,})(?![0-9]{4,})(?![A-Z]{4,})(?![^a-zA-Z0-9_]{4,})(?![_]{4,}).)*$

Erläuterung: Hier ist es ähnlich des Regex mit den 3 gleichen Zeichen. Nur das es hier viel spezieller wird. Die Stringbegrenzung, sowie Non-Capturing Gruppe, sowie dem Negativ Lookahead sollten klar sein. Die erste Klammer nach der Non-Capturing Gruppe sagt folgendes aus. Finde alle kleine Buchstaben, die nur maximal 3x nacheinander vorkommen. Die zweite Klammer, macht dieses mit Zahlen, usw. Hinter der letzten Gruppe, mit dem “_” setzen wir einen Punkt, da nach dem auftreten jedes Zeichen folgen kann. Durch den * wird signalisert, dass unendlich viele Wiederholungen möglich sind.


Username nicht im Passwort

^(?!(?=.*" + username + "))
Delimiter gi

Erläuterung: Hier wird der String mit dem ^angefangen aber nicht geschloßen. Es folgt ein Negativ Lookahead, wo alles was in der Gruppe gefunden wird negiert wird. Die Gruppe sagt aus, finde den Username, es spielt keine Rolle, welche Zeichen davor oder dahinter stehen. Durch die Delimiter g/i wird alles gemachtet und nicht beim ersten abgebrochen, sowie auf Groß- und Kleinschreibung verzichtet.


Strings der Blacklist dürfen nicht vorkommen

^(?!(?=.*password|passw0rd|pass))
Delimiter gi

Erläuterung: Ähnlich der Usernameprüfung, nur dass hier mehrere Wörter gefunden werden können. Getrennt durch eine “Oder-Bedingung |” .


Altes Passwort, darf nicht im neuen Passwort vorkommen.

^(?!(?=.*"altesPasswort"))
Delimiter gi

Erläuterung: Siehe Usernamenprüfung.


Passwortlänge von mindestens 8 Zeichen

.{8,}$

Erläuterung: Wiederhole jedes Zeichen(.) und gebe bei mindestens 8 Zeichen {8,}, den Match zurück.

HTML-Markup

Wir bauen uns ein kleines Formular, wo nur die nötigsten Sachen hinterlegt sind. Es ist ein HTML-Markup von Laravel und Bootstrap.

<form class="form-horizontal" 
      role="form" method="POST"
      action="{{ url('/auth/register') }}">

    <input type="hidden" name="_token" value="{{ csrf_token() }}">

    <div class="form-group">
        <label class="col-md-4 control-label">Username</label>
        <div class="col-md-6">
            <input type="text" class="form-control" 
                   name="username" id="username"
                   value="{{ old('username') }}" autofocus>
        </div>
    </div>

    <div class="form-group">
        <label class="col-md-4 control-label">Password</label>
        <div class="col-md-6">
            <input type="password" class="form-control" 
                   name="password_old" 
                   id="password_old" value="l1T!K.3HLl">
        </div>
    </div>

    <div class="form-group">
        <label class="col-md-4 control-label">Password</label>
        <div class="col-md-6">
            <input type="password" class="form-control" 
                   name="password" 
                   id="password">
        </div>
    </div>

    <div class="form-group">
        <div class="col-md-6 col-md-offset-4">
            <button type="submit" class="btn btn-primary">Register</button>
            <div class="result" id="result"></div>
        </div>
    </div>
</form>

Function

Die komplette Funktion mit den Kommentaren als Beschreibungen.

(function() {
    $.fn.strengthChecker = function(userData, useFunctions, boolEnable) {

        // strengthChecker aktiv ja oder nein - Wenn kein Wert übergeben wird, dann aktiv
        var enable      = (typeof boolEnable === 'undefined') ? true : boolEnable;

        // Benutzerdefinierte Felder, - Wenn kein Wert übergeben wird, dann default
        var data        = checkDataParams(userData);

        // Einzelne Regeln de/aktivieren, - Wenn kein Wert übergeben wird, dann default
        var functions   = checkUseFunctions(useFunctions);

        // Ergebnisse der Regeln
        var result      = {} ;

        // Checker ist aktiv
        if(enable) {
            // Durchlaufe alle Regeln
            $.each(functions, function (i, val) {

                //Setze den Regex und Delimiter
                var re = new RegExp(val["regex"], val["delimiter"]);

                // Regel ist aktiv
                if (val["enable"] === true) {
                    // Prüfe ob die Regel schon dargestellt wird
                    if ($('#' + i).length == 0) {
                        // Lege die Regel und Ihre Beschreibung an
                        // Standard nicht erfolgreich
                        $('#result').append(
                                    "<p id='" + i + "'>" + val['description'] + "</p>"
                                           )
                                    .addClass('notSuccess');
                    }
                    // Regel ist erfüllt
                    // Bedingung wird über Css angepasst

                    if ($(data['passwordField']).val().match(re) != null) {
                        result[i] = true;
                        $('#' + i).removeClass('notSuccess').addClass('success');
                    }
                    // Regel ist nicht erfüllt
                    // Bedingung wird über Css angepasst
                    else 
                    {
                        result[i] = false;
                        $('#' + i).removeClass('success').addClass('notSuccess');
                    }
                }
            });

            // Durchlaufe die Ergebnisse
            $.each(result, function (i, val) {
                //  Mindestens eine Bedingung ist nicht erfüllt
                // Bedingungen werden angezeigt
                // Button wird deaktiviert
                if (val == false) {
                    $('#result').show();
                    $("[type=submit]").attr("disabled", "disabled");
                    return false;
                }
                // Alle Bedingung sind erfüllt
                // Bedingungen werden ausgeblendet
                // Button wird aktiviert
                else 
                {
                    $('#result').hide();
                    $("[type=submit]").removeAttr("disabled");
                }
            });
        }

        // Welche Funktionen werden vom User angewendet
        // Setze die Standardfunktionen 
        // Ändere die Bedingungen auf die vom User übergebenen
        function checkUseFunctions(useFunctions)
        {
            defaultParams   = {
                lowLetter:{
                    enable: true, 
                    regex:"[a-z]", 
                    delimiter:"", 
                    description: " Mindestens ein kleiner Buchstabe"},
                upLetter:{
                    enable: true, 
                    regex:"[A-Z]", 
                    delimiter:"", 
                    description: " Mindestens ein großer Buchstabe"},
                digits:{
                    enable: true, 
                    regex:"[0-9]", 
                    delimiter:"", 
                    description: " Mindestens eine Zahl"},
                special:{
                    enable: true, 
                    regex:"[^a-zA-Z0-9 ]", 
                    delimiter:"", 
                    description: " Mindestens ein Sonderzeichen"},
                sameSigns:{
                    enable: true, 
                    regex:"^(?:(.)(?!\\1\\1))*$", 
                    delimiter:"", 
                    description: " Keine 3x wiederholenden Zeichen"},
                sameTypes:{
                    enable: true, 
                    regex:"^(?:(?![a-z]{4,})(?![0-9]{4,})(?![A-Z]{4,})
                            (?![^a-zA-Z0-9_]{4,})(?![_]{4,}).)*$", 
                    delimiter:"", 
                    description: " Keine 4 gleichen Typen aufeinander"},
                sameUser:{
                    enable: true, 
                    regex:"^(?!(?=.*" + $(data['usernameField']).val() + "))", 
                    delimiter:"gi", 
                    description: " Benutzerame darf nicht im Passwort vorkommen 
                                 ( Groß- und Kleinschreibung wird nicht unterschieden )"},
                blacklist:{
                    enable: true,
                    regex:"^(?!(?=.*password|passw0rd|pass))", 
                    delimiter:"gi", 
                    description: " Passwort in der Blacklist vorhanden"},
                length:{
                    enable: true, 
                    regex:".{8,}$", 
                    delimiter:"", 
                    description: "Das Passwort muss mindestens 8 Zeichen lang sein."},
                samePass:{
                    enable: true, 
                    regex:"^(?!(?=.*" + $(data['oldPasswordField']).val() + "))", 
                    delimiter:"gi", 
                    description: " Altes Passwort darf nicht im neuen Passwort vorkommen 
                                 ( Groß- und Kleinschreibung wird nicht unterschieden )"}
            };
            // Kein Wert vom User übergeben
            if(typeof useFunctions === 'undefined')
            {
                return defaultParams;
            }
            else
            {
                $.each(defaultParams, function (i, val)
                {

                    if(i in useFunctions)
                    {
                        defaultParams[i]['enable']    = useFunctions[i];
                    }
                });
                return defaultParams;
            }
        }
        // Welche Parameter wurden vom User geändert
        // Setze die Standardparameter
        // Ändere die Bedingungen auf die vom User übergebenen
        function checkDataParams(data)
        {
            defaultParams = {
                usernameField: '#username',
                passwordField: '#password',
                oldPasswordField: '#password_old'
            };

            if(typeof data === 'undefined')
            {
                return defaultParams;
            }
            else
            {
                $.each(defaultParams, function (i, val)
                {
                    if(i in data)
                    {
                        defaultParams[i]    = data[i];
                    }
                });
                return defaultParams;
            }
        }
    };
})(jQuery);

Hier kann so ziemlich alles geändert werden, was nur möglich ist. Es ist, wie bereits in der Einleitung geschrieben nicht perfekt und dient nur der Näherbringung von Regex und der Funktionsweise. Die ganzen Standardwerte würde ich via Ajax, nachladen. Die Struktur sollte komplett als Klasse ausgelagert und personalisiert werden. Dadurch entsteht eine höhere Wart- und Lesbarkeit.

Less

Nun zum Abschluss bringen wir noch ein bisschen Farbe ins Spiel.

// Entweder als Package downloaden und integrieren oder via 
// https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css
@import "fontawesome/font-awesome";

.result {
  display: none;
  position:absolute;
  top:0;
  right: 0;
  float: right;
  width:300px;
  padding:15px;
  background:radial-gradient(ellipse, #ededed, darkgray);
  font-size:.5em;
  border-radius:5px;
  .box-shadow(1px 1px 1px);
  border:1px solid #ccc;
  z-index: 1;
  display: none;
  &::before {
    content: "\25B2";
    position:absolute;
    top:-12px;
    left:45%;
    font-size:14px;
    line-height:14px;
    color:#ddd;
    text-shadow:none;
    display:block;
  }
  p {
    padding: 2px;
    margin: 0;
    font-weight: bold;
  }
  .success {
    color: green;
    &:before {
      font-family: FontAwesome;
      content: "\F00C";
    }
  }
  .notSuccess {
    color: red;
    &:before {
      font-family: FontAwesome;
      content: "\F00D";
    }
  }
}

Wenn Unklarheiten oder Verbesserungsvorschläge sind, dann einfach ein kurzes Feedback dalassen.

Viele Grüße

_ Cyrix _

This article has no comments. You can leave the first comment.