Hallo,
in mein aktuelles Projekt bekommt ja einen verschlüsselten Dokumenten-Container. Für die Passwortwahl will ich dem Benutzer ein Hilfe geben, wie gut das gewählte Passwort ist. Dazu hab' ich im Internet viele Theorien gelesen und natürlich
PassphraseQuality aus der Codelib ausprobiert.
Erst schien mir PassphraseQuality perfekt, wobei mir dann insbesondere bei der Passwortlänge ein merkwürdiges Verhalten aufgefallen ist. "Satty67!" wird mit 21% bewertet, während "Sattttttttttttttttttttttttttttttttttty67!" unter 5% liegt (wie kann das schlechter sein?).
Dann "TheQuickBrownFoxJumpsOverTheLazyDog" bekommt 100% während "TheWeatherIsWet,WhenTheWeatherIsWet!" nur 55% erhält. Beide Passwörter sind gleich lang, nur das Erste enthält lauter unterschiedliche Buchstaben. Allerdings denke ich, dass die Sonderzeichen höher zu bewerten sind, denn bei einer BruteForce-Attacke ist der eigeschränkte Buchstabensatz ja nicht bekannt (A-Z wir immer geprüft). D.h. es wird auch etwas zuviel Wert auf unterschiedliche Zeichen gelegt, wobei die zusätzliche Verwendung von Sonderzeichen keinen verstärkenden Einfluß hat.
Lange Rede, kurzer Sinn, ich hab' mir selber was überlegt
- Zeichenwiederholungen werden bei der Bewertung ignoriert und als Einzelzeichen gewertet, die restlichen Zeichen aber dadurch nicht abgewertet.
- Bei erkanntem Datum werden die Punkte nicht als wertvolle Sonderzeichen gewertet
- Prüfen, ob der Zeichensatz breit genutzt wird (Also Ziffern, Groß/Kleinbuchstaben, Sonderzeichen). Mindestens ein Zeichen der Gruppe ist nötig, um eine evtl. Attacke zu einem erweiterten Suchbereich zu zwingen.
- Anzahl unterschiedliche Zeichen im Verhältnis zur Länge ermitteln (nicht extrem hoch bewertet)
- Passwörter, die nur aus Buchstaben bestehen, abwerten (vermutetes Wort einer Sprache)
- Gesamtlänge bewerten (Länge schätze ich bis zu einer Obergrenze am wichtigsten ein)
- Die ganzen Einzelergebnisse ins richtige Verhältnis setzen
Ergebnis der Funktion ist hier auch ein Wert zw. 0.0 und 1.0 (zur besseren Vergleichbarkeit). Später werde ich wohl gleich ganze Prozente übergeben, damit man es gleich ohne Umrechnen einer ProgressBar o.ä. übergeben kann.
Alles in allem bin ich über das Bewertungsergebnis glücklicher, allerdings kenne ich nicht die Techniken im Detail, die bei einer Attacke angewendet werden. Deshalb kann ich mich bei der Bewertung eines Passwortes auch irren!
ToDo:- TopTen-Passwörter abwerten, wenn das dann in ein TPasswordEdit kommt, per Property setzbar
- Keyboard-Layout: QWERTZ und CO, was Hagen bei sich implementiert hat
Der Code wird später in einer Komponente verbaut und ist nur zu besseren Prüf-/Tesbarkeit hier als Funktion vorgestellt. Kann es auch später als Einzelklasse aufbauen, das ist fix gemacht. Nach derzeitigem Diskussionsstand ist man aber noch nicht von der Qualität des Prüfergebnisses überzeugt, weshalb es sich noch nicht lohnt da etwas zu entwerfen.
Delphi-Quellcode:
function PasswordStrength(const Password : String): Extended;
const
LowerMultiplicator = 1.9; // Verhältnisse der 4 Zeichengruppen untereinander
UpperMultiplicator = 2.1; // und Anteil am Gesamtergebnis
NumericMultiplicator = 1.5; // In der Summe etwa 10 scheint ausgewogen
SignMultiplicator = 2.5;
DiffCharsMaxMulti = 10; // Entropische Prüfung [schwach 10 - 100 stark]
MinPasswordLength = 4; // Ein kürzeres Passwort fällt völlig durch
MaxPasswordLength = 32; // Ein längeres Passwort verbessert nicht mehr das Ergebnis
// Zeichenwiederholungen (da wenig Wert) entfernen
function RemoveRepetitions(const AString : string): String;
var
i : Integer;
begin
Result := AString;
i := 2;
while i <= Length(Result) do
begin
if Result[i] = Result[i-1] then
Delete(Result, i, 1)
else
inc(i);
end;
end;
// Sofern ein Datum erkannt wird, wertlose Separatoren entfernen
function RemoveDateSeparator(const AString : string): String;
var
i : Integer;
dt : TDateTime;
DateStr : String;
begin
DateStr := AString;
i := Length(DateStr);
if (i > 0) and (AString[i] = DateSeparator) then
Delete(DateStr, i, 1);
if TryStrToDate(DateStr, dt) then
Result := StringReplace(AString, DateSeparator, '', [rfReplaceAll])
else
Result := AString;
end;
// Längen-Multiplikator mit Rücksicht auf Obergrenze
function LengthMul(const CurrentLength, MaxLength : Integer): Extended;
begin
if CurrentLength > MaxLength then
Result := Math.Log2(MaxLength) * Math.Log2(MaxLength)
else
Result := Math.Log2(CurrentLength) * Math.Log2(CurrentLength)
end;
// Bewerten der im Passwort verwendeten Zeichen
function CalculateEntropie(CleanPWLength, DiffCharsCount : Integer;
LowerMul, UpperMul, NumericMul, SignMul: Extended):Extended;
begin
Result := (DiffCharsCount * DiffCharsMaxMulti) / CleanPWLength;
if (NumericMul + SignMul) = 0 then
Result := Result + ((LowerMul + UpperMul) / 2)
else
Result := Result + LowerMul + UpperMul + NumericMul + SignMul;
end;
// Maximal ereichbares Ergebnis für Prozentrechnung ermitteln
function GetBestResult: Extended;
begin
Result := CalculateEntropie(1, 1, LowerMultiplicator, UpperMultiplicator,
NumericMultiplicator, SignMultiplicator);
Result := Result * LengthMul(MaxPasswordLength, MaxPasswordLength);
end;
var
i, CleanPWLength : Integer;
CleanPassword,
DiffChars : String;
EntropieMul,
LowerMul, UpperMul,
NumericMul, SignMul : Extended;
begin
Result := 0;
LowerMul := 0;
UpperMul := 0;
NumericMul := 0;
SignMul := 0;
CleanPassword := Trim(Password);
CleanPassword := RemoveDateSeparator(CleanPassword);
CleanPassword := RemoveRepetitions(CleanPassword);
CleanPWLength := Length(CleanPassword);
if (CleanPWLength >= MinPasswordLength) then
begin
for i := 1 to Length(CleanPassword) do
begin
case CleanPassword[i] of
'a'..'z': LowerMul := LowerMultiplicator;
'A'..'Z': UpperMul := UpperMultiplicator;
'0'..'9': NumericMul := NumericMultiplicator;
else
SignMul := SignMultiplicator;
end;
if Pos(CleanPassword[i], DiffChars) < 1 then
DiffChars := DiffChars + CleanPassword[i];
end;
EntropieMul := CalculateEntropie(CleanPWLength, Length(DiffChars),
LowerMul, UpperMul, NumericMul, SignMul);
Result := EntropieMul * LengthMul(CleanPWLength, MaxPasswordlength);
Result := Result / GetBestResult;
end;
end;
Der Vollständigkeit halber ein Anwendungsbeispiel:
Delphi-Quellcode:
procedure TForm1.Edit1Change(Sender: TObject);
begin
Label1.Caption := Format('Sicherheit bei annähernd %.1f%%',
[PasswordStrength(Edit1.Text) * 100]);
end;