Personnellement, j'utiliserais mcrypt
comme d'autres postés. Mais il y a beaucoup plus à noter ...
Comment crypter et décrypter un mot de passe en PHP?
Voir ci-dessous pour une classe forte qui s'occupe de tout pour vous:
Quel est l'algorithme le plus sûr pour crypter les mots de passe?
le plus sûr ? n'importe lequel d'entre eux. La méthode la plus sûre si vous allez chiffrer est de vous protéger contre les vulnérabilités de divulgation d'informations (XSS, inclusion à distance, etc.). S'il sort, l'attaquant peut éventuellement déchiffrer le chiffrement (aucun chiffrement n'est 100% irréversible sans la clé - comme le souligne @NullUserException, ce n'est pas tout à fait vrai. Il existe des schémas de chiffrement impossibles à déchiffrer comme OneTimePad ) .
Où stocker la clé privée?
Ce que je ferais, c'est utiliser 3 clés. L'un est fourni par l'utilisateur, l'un est spécifique à l'application et l'autre est spécifique à l'utilisateur (comme un sel). La clé spécifique à l'application peut être stockée n'importe où (dans un fichier de configuration en dehors de la racine Web, dans une variable d'environnement, etc.). Celui spécifique à l'utilisateur serait stocké dans une colonne de la base de données à côté du mot de passe crypté. Celui fourni par l'utilisateur ne serait pas stocké. Ensuite, vous feriez quelque chose comme ceci:
$key = $userKey . $serverKey . $userSuppliedKey;
L'avantage ici, c'est que 2 des clés peuvent être compromises sans que les données soient compromises. S'il y a une attaque par injection SQL, ils peuvent obtenir le $userKey
, mais pas l'autre 2. S'il y a un exploit de serveur local, ils peuvent obtenir $userKey
et $serverKey
, mais pas le troisième $userSuppliedKey
. S'ils battent l'utilisateur avec une clé, ils peuvent obtenir les $userSuppliedKey
2 autres, mais pas les 2 autres (mais encore une fois, si l'utilisateur est battu avec une clé, vous êtes de toute façon trop tard).
Au lieu de stocker la clé privée, est-ce une bonne idée de demander aux utilisateurs de saisir la clé privée chaque fois qu'ils ont besoin d'un mot de passe déchiffré? (Les utilisateurs de cette application peuvent être dignes de confiance)
Absolument. En fait, c'est la seule façon dont je le ferais. Sinon, vous devrez stocker une version non chiffrée dans un format de stockage durable (mémoire partagée comme APC ou Memcached, ou dans un fichier de session). Cela vous expose à des compromis supplémentaires. Ne stockez jamais la version non chiffrée du mot de passe dans autre chose qu'une variable locale.
De quelles manières le mot de passe peut-il être volé et déchiffré? De quoi ai-je besoin d'être conscient?
Toute forme de compromission de vos systèmes leur permettra de visualiser les données cryptées. S'ils peuvent injecter du code ou accéder à votre système de fichiers, ils peuvent afficher les données déchiffrées (car ils peuvent éditer les fichiers qui déchiffrent les données). Toute forme d'attaque Replay ou MITM leur donnera également un accès complet aux clés impliquées. Renifler le trafic HTTP brut leur donnera également les clés.
Utilisez SSL pour tout le trafic. Et assurez-vous que rien sur le serveur ne présente de vulnérabilités (CSRF, XSS, injection SQL, escalade de privilèges, exécution de code à distance, etc.).
Edit: Voici une implémentation de classe PHP d'une méthode de cryptage forte:
/**
* 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);
}
}
Notez que je suis en utilisant une fonction ajoutée en PHP 5.6: hash_equals
. Si vous êtes sur une valeur inférieure à 5,6, vous pouvez utiliser cette fonction de remplacement qui implémente une fonction de comparaison sécurisée en fonction du temps en utilisant la double vérification HMAC :
function hash_equals($a, $b) {
$key = mcrypt_create_iv(128, MCRYPT_DEV_URANDOM);
return hash_hmac('sha512', $a, $key) === hash_hmac('sha512', $b, $key);
}
Usage:
$e = new Encryption(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$encryptedData = $e->encrypt($data, $key);
Ensuite, pour décrypter:
$e2 = new Encryption(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$data = $e2->decrypt($encryptedData, $key);
Notez que j'ai utilisé $e2
la deuxième fois pour vous montrer que différentes instances déchiffreront toujours correctement les données.
Maintenant, comment ça marche / pourquoi l'utiliser sur une autre solution:
Clés
Les clés ne sont pas utilisées directement. Au lieu de cela, la clé est étirée par une dérivation PBKDF2 standard.
La clé utilisée pour le chiffrement est unique pour chaque bloc de texte chiffré. La clé fournie devient donc une "clé principale". Cette classe fournit donc une rotation de clé pour les clés de chiffrement et d'authentification.
REMARQUE IMPORTANTE , le $rounds
paramètre est configuré pour de vraies clés aléatoires de force suffisante (128 bits de cryptographie aléatoire au minimum). Si vous allez utiliser un mot de passe, ou une clé non aléatoire (ou moins aléatoire que 128 bits de CS aléatoire), vous devez augmenter ce paramètre. Je suggérerais un minimum de 10000 pour les mots de passe (plus vous pouvez vous le permettre, mieux c'est, mais cela ajoutera à l'exécution) ...
Intégrité des données
- La version mise à jour utilise ENCRYPT-THEN-MAC, qui est une méthode bien meilleure pour garantir l'authenticité des données cryptées.
Chiffrement:
- Il utilise mcrypt pour effectuer le cryptage. Je suggérerais d'utiliser soit
MCRYPT_BLOWFISH
ou MCRYPT_RIJNDAEL_128
cyphers et MCRYPT_MODE_CBC
pour le mode. C'est assez fort, et toujours assez rapide (un cycle de cryptage et de décryptage prend environ 1/2 seconde sur ma machine).
Maintenant, en ce qui concerne le point 3 de la première liste, ce que cela vous donnerait est une fonction comme celle-ci:
function makeKey($userKey, $serverKey, $userSuppliedKey) {
$key = hash_hmac('sha512', $userKey, $serverKey);
$key = hash_hmac('sha512', $key, $userSuppliedKey);
return $key;
}
Vous pouvez l'étirer dans la makeKey()
fonction, mais comme il sera étiré plus tard, il n'y a pas vraiment d'intérêt à le faire.
En ce qui concerne la taille de stockage, cela dépend du texte brut. Blowfish utilise une taille de bloc de 8 octets, vous aurez donc:
- 16 octets pour le sel
- 64 octets pour le hmac
- longueur des données
- Remplissage pour que la longueur des données% 8 == 0
Donc, pour une source de données à 16 caractères, il y aura 16 caractères de données à chiffrer. Cela signifie donc que la taille réelle des données chiffrées est de 16 octets en raison du remplissage. Ajoutez ensuite les 16 octets pour le sel et 64 octets pour le hmac et la taille totale stockée est de 96 octets. Il y a donc au mieux un overhead de 80 caractères, et au pire un overhead de 87 caractères ...
J'espère que cela aide ...
Remarque: 12/11/12: Je viens de mettre à jour cette classe avec une méthode de cryptage BEAUCOUP meilleure, en utilisant de meilleures clés dérivées et en corrigeant la génération MAC ...