Hey Leute,
ich wollte euch ganz kurz in die Basics der Kryptologie einführen, da scheinbar einige das mit den Passwörtern noch nicht verstanden haben
Erst einmal klären wir ein paar Fachbegriffe:
Einwegfunktionen
Eine "echte" Einwegfunktion verändert einen Wert und lässt ihn (offiziell) nicht wieder zurückrechnen
Ein (schlechtes) Beispiel ist z.B.
Aus dem alten Wert lässt sich ruck-zuck ein neuer Wert berechnen, anders herum kann es zu Schwierigkeiten kommen und man müsste mit Raten anfangen...
Für die, die mir nicht glauben: neuerwert = 4,3125 Wer das zurückrechnet, dem schicke ich per Post einen Lollie
Einwegfunktionen mit Falltür In diesem Beispiel: Public-Key-Verfahren
Nun gibt es einige Einwegfunktionen, die man doch zurückrechnen kann, aber nicht so, wie man sie berechnet hat.
Das mag jetzt kompliziert klingen (ist es auch), aber ich stelle das mal bildlich da, erkläre das dann danach:
- Ihr habt eine Poststelle (o.ä.) an der jeder eine Eisenkasette hat
- Wollt ihr etwas verschlüsseln, macht ihr die Kasette zu und lasst das Schloss einklicken.
- Nun kann niemand mehr an den Inhalt rein (außer er nutzt Gewalt, aber wir gehen von netten Leuten aus)
- Der, dem die Kassette gehört, geht nun in die Poststelle, öffnet die Kasette mit seinem Schlüssel und hat den Inhalt vorliegen
Ein tägliches Beispiel davon, und genau davon, finden wir täglich: HTTP mit SSL ( https )
Beim Public-Key-Verfahren gibt es einen Public-Key und einen Private-Key. Der Public-Key ist öffentlich bekannt und das Schloss der Kassette, der Private-Key der Schlüssel.
Hash
Ein Hash ist eine Einwegfunktion. Das bedeutet, man kann ihn nicht zurückberechnen, man kann aber "probieren"
Salts ( Salz )
Salts sind noch mal ein Aufsatz auf den Hash, weil dabei 2 "Passwörter" existieren - eines wird vom Server bei der ersten Erstellung des Accountes erstellt, das andere kommt von Benutzer.
Dann werden beide aneinander gehängt und gehasht. Davon der Hash und der Salt werden gespeichert.
Beispiel:
Benutzerpasswort: helloworld
Salt: kdbjyhbfdxkjyhxdbkjsf
Vor dem Hash: helloworldkdbjyhbfdxkjyhxdbkjsf
Gehasht: hdhggksdhfmkfudjdyuhglgfj
Gespeichert wird der Salt und "Gehasht", dann wird das jedes mal neu zusammengesetzt.
Der Vorteil von Salts liegt darin, dass ein (hoffentlich nicht) Hacker die Hash-Summen nicht einfach in einer großen Datenbank nachschlägt, da der gesaltete Hash von dem normalen Hash unterscheidet.
Pepper ( Pfeffer )
Der Pepper ist im Prinzip ein "globaler Salt", der für alle Accounts gleich ist und, genau wieder Salt, optional alleine oder in Verbindung mit ihm genutzt werden kann. Pfeffer und Salz ergänzen sich prima, da, sollte jemand Zugriff auf die Datenbank haben, er den Pepper nicht kennt, sollte er ihn aber kennen und in den Jahren vor dem Hack eine Datenbank mit dem Pepper angelegt haben über den Salt stolpert. Außerdem macht er den String vor dem Hashen noch etwas länger
Kollisionen
Kollisionen existieren, wenn 2 Werte den selben Hash haben. Eine Beispiel-Kollision für MD5:
d131dd02c5e6eec4693d9a0698aff95c
2fcab5[b]8[/b]712467eab4004583eb8fb7f89
55ad340609f4b30283e4888325[b]7[/b]1415a
085125e8f7cdc99fd91dbd[b]f[/b]280373c5b
d8823e3156348f5bae6dacd436c919c6
dd53e2b487da03fd02396306d248cda0
e99f33420f577ee8ce54b67080a80d1e
c69821bcb6a8839396f9652b6ff72a70
d131dd02c5e6eec4693d9a0698aff95c
2fcab5[b]0[/b]712467eab4004583eb8fb7f89
55ad340609f4b30283e4888325[b]f[/b]1415a
085125e8f7cdc99fd91dbd[b]7[/b]280373c5b
d8823e3156348f5bae6dacd436c919c6
dd53e23487da03fd02396306d248cda0
e99f33420f577ee8ce54b67080280d1e
c69821bcb6a8839396f965ab6ff72a70
Alles anzeigen
Nun ist es sehr unwarscheinlich, dass ein anderer Benutzer just dieses Passwort hat, aber wenn man genug Zeit hat kann man zu eigentlich jeden Hash eine Kollision finden
Verschlüsselung
Eine Verschlüsselung ist kein Hash !
Eine Verschlüsselung zeichnet sich dadurch aus, dass man sie wieder entschlüsseln kann - unterschieden wird dabei zwischen symetrisch und asymetrisch. Bei ersteres wird zum Ver- und Entschlüsseln das gleiche Passwort genutzt ( Beispiel: AES ), bei zweiteren sind die Passwörter unterschiedlich ( Beispiel: DES, Public-Key-Verfahren. Natürlich kann man das auch mischen, dass das Passwort asymetrisch gespeichert / gesendet wird und danach auf symetrische verfahren zugegriffen wird ( "Hybride Verschlüsselung" ) - der Vorteil liegt dabei daran, dass man nicht den Geschwindigkeitsverlust von dem asymetrischen Verschlüsselungsverfahren einbüßt, das Passwort sicher übertragen kann und die Verbindung danach auch noch sicher ist.
Kodierungen
Eine Kodierung unterscheidet sich von einer Verschlüsselung dadurch, dass die Kodierung kein Passwort hat. Als ein bekanntes Beispiel dient Base64 oder Caesar. Problematisch wird es u.a. bei der Rot-"Verschlüsselung", da dort theoretisch auch ein Passwort genutzt wird, es sich aber schnell erraten lässt - und das Caesar theoretisch auch nur Rot13 ist, könnte man es ja auch als Passwort einstufen
Wie realisiere ich jetzt einen sicheren Login ?
- Der Benutzer registriert sich und gibt sein Passwort ein.
- Wir generieren für das Passwort einen Salt und hashen das beides zusammen
- Gesalteter Hash und Salt kommen in die Datei/Datenbank/...
- Bei der Passwortabfrage hängen wir an das eingegebene Passwort wieder den Salt an
- Jetzt hashen wir die zusammenstellung oben und vergleichen das mit dem gesaltenem Hash
- Stimmt das überein, ist das Passwort richtig
Als Hilfe dafür habe ich 2 Funktionen definiert (findet ihr gaaaaaanz unten), eine erzeugt einen Salt beliebiger Länge, die andere Hasht den Salt und das Passwort zu beliebiger Länge.
Beispiel zur Nutzung: new salt[64];
new pwhash[128];
GenerateSalt(64, salt); // Wir erstellen einen zufälligen Salt für den Benutzer
CreateSaltedHash(inputtext, salt, 128, pwhash); // Wir erstellen einen salted Hash des Passwortes ( inputtext <=> Passwort ) Wichtig ist, und das wiederhole ich jetzt noch einmal, dass der Salt mitgespeichert wird.
Denn ein anderer Salt ergibt ein anderen Hash.
Vergleichen können wir die Passwörter nun folgendermaßen: new pwhash[128];
CreateSaltedHash(inputtext, PInfo[playerid][pSalt], 128, pwhash); // Wir erstellen einen salted Hash des Passwortes ( inputtext <=> Passwort )
if(strcmp(PInfo[playerid][pSaltedHash], pwhash, false) == 0)
SendClientMessage(playerid, 0xFF00FF, "Du hast dich eingeloggt");
else
SendClientMessage(playerid, 0xFFFF00, "Login falsch");
MD5
MD5 ist ein Algorithmus, der sich mittlerweile sehr durchgesetzt hat - wie alle anderen Hashfunktionen lässt auch er sich nicht wieder "zurückumwandeln", es ist aber auch hier möglich, den originalen Wert zu "erraten" ( dauert aber etwas ). MD5 gibt unabhängig von dem normalen Passwort einen 32 Byte langen String zurück der die Zeichen 0 - 9 und a - f / A - F hat.
Hier findet ihr eine Include von Y-Less, mit der ihr MD5 in SA:MP umsetzen könnt. Empfohlen wird aber auch hier, einen Salt hinzuzuschalten, da es für MD5 wesentlich größere Datenbanken gibt. Ein Beispiel ohne Salt währe folgendes: new hash[32];
hash = MD5_Hash("test"); Und mit dem Salt könnte man es wie folgt umsetzen: new salt[32];
new password[20 + 32]; // 20 Zeichen + 32 Zeichen vom Salt
new hash[32];
GenerateSalt(32, salt);
format(password, sizeof(password), "%s%s", inputtext, salt);
hash = MD5_Hash(password); Bei beiden Beispielen haben wir dann in der variable "Hash" den Hash, den wir eintragen. Im Zewiten brauchen wir zusätzlich noch den Salt, den wir speichern
Auf der Basis meiner Salt- und Hashsysteme unten, die ich übrigens selber geschrieben habe (deswegen hat das auch so lange gedauert ) könnt ihr übrigens eure eigenen Hashsysteme basteln
Dazu müsst ihr nur das Array erweitern und bei der CreateSaltedHash den 2.ten Operator der Modulorechnung anpassen
Ich hoffe, ich konnte euch der Passwortsicherheit ein Stücken näher bringen
Tion
Auch jetzt gilt, wie immer: Fragen sind zum Fragen da, Feedback ist erwünscht (heute auch mal negatives) und Ideen können eingebracht werden
public GenerateSalt(length, salt[])
{
new saltchars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
for(new i = 0; i < length - 1; i++)
{
salt[i] = saltchars[random(strlen(saltchars) - 2) + 1];
}
salt[length - 1] = 0;
return true;
}
public CreateSaltedHash(string[], salt[], length, dest[])
{
new hashchars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
new c1, c2;
for(new i = 0; i < length - 1; i++)
{
if(c1 >= strlen(string) - 1) c1 = 0;
if(c2 >= strlen(salt) - 1) c2 = 0;
dest[i] = hashchars[(string[c1] * salt[c2]) % 63];
c1++; c2++;
}
dest[length - 1] = 0;
} 100.000 Durchgänge brauchen bei einem 16-stelligen Salt und einem resultierendem 16-Stelligen Hash etwa 3 Sekunden.
Testscript: #include <a_samp>
#define HASH_LENGTH 16 // Wie lang soll der Hash sein ?
{
new password[20] = "Supersicheres PW";
new salt[SALT_LENGTH] = "hfgthdgtfhgbdgf";
new saltedpw[HASH_LENGTH];
new messungen = 100000;
new tmp = 0;
new gesammt = 0;
for(new i = 0; i < messungen; i+= 10)
{
tmp = GetTickCount();
GenerateSalt(SALT_LENGTH, salt);
CreateSaltedHash(password, salt, HASH_LENGTH, saltedpw);
GenerateSalt(SALT_LENGTH, salt);
CreateSaltedHash(password, salt, HASH_LENGTH, saltedpw);
GenerateSalt(SALT_LENGTH, salt);
CreateSaltedHash(password, salt, HASH_LENGTH, saltedpw);
GenerateSalt(SALT_LENGTH, salt);
CreateSaltedHash(password, salt, HASH_LENGTH, saltedpw);
GenerateSalt(SALT_LENGTH, salt);
CreateSaltedHash(password, salt, HASH_LENGTH, saltedpw);
GenerateSalt(SALT_LENGTH, salt);
CreateSaltedHash(password, salt, HASH_LENGTH, saltedpw);
GenerateSalt(SALT_LENGTH, salt);
CreateSaltedHash(password, salt, HASH_LENGTH, saltedpw);
GenerateSalt(SALT_LENGTH, salt);
CreateSaltedHash(password, salt, HASH_LENGTH, saltedpw);
GenerateSalt(SALT_LENGTH, salt);
CreateSaltedHash(password, salt, HASH_LENGTH, saltedpw);
GenerateSalt(SALT_LENGTH, salt);
CreateSaltedHash(password, salt, HASH_LENGTH, saltedpw);
gesammt += GetTickCount() - tmp;
}
printf("%50s%5d", "Messungen:", messungen);
printf("%50s%5d", "Gesammtzeit:", gesammt);
printf("%50s%5f", "Durchschnitt:", (gesammt / messungen));
return true;
}
public GenerateSalt(length, salt[])
{
new saltchars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
for(new i = 0; i < length - 1; i++)
{
salt[i] = saltchars[random(strlen(saltchars) - 2) + 1];
}
salt[length - 1] = 0;
return true;
}
public CreateSaltedHash(string[], salt[], length, dest[])
{
new hashchars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
new c1, c2;
for(new i = 0; i < length - 1; i++)
{
if(c1 >= strlen(string) - 1) c1 = 0;
if(c2 >= strlen(salt) - 1) c2 = 0;
dest[i] = hashchars[(string[c1] * salt[c2]) % 63];
c1++; c2++;
}
dest[length - 1] = 0;
}