Préface
En commençant par la définition de votre table:
- UserID
- Fname
- Lname
- Email
- Password
- IV
Voici les changements:
- Les champs
Fname
, Lname
et Email
seront cryptés à l'aide d'un chiffrement symétrique, fourni par OpenSSL ,
- Le
IV
champ stockera le vecteur d'initialisation utilisé pour le cryptage. Les exigences de stockage dépendent du chiffrement et du mode utilisés; plus à ce sujet plus tard.
- Le
Password
champ sera haché à l'aide d'un hachage de mot de passe à sens unique,
Chiffrement
Chiffre et mode
Le choix du meilleur chiffrement et du meilleur mode de chiffrement dépasse le cadre de cette réponse, mais le choix final affecte la taille de la clé de chiffrement et du vecteur d'initialisation; pour cet article, nous utiliserons AES-256-CBC qui a une taille de bloc fixe de 16 octets et une taille de clé de 16, 24 ou 32 octets.
Clé de cryptage
Une bonne clé de chiffrement est un objet blob binaire généré à partir d'un générateur de nombres aléatoires fiable. L'exemple suivant serait recommandé (> = 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
Cela peut être fait une ou plusieurs fois (si vous souhaitez créer une chaîne de clés de chiffrement). Gardez-les aussi privés que possible.
IV
Le vecteur d'initialisation ajoute un caractère aléatoire au cryptage et est requis pour le mode CBC. Ces valeurs devraient idéalement être utilisées une seule fois (techniquement une fois par clé de chiffrement), de sorte qu'une mise à jour de n'importe quelle partie d'une ligne devrait la régénérer.
Une fonction est fournie pour vous aider à générer l'IV:
$iv_size = 16; // 128 bits
$iv = openssl_random_pseudo_bytes($iv_size, $strong);
Exemple
Crypterons le champ de nom, en utilisant le précédent $encryption_key
et $iv
; pour ce faire, nous devons remplir nos données à la taille du bloc:
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
);
Exigences de stockage
La sortie cryptée, comme l'IV, est binaire; le stockage de ces valeurs dans une base de données peut être effectué en utilisant des types de colonnes désignés tels que BINARY
ou VARBINARY
.
La valeur de sortie, comme l'IV, est binaire; pour stocker ces valeurs dans MySQL, pensez à utiliser des colonnes BINARY
ouVARBINARY
. Si ce n'est pas une option, vous pouvez également convertir les données binaires en une représentation textuelle à l'aide de base64_encode()
ou bin2hex()
, cela nécessite entre 33% et 100% d'espace de stockage supplémentaire.
Décryptage
Le déchiffrement des valeurs stockées est similaire:
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
));
Cryptage authentifié
Vous pouvez améliorer encore l'intégrité du texte chiffré généré en ajoutant une signature générée à partir d'une clé secrète (différente de la clé de chiffrement) et du texte chiffré. Avant que le texte chiffré ne soit déchiffré, la signature est d'abord vérifiée (de préférence avec une méthode de comparaison à temps constant).
Exemple
// 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
}
Voir également: hash_equals()
Hashing
Le stockage d'un mot de passe réversible dans votre base de données doit être évité autant que possible; vous souhaitez uniquement vérifier le mot de passe plutôt que de connaître son contenu. Si un utilisateur perd son mot de passe, il est préférable de lui permettre de le réinitialiser plutôt que de lui envoyer son mot de passe d'origine (assurez-vous que la réinitialisation du mot de passe ne peut être effectuée que pour une durée limitée).
L'application d'une fonction de hachage est une opération unidirectionnelle; ensuite, il peut être utilisé en toute sécurité pour la vérification sans révéler les données originales; pour les mots de passe, une méthode de force brute est une approche réalisable pour le découvrir en raison de sa longueur relativement courte et des mauvais choix de mots de passe de nombreuses personnes.
Des algorithmes de hachage tels que MD5 ou SHA1 ont été créés pour vérifier le contenu du fichier par rapport à une valeur de hachage connue. Ils sont grandement optimisés pour rendre cette vérification aussi rapide que possible tout en restant précise. Compte tenu de leur espace de sortie relativement limité, il était facile de créer une base de données avec des mots de passe connus et leurs sorties de hachage respectives, les tables arc-en-ciel.
Ajouter un sel au mot de passe avant le hachage rendrait une table arc-en-ciel inutile, mais les récentes avancées matérielles ont fait des recherches par force brute une approche viable. C'est pourquoi vous avez besoin d'un algorithme de hachage délibérément lent et tout simplement impossible à optimiser. Il devrait également être en mesure d'augmenter la charge d'un matériel plus rapide sans affecter la capacité de vérifier les hachages de mots de passe existants pour le rendre à l'épreuve du temps.
Actuellement, il existe deux choix populaires disponibles:
- PBKDF2 (fonction de dérivation de clé basée sur le mot de passe v2)
- bcrypt (alias Blowfish)
Cette réponse utilisera un exemple avec bcrypt.
Génération
Un hachage de mot de passe peut être généré comme ceci:
$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);
Le sel est généré avec openssl_random_pseudo_bytes()
pour former une goutte aléatoire de données qui est ensuite parcourue base64_encode()
et strtr()
pour correspondre à l'alphabet requis de [A-Za-z0-9/.]
.
La crypt()
fonction effectue le hachage en fonction de l'algorithme ( $2y$
pour Blowfish), du facteur de coût (un facteur de 13 prend environ 0,40s sur une machine 3GHz) et du sel de 22 caractères.
Validation
Une fois que vous avez récupéré la ligne contenant les informations utilisateur, vous validez le mot de passe de cette manière:
$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;
}
Pour vérifier un mot de passe, vous appelez à crypt()
nouveau mais vous transmettez le hachage précédemment calculé comme valeur de sel. La valeur de retour donne le même hachage si le mot de passe donné correspond au hachage. Pour vérifier le hachage, il est souvent recommandé d'utiliser une fonction de comparaison à temps constant pour éviter les attaques de synchronisation.
Hachage de mot de passe avec PHP 5.5
PHP 5.5 a introduit les fonctions de hachage de mot de passe que vous pouvez utiliser pour simplifier la méthode de hachage ci-dessus:
$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 13]);
Et vérifier:
if (password_verify($given_password, $db_hash)) {
// password valid
}
Voir aussi: password_hash()
,password_verify()