Vorwort
Beginnend mit Ihrer Tabellendefinition:
- UserID
- Fname
- Lname
- Email
- Password
- IV
Hier sind die Änderungen:
- Die Felder
Fname
, Lname
und Email
werden verschlüsselt eine symmetrische Chiffre, bereitgestellt von OpenSSL ,
- Das
IV
Feld speichert den Initialisierungsvektor für die Verschlüsselung verwendeten . Die Speicheranforderungen hängen von der verwendeten Verschlüsselung und dem verwendeten Modus ab. dazu später mehr.
- Das
Password
Feld wird mit einem Einweg- Passwort-Hash gehasht.
Verschlüsselung
Chiffre und Modus
Die Auswahl der besten Verschlüsselung und des besten Verschlüsselungsmodus geht über den Rahmen dieser Antwort hinaus. Die endgültige Auswahl wirkt sich jedoch auf die Größe des Verschlüsselungsschlüssels und des Initialisierungsvektors aus. Für diesen Beitrag verwenden wir AES-256-CBC mit einer festen Blockgröße von 16 Byte und einer Schlüsselgröße von 16, 24 oder 32 Byte.
Verschlüsselungsschlüssel
Ein guter Verschlüsselungsschlüssel ist ein binärer Blob, der von einem zuverlässigen Zufallszahlengenerator generiert wird. Das folgende Beispiel wird empfohlen (> = 5,3):
$key_size = 32; // 256 bits
$encryption_key = openssl_random_pseudo_bytes($key_size, $strong);
// $strong will be true if the key is crypto safe
Dies kann ein- oder mehrmals erfolgen (wenn Sie eine Kette von Verschlüsselungsschlüsseln erstellen möchten). Halten Sie diese so privat wie möglich.
IV
Der Initialisierungsvektor fügt der Verschlüsselung Zufälligkeit hinzu und ist für den CBC-Modus erforderlich. Diese Werte sollten idealerweise nur einmal verwendet werden (technisch einmal pro Verschlüsselungsschlüssel), daher sollte eine Aktualisierung eines beliebigen Teils einer Zeile diese neu generieren.
Eine Funktion hilft Ihnen beim Generieren der IV:
$iv_size = 16; // 128 bits
$iv = openssl_random_pseudo_bytes($iv_size, $strong);
Beispiel
Verschlüsseln wir das Namensfeld mit dem früheren $encryption_key
und $iv
; Dazu müssen wir unsere Daten auf die Blockgröße auffüllen:
function pkcs7_pad($data, $size)
{
$length = $size - strlen($data) % $size;
return $data . str_repeat(chr($length), $length);
}
$name = 'Jack';
$enc_name = openssl_encrypt(
pkcs7_pad($name, 16), // padded data
'AES-256-CBC', // cipher and mode
$encryption_key, // secret key
0, // options (not used)
$iv // initialisation vector
);
Lagerungssansprüche
Die verschlüsselte Ausgabe ist wie die IV binär; Das Speichern dieser Werte in einer Datenbank kann unter Verwendung bestimmter Spaltentypen wie BINARY
oder erfolgenVARBINARY
.
Der Ausgabewert ist wie der IV binär; Um diese Werte in MySQL zu speichern, sollten Sie BINARY
oderVARBINARY
Spalten verwenden. Wenn dies keine Option ist, können Sie die Binärdaten auch mit base64_encode()
oder in eine Textdarstellung konvertieren. Dies bin2hex()
erfordert zwischen 33% und 100% mehr Speicherplatz.
Entschlüsselung
Die Entschlüsselung der gespeicherten Werte ist ähnlich:
function pkcs7_unpad($data)
{
return substr($data, 0, -ord($data[strlen($data) - 1]));
}
$row = $result->fetch(PDO::FETCH_ASSOC); // read from database result
// $enc_name = base64_decode($row['Name']);
// $enc_name = hex2bin($row['Name']);
$enc_name = $row['Name'];
// $iv = base64_decode($row['IV']);
// $iv = hex2bin($row['IV']);
$iv = $row['IV'];
$name = pkcs7_unpad(openssl_decrypt(
$enc_name,
'AES-256-CBC',
$encryption_key,
0,
$iv
));
Authentifizierte Verschlüsselung
Sie können die Integrität des generierten Chiffretextes weiter verbessern, indem Sie eine Signatur anhängen, die aus einem geheimen Schlüssel (der sich vom Verschlüsselungsschlüssel unterscheidet) und dem Chiffretext generiert wird. Bevor der Chiffretext entschlüsselt wird, wird zuerst die Signatur überprüft (vorzugsweise mit einer zeitkonstanten Vergleichsmethode).
Beispiel
// generate once, keep safe
$auth_key = openssl_random_pseudo_bytes(32, $strong);
// authentication
$auth = hash_hmac('sha256', $enc_name, $auth_key, true);
$auth_enc_name = $auth . $enc_name;
// verification
$auth = substr($auth_enc_name, 0, 32);
$enc_name = substr($auth_enc_name, 32);
$actual_auth = hash_hmac('sha256', $enc_name, $auth_key, true);
if (hash_equals($auth, $actual_auth)) {
// perform decryption
}
Siehe auch: hash_equals()
Hashing
Das Speichern eines umkehrbaren Passworts in Ihrer Datenbank muss so weit wie möglich vermieden werden. Sie möchten nur das Passwort überprüfen, anstatt dessen Inhalt zu kennen. Wenn ein Benutzer sein Passwort verliert, ist es besser, ihm das Zurücksetzen zu erlauben, als ihm sein ursprüngliches Passwort zu senden (stellen Sie sicher, dass das Zurücksetzen des Passworts nur für eine begrenzte Zeit möglich ist).
Das Anwenden einer Hash-Funktion ist eine Einwegoperation. Danach kann es sicher zur Überprüfung verwendet werden, ohne die Originaldaten preiszugeben. Für Passwörter ist eine Brute-Force-Methode aufgrund ihrer relativ kurzen Länge und der schlechten Passwortauswahl vieler Menschen ein praktikabler Ansatz, um sie aufzudecken.
Hashing-Algorithmen wie MD5 oder SHA1 wurden erstellt, um den Dateiinhalt anhand eines bekannten Hashwerts zu überprüfen. Sie sind stark optimiert, um diese Überprüfung so schnell wie möglich und dennoch genau durchzuführen. Aufgrund ihres relativ begrenzten Ausgabebereichs war es einfach, eine Datenbank mit bekannten Passwörtern und ihren jeweiligen Hash-Ausgaben, den Regenbogentabellen, zu erstellen.
Das Hinzufügen eines Salt zum Kennwort vor dem Hashing würde eine Regenbogentabelle unbrauchbar machen, aber die jüngsten Hardware-Fortschritte machten Brute-Force-Lookups zu einem praktikablen Ansatz. Deshalb benötigen Sie einen Hashing-Algorithmus, der bewusst langsam und einfach nicht zu optimieren ist. Es sollte auch in der Lage sein, die Last für schnellere Hardware zu erhöhen, ohne die Fähigkeit zu beeinträchtigen, vorhandene Kennwort-Hashes zu überprüfen, um sie zukunftssicher zu machen.
Derzeit stehen zwei beliebte Optionen zur Verfügung:
- PBKDF2 (Passwortbasierte Schlüsselableitungsfunktion v2)
- bcrypt (auch bekannt als Blowfish)
Diese Antwort verwendet ein Beispiel mit bcrypt.
Generation
Ein Passwort-Hash kann folgendermaßen generiert werden:
$password = 'my password';
$random = openssl_random_pseudo_bytes(18);
$salt = sprintf('$2y$%02d$%s',
13, // 2^n cost factor
substr(strtr(base64_encode($random), '+', '.'), 0, 22)
);
$hash = crypt($password, $salt);
Das Salz wird mit erzeugt openssl_random_pseudo_bytes()
, um einen zufälligen Datenklumpen zu bilden, der dann durchlaufen wird base64_encode()
und strtr()
mit dem erforderlichen Alphabet von übereinstimmt [A-Za-z0-9/.]
.
Die crypt()
Funktion führt das Hashing basierend auf dem Algorithmus durch ($2y$
für Blowfish), dem Kostenfaktor (ein Faktor von 13 dauert auf einer 3-GHz-Maschine ungefähr 0,40 Sekunden) und dem Salz von 22 Zeichen durch.
Validierung
Nachdem Sie die Zeile mit den Benutzerinformationen abgerufen haben, überprüfen Sie das Kennwort auf folgende Weise:
$given_password = $_POST['password']; // the submitted password
$db_hash = $row['Password']; // field with the password hash
$given_hash = crypt($given_password, $db_hash);
if (isEqual($given_hash, $db_hash)) {
// user password verified
}
// constant time string compare
function isEqual($str1, $str2)
{
$n1 = strlen($str1);
if (strlen($str2) != $n1) {
return false;
}
for ($i = 0, $diff = 0; $i != $n1; ++$i) {
$diff |= ord($str1[$i]) ^ ord($str2[$i]);
}
return !$diff;
}
Um ein Passwort zu überprüfen, rufen Sie crypt()
erneut an, übergeben jedoch den zuvor berechneten Hash als Salt-Wert. Der Rückgabewert ergibt den gleichen Hash, wenn das angegebene Passwort mit dem Hash übereinstimmt. Um den Hash zu überprüfen, wird häufig empfohlen, eine Vergleichsfunktion mit konstanter Zeit zu verwenden, um Timing-Angriffe zu vermeiden.
Passwort-Hashing mit PHP 5.5
PHP 5.5 führte die Passwort-Hashing-Funktionen ein , mit denen Sie die oben beschriebene Hashing-Methode vereinfachen können:
$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 13]);
Und überprüfen:
if (password_verify($given_password, $db_hash)) {
// password valid
}
Siehe auch : password_hash()
,password_verify()