Tipps zum sicheren Speichern von Passwörtern

Haben Sie sich schon Gedanken darüber gemacht, ob Sie Passwörter Ihrer Benutzer sicher abspeichern? Was bedeutet sicher in diesem Kontext überhaupt?

Nehmen Sie mal an, es gelingt einem Angreifer Ihre Datenbank komplett zu stehlen. Gehen Sie zusätzlich davon aus, Ihre Benutzer verwenden das gleiche Kennwort auch für andere Dienste, beispielsweise für den E-Mail Account. Was bedeutet das für Ihre Benutzer?

Wenn Sie Passwörter als Klartext in der Datenbank speichern, werden Hacker Sie lieben. Einfacher können Sie es einem Angreifer nicht mehr machen. Auch wenn man es kaum glauben kann, man trifft dies regelmässig an.

Vielleicht speichern Sie die Passwörter verschlüsselt in die Datenbank? Das zeigt zwar, dass Sie sich der Problematik bewusst sind, leider ist das ebenfalls nicht sicher. Ein prominentes Beispiel ist der Softwarehersteller Adobe. Im Jahr 2013 sind der Firma 152 Millionen Passwörter inklusive dazugehörigen E-Mail Adressen abhanden gekommen. Der Reputationsschaden war natürlich enorm.

Das Problem beim Verschlüsseln von Passwörtern ist die Tatsache, dass sich Verschlüsselungen umkehren lassen. Kennt ein Angreifer den Schlüssel, lassen sich sämtliche Passwörter entschlüsseln.

Um Kennwörter sicher zu speichern, müssen Sie eine Kryptografische Hashfunktion verwenden. Eine kryptografische Hashfunktion ist eine Einweg-Verschlüsselung, die aus dem Klartext-Passwort einen Hash generiert. Es ist also nicht möglich, aus dem Hash das Passwort zu berechnen.

Leider ist es damit alleine noch nicht getan. Einerseits gelten ältere Hashfunktionen nicht mehr als sicher. Andererseits gibt es auch gegen die sicheren Hashfunktionen Angriffsmöglichkeiten.

Zu den unsicheren Hashfunktionen gehören MD4, MD5, SHA sowie SHA-1. Verzichten Sie für das Speichern von Passwörtern auf diese Hashfunktionen.

Auch wenn sich sichere kryptografische Hashfunktionen nicht umkehren lassen, sind sie nicht automatisch sicher. Es gibt unzählige Wörterbuchlisten im Internet, mit bereits berechneten Hashs. Damit lassen sich viele Standardpasswörter knacken. Um dies zu erschweren, sollten Sie die Passwörter vor dem Hashen zuerst salzen.

Unter salzen versteht man das zuziehen eines zufällig generierten Wertes, dem Salt. Dieser Salt wird in die Berechnung des Hashes mit einbezogen, was ein total unterschiedliches Resultat ergibt. Wichtig ist hierbei noch zu erwähnen, dass für jedes Passwort ein neuer Salt generiert wird.

Es gibt mehrere Hashfunktionen, die sehr empfehlenswert sind. Auf drei gehe ich näher ein.

PBKDF2

PBKDF2 steht für Password-Based Key Derivation Function 2 und wurde im Jahr 2000 als RFC 2898 standardisiert. Das .NET Framework implementiert diese Funktion in der Klasse Rfc2898DeriveBytes.

Zuerst wird der Salt berechnet, dafür verwende ich die Klasse RNGCryptoServiceProvider. Die Rfc2898DeriveBytes-Klasse benötigt nebst dem Salt und dem Passwort auch eine Rundenzahl. Je grösser diese Zahl gewählt wird, desto länger dauert die Berechnung des Hashs. Ich wähle hier 150000, womit die Berechnung ungefähr eine Sekunde dauert.

Um den Hash in der Datenbank zu speichern, wird der Salt dem Hash vorangehängt und als Base64-String zurückgegeben.

public static byte[] GetRandomSalt()
{
    var salt = new byte[8];
    using (var csp = new RNGCryptoServiceProvider())
    {
        csp.GetBytes(salt);
    }
    return salt;
}

public static string GetPbkdf2Hash(string password)
{
    var salt = GetRandomSalt();
    var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 150000);
    var hash = pbkdf2.GetBytes(16);
    var result = new byte[hash.Length + salt.Length];

    Array.Copy(salt, 0, result, 0, salt.Length);
    Array.Copy(hash, 0, result, salt.Length, hash.Length);

    return Convert.ToBase64String(result);
}

bcrypt

Bcrypt basiert auf dem Blowfish Algorithmus und wurde im Jahr 1999 veröffentlicht. Für .NET gibt es mehrere Implementierungen, eine davon ist BCrypt.Net.

Zuerst installiert man das NuGet Packet.

PM> Install-Package BCrypt-Official

Die Nutzung der Bibliothek ist dann ein Einzeiler. Zuerst wird das Passwort übergeben, danach ein Faktor, der die Rundenzahl erhöht. Ich wähle hier 12, womit die Berechnung ebenfalls ungefähr eine Sekunde dauert.

public static string GetBcryptHash(string password)
{
    return BCrypt.Net.BCrypt.HashPassword(password, 12);
}

scrypt

Scrypt wurde im Jahr 2010 veröffentlicht und ist daher noch weniger erforscht und erprobt als PBKDF2 und bcrypt. Der Algorithmus gilt jedoch als ähnlich sicher. Für das .NET Framework gibt es ebenfalls eine Implementierung, die in der Bibliothek CryptSharp zur Verfügung steht.

Dazu installiert man ebenfalls zuerst das NuGet Packet.

PM> Install-Package CryptSharpOfficial

Wie bei PBKDF2 wird zuerst ein zufälliger Salt generiert. Der Algorithmus benötigt nebst Kennwort und Salt ebenfalls eine Rundenzahl, hier 32768, was wiederum ungefähr eine Sekunde dauert. Ebenfalls wie bei PBKDF2 wird das Salt mit dem Hash kombiniert und als Base64-String zurückgegeben.

public static string GetScryptHash(string password)
{
    var salt = GetRandomSalt();
    var key = Encoding.UTF8.GetBytes(password);
    var hash = CryptSharp.Utility.SCrypt.ComputeDerivedKey(key, salt, 32768, 8, 1, null, 32);
    var result = new byte[hash.Length + salt.Length];

    Array.Copy(salt, 0, result, 0, salt.Length);
    Array.Copy(hash, 0, result, salt.Length, hash.Length);

    return Convert.ToBase64String(result);
}

Lässt man alle drei Funktionen laufen, ergibt sich folgendes Resultat:

password: JT$hQc*nw6BJW8tHyMGX
PBKDF2 (971ms) gB4PuOhWAZnfn9okvDCpxASUtw/vhuBz
Bcrypt (901ms) $2a$12$qPr1cQRy6vOcbrRlIbfpLusoiXuowaRkxxyWxSL2bf7dRym5H4/1e
Scrypt (1088ms) oFrFLySYbG2I4BSeVz4dqnSSdgYXkkbzxgDPMtYPEbinv9fnZYc9gA==

Weitere Tipps

  • Leider verwenden viele Benutzer schlechte Passwörter wie 123456, password oder qwerty. Nutzen Sie eine Blacklist für schlechte Passwörter.
  • Animieren Sie Ihre Benutzer, selbst sichere Kennwörter zu wählen, indem Sie die Kennwortstärke angeben.
  • Setzen Sie keine Beschränkungen. Weder in der Länge des Kennwortes noch in den verwendeten Sonderzeichen.