Persönlich würde ich mcrypt
gerne andere posten. Aber es gibt noch viel mehr zu beachten ...
Wie verschlüssele und entschlüssele ich ein Passwort in PHP?
Unten finden Sie eine starke Klasse, die sich um alles für Sie kümmert:
Was ist der sicherste Algorithmus zum Verschlüsseln der Passwörter?
am sichersten ? jeder von ihnen. Die sicherste Methode zum Verschlüsseln ist der Schutz vor Sicherheitslücken bei der Offenlegung von Informationen (XSS, Remote Inclusion usw.). Wenn es herauskommt, kann der Angreifer möglicherweise die Verschlüsselung knacken (keine Verschlüsselung ist ohne den Schlüssel zu 100% nicht umkehrbar - Wie @NullUserException hervorhebt, ist dies nicht ganz richtig. Es gibt einige Verschlüsselungsschemata, die nicht geknackt werden können, wie z. B. OneTimePad ). .
Wo speichere ich den privaten Schlüssel?
Was ich tun würde, ist 3 Schlüssel zu verwenden. Einer ist vom Benutzer bereitgestellt, einer ist anwendungsspezifisch und der andere ist benutzerspezifisch (wie ein Salz). Der anwendungsspezifische Schlüssel kann überall gespeichert werden (in einer Konfigurationsdatei außerhalb des Webstamms, in einer Umgebungsvariablen usw.). Das benutzerspezifische wird in einer Spalte in der Datenbank neben dem verschlüsselten Passwort gespeichert. Der vom Benutzer angegebene wird nicht gespeichert. Dann würden Sie so etwas tun:
$key = $userKey . $serverKey . $userSuppliedKey;
Der Vorteil dort ist, dass 2 beliebige Schlüssel kompromittiert werden können, ohne dass die Daten kompromittiert werden. Wenn es einen SQL Injection-Angriff gibt, können sie den $userKey
, aber nicht den anderen 2 erhalten. Wenn es einen lokalen Server-Exploit gibt, können sie $userKey
und $serverKey
, aber nicht den dritten $userSuppliedKey
. Wenn sie den Benutzer mit einem Schraubenschlüssel schlagen, können sie die $userSuppliedKey
, aber nicht die anderen 2 bekommen (aber wenn der Benutzer mit einem Schraubenschlüssel geschlagen wird, sind Sie sowieso zu spät).
Ist es eine gute Idee, Benutzer zu verpflichten, den privaten Schlüssel jedes Mal einzugeben, wenn sie ein entschlüsseltes Kennwort benötigen, anstatt den privaten Schlüssel zu speichern? (Benutzern dieser Anwendung kann vertraut werden)
Absolut. In der Tat ist das der einzige Weg, wie ich es tun würde. Andernfalls müssten Sie eine unverschlüsselte Version in einem dauerhaften Speicherformat (gemeinsam genutzter Speicher wie APC oder Memcached oder in einer Sitzungsdatei) speichern. Das setzt Sie zusätzlichen Kompromissen aus. Speichern Sie die unverschlüsselte Version des Passworts niemals in einer anderen als einer lokalen Variablen.
Wie kann das Passwort gestohlen und entschlüsselt werden? Was muss ich beachten?
Bei jeder Form von Kompromittierung Ihrer Systeme können sie verschlüsselte Daten anzeigen. Wenn sie Code einfügen oder in Ihr Dateisystem gelangen können, können sie entschlüsselte Daten anzeigen (da sie die Dateien bearbeiten können, die die Daten entschlüsseln). Jede Form von Replay oder MITM-Angriff ermöglicht ihnen auch den vollen Zugriff auf die beteiligten Schlüssel. Wenn Sie den rohen HTTP-Verkehr abhören, erhalten Sie auch die Schlüssel.
Verwenden Sie SSL für den gesamten Datenverkehr. Stellen Sie außerdem sicher, dass auf dem Server keinerlei Sicherheitslücken bestehen (CSRF, XSS, SQL Injection, Eskalation von Berechtigungen, Remotecodeausführung usw.).
Bearbeiten: Hier ist eine PHP-Klassenimplementierung einer starken Verschlüsselungsmethode:
/**
* A class to handle secure encryption and decryption of arbitrary data
*
* Note that this is not just straight encryption. It also has a few other
* features in it to make the encrypted data far more secure. Note that any
* other implementations used to decrypt data will have to do the same exact
* operations.
*
* Security Benefits:
*
* - Uses Key stretching
* - Hides the Initialization Vector
* - Does HMAC verification of source data
*
*/
class Encryption {
/**
* @var string $cipher The mcrypt cipher to use for this instance
*/
protected $cipher = '';
/**
* @var int $mode The mcrypt cipher mode to use
*/
protected $mode = '';
/**
* @var int $rounds The number of rounds to feed into PBKDF2 for key generation
*/
protected $rounds = 100;
/**
* Constructor!
*
* @param string $cipher The MCRYPT_* cypher to use for this instance
* @param int $mode The MCRYPT_MODE_* mode to use for this instance
* @param int $rounds The number of PBKDF2 rounds to do on the key
*/
public function __construct($cipher, $mode, $rounds = 100) {
$this->cipher = $cipher;
$this->mode = $mode;
$this->rounds = (int) $rounds;
}
/**
* Decrypt the data with the provided key
*
* @param string $data The encrypted datat to decrypt
* @param string $key The key to use for decryption
*
* @returns string|false The returned string if decryption is successful
* false if it is not
*/
public function decrypt($data, $key) {
$salt = substr($data, 0, 128);
$enc = substr($data, 128, -64);
$mac = substr($data, -64);
list ($cipherKey, $macKey, $iv) = $this->getKeys($salt, $key);
if (!hash_equals(hash_hmac('sha512', $enc, $macKey, true), $mac)) {
return false;
}
$dec = mcrypt_decrypt($this->cipher, $cipherKey, $enc, $this->mode, $iv);
$data = $this->unpad($dec);
return $data;
}
/**
* Encrypt the supplied data using the supplied key
*
* @param string $data The data to encrypt
* @param string $key The key to encrypt with
*
* @returns string The encrypted data
*/
public function encrypt($data, $key) {
$salt = mcrypt_create_iv(128, MCRYPT_DEV_URANDOM);
list ($cipherKey, $macKey, $iv) = $this->getKeys($salt, $key);
$data = $this->pad($data);
$enc = mcrypt_encrypt($this->cipher, $cipherKey, $data, $this->mode, $iv);
$mac = hash_hmac('sha512', $enc, $macKey, true);
return $salt . $enc . $mac;
}
/**
* Generates a set of keys given a random salt and a master key
*
* @param string $salt A random string to change the keys each encryption
* @param string $key The supplied key to encrypt with
*
* @returns array An array of keys (a cipher key, a mac key, and a IV)
*/
protected function getKeys($salt, $key) {
$ivSize = mcrypt_get_iv_size($this->cipher, $this->mode);
$keySize = mcrypt_get_key_size($this->cipher, $this->mode);
$length = 2 * $keySize + $ivSize;
$key = $this->pbkdf2('sha512', $key, $salt, $this->rounds, $length);
$cipherKey = substr($key, 0, $keySize);
$macKey = substr($key, $keySize, $keySize);
$iv = substr($key, 2 * $keySize);
return array($cipherKey, $macKey, $iv);
}
/**
* Stretch the key using the PBKDF2 algorithm
*
* @see http://en.wikipedia.org/wiki/PBKDF2
*
* @param string $algo The algorithm to use
* @param string $key The key to stretch
* @param string $salt A random salt
* @param int $rounds The number of rounds to derive
* @param int $length The length of the output key
*
* @returns string The derived key.
*/
protected function pbkdf2($algo, $key, $salt, $rounds, $length) {
$size = strlen(hash($algo, '', true));
$len = ceil($length / $size);
$result = '';
for ($i = 1; $i <= $len; $i++) {
$tmp = hash_hmac($algo, $salt . pack('N', $i), $key, true);
$res = $tmp;
for ($j = 1; $j < $rounds; $j++) {
$tmp = hash_hmac($algo, $tmp, $key, true);
$res ^= $tmp;
}
$result .= $res;
}
return substr($result, 0, $length);
}
protected function pad($data) {
$length = mcrypt_get_block_size($this->cipher, $this->mode);
$padAmount = $length - strlen($data) % $length;
if ($padAmount == 0) {
$padAmount = $length;
}
return $data . str_repeat(chr($padAmount), $padAmount);
}
protected function unpad($data) {
$length = mcrypt_get_block_size($this->cipher, $this->mode);
$last = ord($data[strlen($data) - 1]);
if ($last > $length) return false;
if (substr($data, -1 * $last) !== str_repeat(chr($last), $last)) {
return false;
}
return substr($data, 0, -1 * $last);
}
}
Beachten Sie, dass ich eine in PHP 5.6 hinzugefügte Funktion verwende : hash_equals
. Wenn Sie weniger als 5,6 verwenden, können Sie diese Ersatzfunktion verwenden, die eine zeitsichere Vergleichsfunktion mit doppelter HMAC-Überprüfung implementiert :
function hash_equals($a, $b) {
$key = mcrypt_create_iv(128, MCRYPT_DEV_URANDOM);
return hash_hmac('sha512', $a, $key) === hash_hmac('sha512', $b, $key);
}
Verwendung:
$e = new Encryption(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$encryptedData = $e->encrypt($data, $key);
Dann, um zu entschlüsseln:
$e2 = new Encryption(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$data = $e2->decrypt($encryptedData, $key);
Beachten Sie, dass ich $e2
das zweite Mal verwendet habe, um Ihnen zu zeigen, dass verschiedene Instanzen die Daten immer noch ordnungsgemäß entschlüsseln.
Wie funktioniert es nun / warum wird es über einer anderen Lösung verwendet:
Schlüssel
Die Schlüssel werden nicht direkt verwendet. Stattdessen wird der Schlüssel durch eine Standard-PBKDF2-Ableitung gestreckt.
Der für die Verschlüsselung verwendete Schlüssel ist für jeden verschlüsselten Textblock eindeutig. Der mitgelieferte Schlüssel wird somit zu einem "Hauptschlüssel". Diese Klasse bietet daher eine Schlüsselrotation für Verschlüsselungs- und Authentifizierungsschlüssel.
WICHTIGER HINWEIS : Der $rounds
Parameter ist für echte Zufallsschlüssel mit ausreichender Stärke konfiguriert (mindestens 128 Bit Cryptographically Secure Random). Wenn Sie ein Kennwort oder einen nicht zufälligen Schlüssel (oder weniger zufällig als 128 Bit CS-Zufall) verwenden möchten, müssen Sie diesen Parameter erhöhen. Ich würde ein Minimum von 10000 für Passwörter vorschlagen (je mehr Sie sich leisten können, desto besser, aber es erhöht die Laufzeit) ...
Datenintegrität
- Die aktualisierte Version verwendet ENCRYPT-THEN-MAC, eine weitaus bessere Methode, um die Authentizität der verschlüsselten Daten sicherzustellen.
Verschlüsselung:
- Es verwendet mcrypt, um die Verschlüsselung tatsächlich durchzuführen. Ich würde vorschlagen, entweder
MCRYPT_BLOWFISH
oder MCRYPT_RIJNDAEL_128
Chiffren und MCRYPT_MODE_CBC
für den Modus zu verwenden. Es ist stark genug und immer noch ziemlich schnell (ein Verschlüsselungs- und Entschlüsselungszyklus dauert auf meinem Computer ungefähr eine halbe Sekunde).
Was nun Punkt 3 aus der ersten Liste betrifft, so würde Ihnen dies eine Funktion wie diese geben:
function makeKey($userKey, $serverKey, $userSuppliedKey) {
$key = hash_hmac('sha512', $userKey, $serverKey);
$key = hash_hmac('sha512', $key, $userSuppliedKey);
return $key;
}
Sie könnten es in der makeKey()
Funktion dehnen , aber da es später gedehnt wird, ist es nicht wirklich sinnvoll, dies zu tun.
Die Speichergröße hängt vom Klartext ab. Blowfish verwendet eine Blockgröße von 8 Byte, sodass Sie Folgendes haben:
- 16 Bytes für das Salz
- 64 Bytes für den hmac
- Datenlänge
- Auffüllen, damit die Datenlänge% 8 == 0 ist
Für eine 16-stellige Datenquelle müssen also 16 Zeichen Daten verschlüsselt werden. Das bedeutet, dass die tatsächliche Größe der verschlüsselten Daten aufgrund des Auffüllens 16 Byte beträgt. Fügen Sie dann die 16 Bytes für das Salt und 64 Bytes für den hmac hinzu, und die insgesamt gespeicherte Größe beträgt 96 Bytes. Es gibt also bestenfalls einen Overhead von 80 Zeichen und im schlimmsten Fall einen Overhead von 87 Zeichen ...
Ich hoffe das hilft...
Hinweis: 11.12.12: Ich habe diese Klasse gerade mit einer VIEL besseren Verschlüsselungsmethode aktualisiert, indem ich besser abgeleitete Schlüssel verwendet und die MAC-Generierung korrigiert habe ...