Comment puis-je stocker les mots de passe de mes utilisateurs en toute sécurité?

169

À quel point est-ce plus sûr que le MD5 ordinaire ? Je viens de commencer à me pencher sur la sécurité des mots de passe. Je suis assez nouveau en PHP.

$salt = 'csdnfgksdgojnmfnb';

$password = md5($salt.$_POST['password']);
$result = mysql_query("SELECT id FROM users
                       WHERE username = '".mysql_real_escape_string($_POST['username'])."'
                       AND password = '$password'");

if (mysql_num_rows($result) < 1) {
    /* Access denied */
    echo "The username or password you entered is incorrect.";
} 
else {
    $_SESSION['id'] = mysql_result($result, 0, 'id');
    #header("Location: ./");
    echo "Hello $_SESSION[id]!";
}
Rebar
la source
Remarque php 5.4+ a cela intégré
Benjamin Gruenbaum
Voir également le framework de hachage de mot de passe PHP d'Openwall (PHPass). Il est portable et renforcé contre un certain nombre d'attaques courantes sur les mots de passe des utilisateurs.
jww
1
Obligatoire "utiliser des AOP au lieu de l'interpolation de chaîne", pour les gens qui trébuchent sur cette question aujourd'hui.
Fund Monica's Lawsuit

Réponses:

270

Le moyen le plus simple de sécuriser votre schéma de stockage de mots de passe consiste à utiliser une bibliothèque standard .

Parce que la sécurité a tendance à être beaucoup plus compliquée et avec plus de possibilités de vissage invisibles que la plupart des programmeurs ne pourraient résoudre seuls, l'utilisation d'une bibliothèque standard est presque toujours l'option disponible la plus simple et la plus sûre (sinon la seule).


La nouvelle API de mot de passe PHP (5.5.0+)

Si vous utilisez PHP version 5.5.0 ou plus récente, vous pouvez utiliser la nouvelle API de hachage de mot de passe simplifiée

Exemple de code utilisant l'API de mot de passe de PHP:

<?php
// $hash is what you would store in your database
$hash = password_hash($_POST['password'], PASSWORD_DEFAULT, ['cost' => 12]);

// $hash would be the $hash (above) stored in your database for this user
$checked = password_verify($_POST['password'], $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}

(Si vous utilisez toujours l'ancienne version 5.3.7 ou plus récente, vous pouvez installer ircmaxell / password_compat pour avoir accès aux fonctions intégrées)


Amélioration des hashes salés: ajoutez du poivre

Si vous voulez une sécurité supplémentaire, les responsables de la sécurité recommandent maintenant (2017) d'ajouter un `` poivre '' aux hachages de mot de passe (automatiquement) salés.

Il existe une classe simple qui implémente en toute sécurité ce modèle, je recommande: Netsilik / PepperedPasswords ( github ).
Il est livré avec une licence MIT, vous pouvez donc l'utiliser comme vous le souhaitez, même dans des projets propriétaires.

Exemple de code utilisant Netsilik/PepperedPasswords:

<?php
use Netsilik/Lib/PepperedPasswords;

// Some long, random, binary string, encoded as hexadecimal; stored in your configuration (NOT in your Database, as that would defeat the entire purpose of the pepper).
$config['pepper'] = hex2bin('012345679ABCDEF012345679ABCDEF012345679ABCDEF012345679ABCDEF');

$hasher = new PepperedPasswords($config['pepper']);

// $hash is what you would store in your database
$hash = $hasher->hash($_POST['password']);

// $hash would be the $hash (above) stored in your database for this user
$checked = $hasher->verify($_POST['password'], $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}


La bibliothèque standard ANCIENNE

Remarque: vous ne devriez plus en avoir besoin! Ce n'est ici qu'à des fins historiques.

Jetez un oeil à: Framework de hachage de mot de passe PHP portable : phpass et assurez-vous d'utiliser l' CRYPT_BLOWFISHalgorithme si possible.

Exemple de code utilisant phpass (v0.2):

<?php
require('PasswordHash.php');

$pwdHasher = new PasswordHash(8, FALSE);

// $hash is what you would store in your database
$hash = $pwdHasher->HashPassword( $password );

// $hash would be the $hash (above) stored in your database for this user
$checked = $pwdHasher->CheckPassword($password, $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}

PHPass a été implémenté dans certains projets assez connus:

  • phpBB3
  • WordPress 2.5+ ainsi que bbPress
  • la version Drupal 7, (module disponible pour Drupal 5 & 6)
  • autres

La bonne chose est que vous n'avez pas à vous soucier des détails, ces détails ont été programmés par des personnes expérimentées et examinés par de nombreuses personnes sur Internet.

Pour plus d'informations sur les schémas de stockage des mots de passe, lisez l' article de blog de Jeff : Vous stockez probablement les mots de passe de manière incorrecte

Quoi que vous fassiez si vous optez pour l' approche « je le ferai moi-même, merci », ne l'utilisez plus MD5ou SHA1plus . Ce sont de bons algorithmes de hachage, mais considérés comme défectueux pour des raisons de sécurité .

Actuellement, l'utilisation de crypt , avec CRYPT_BLOWFISH est la meilleure pratique.
CRYPT_BLOWFISH en PHP est une implémentation du hachage Bcrypt. Bcrypt est basé sur le chiffrement par bloc Blowfish, utilisant sa configuration de clé coûteuse pour ralentir l'algorithme.

Jacco
la source
29

Vos utilisateurs seront beaucoup plus en sécurité si vous utilisez des requêtes paramétrées au lieu de concaténer des instructions SQL. Et le sel doit être unique pour chaque utilisateur et doit être stocké avec le hachage du mot de passe.

Anton Gogolev
la source
1
Il y a un bon article sur la sécurité en PHP chez Nettuts +, le salage de mot de passe est également mentionné. Vous devriez peut-être jeter un œil à: net.tutsplus.com/tutorials/php/…
Fábio Antunes
3
Le Nettuts + est un très mauvais article à utiliser comme modèle - il inclut l'utilisation de MD5 qui peut être forcé très facilement même avec du sel. Au lieu de cela, utilisez simplement la bibliothèque PHPass qui est de loin, bien meilleure que n'importe quel code que vous pouvez trouver sur un site de didacticiel, c'est-à-dire cette réponse: stackoverflow.com/questions/1581610/…
RichVel
11

Une meilleure façon serait pour chaque utilisateur d'avoir un sel unique.

L'avantage d'avoir un sel est qu'il est plus difficile pour un attaquant de pré-générer la signature MD5 de chaque mot du dictionnaire. Mais si un attaquant apprend que vous avez un sel fixe, il pourrait alors pré-générer la signature MD5 de chaque mot du dictionnaire préfixé par votre sel fixe.

Une meilleure façon est que chaque fois qu'un utilisateur change son mot de passe, votre système génère un sel aléatoire et stocke ce sel avec l'enregistrement de l'utilisateur. Cela rend un peu plus cher la vérification du mot de passe (puisque vous devez rechercher le sel avant de pouvoir générer la signature MD5), mais cela rend beaucoup plus difficile pour un attaquant de pré-générer des MD5.

R Samuel Klatchko
la source
3
Les sels sont généralement stockés avec le hachage du mot de passe (par exemple, la sortie de la crypt()fonction). Et comme vous devez de toute façon récupérer le hachage du mot de passe, l'utilisation d'un sel spécifique à l'utilisateur ne rendra pas la procédure plus coûteuse. (Ou vouliez-vous dire que générer un nouveau sel aléatoire coûte cher? Je ne pense pas vraiment.) Sinon +1.
Inshallah
Pour des raisons de sécurité, vous souhaiterez peut-être fournir l'accès à la table uniquement via des procédures stockées et empêcher le hachage d'être renvoyé. Au lieu de cela, le client transmet ce qu'il pense être le hachage et obtient un indicateur de réussite ou d'échec. Cela permet au processus stocké d'enregistrer la tentative, de créer une session, etc.
Steven Sudit
@Inshallah - si tous les utilisateurs ont le même sel, vous pouvez réutiliser l'attaque par dictionnaire que vous utilisez sur user1 contre user2. Mais si chaque utilisateur a un sel unique, vous devrez générer un nouveau dictionnaire pour chaque utilisateur que vous souhaitez attaquer.
R Samuel Klatchko
@R Samuel - c'est exactement pourquoi j'ai voté pour votre réponse, car elle recommande la stratégie des meilleures pratiques pour éviter de telles attaques. Mon commentaire visait à exprimer ma perplexité quant à ce que vous avez dit concernant le coût supplémentaire d'un sel par utilisateur, ce que je ne comprenais pas du tout. (puisque "les sels sont généralement stockés avec le hachage du mot de passe", tout stockage supplémentaire et les exigences de processeur pour un sel par utilisateur sont si microscopiques qu'ils n'ont même pas besoin d'être mentionnés ...)
Inshallah
@Inshallah - Je pensais au cas où vous avez vérifié la base de données si le mot de passe haché est correct (alors vous avez une récupération de base de données pour obtenir le sel et un deuxième accès à la base de données pour vérifier le mot de passe haché). Vous avez raison sur le cas où vous téléchargez le mot de passe salt / haché en une seule récupération, puis faites la comparaison sur le client. Désolé pour la confusion.
R Samuel Klatchko
11

Avec PHP 5.5 (ce que je décris est disponible pour des versions encore plus anciennes, voir ci-dessous) au coin de la rue, j'aimerais suggérer d'utiliser sa nouvelle solution intégrée: password_hash()et password_verify(). Il propose plusieurs options pour atteindre le niveau de sécurité des mots de passe dont vous avez besoin (par exemple en spécifiant un paramètre «cost» via le $optionstableau)

<?php
var_dump(password_hash("my-secret-password", PASSWORD_DEFAULT));

$options = array(
    'cost' => 7, // this is the number of rounds for bcrypt
    // 'salt' => 'TphfsM82o1uEKlfP9vf1f', // you could specify a salt but it is not recommended
);
var_dump(password_hash("my-secret-password", PASSWORD_BCRYPT, $options));
?>

reviendra

string(60) "$2y$10$w2LxXdIcqJpD6idFTNn.eeZbKesdu5y41ksL22iI8C4/6EweI7OK."
string(60) "$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d."

Comme vous pouvez le voir, la chaîne contient le sel ainsi que le coût spécifié dans les options. Il contient également l'algorithme utilisé.

Par conséquent, lors de la vérification du mot de passe (par exemple lorsque l'utilisateur se connecte), lors de l'utilisation de la password_verify()fonction complémentaire , il extraira les paramètres cryptographiques nécessaires du hachage du mot de passe lui-même.

Lorsque vous ne spécifiez pas de sel, le hachage du mot de passe généré sera différent à chaque appel de password_hash()car le sel est généré de manière aléatoire. Par conséquent, la comparaison d'un hachage précédent avec un hachage nouvellement généré échouera, même pour un mot de passe correct.

La vérification fonctionne comme ceci:

var_dump(password_verify("my-secret-password", '$2y$10$BjHJbMCNWIJq7xiAeyFaHOGaO0jjNoE11e0YAer6Zu01OZHN/gk6K'));
var_dump(password_verify("wrong-password", '$2y$10$BjHJbMCNWIJq7xiAeyFaHOGaO0jjNoE11e0YAer6Zu01OZHN/gk6K'));

var_dump(password_verify("my-secret-password", '$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d.'));
var_dump(password_verify("wrong-password", '$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d.'));

J'espère que fournir ces fonctions intégrées fournira bientôt une meilleure sécurité par mot de passe en cas de vol de données, car cela réduit la quantité de réflexion que le programmeur doit consacrer à une mise en œuvre appropriée.

Il existe une petite bibliothèque (un fichier PHP) qui vous donnera PHP 5.5 password_hashen PHP 5.3.7+: https://github.com/ircmaxell/password_compat

Akirk
la source
2
Dans la plupart des cas, il est préférable d'omettre le paramètre salt. La fonction crée un sel à partir de la source aléatoire du système d'exploitation, il y a très peu de chances que vous puissiez fournir un meilleur sel par vous-même.
martinstoeckli
1
C'est ce que j'ai écrit, n'est-ce pas? "si aucun sel n'est spécifié, il est généré aléatoirement, c'est pourquoi il est préférable de ne pas spécifier de sel"
akirk
La plupart des exemples montrent comment ajouter les deux paramètres, même s'il n'est pas recommandé d'ajouter un sel, alors je me demande pourquoi? Et pour être honnête, je n'ai lu que le commentaire derrière le code, pas sur la ligne suivante. Quoi qu'il en soit, ne serait-il pas préférable que l'exemple montre comment utiliser au mieux la fonction?
martinstoeckli
Tu as raison, je suis d'accord. J'ai changé ma réponse en conséquence et commenté la ligne. Merci
akirk
comment dois-je vérifier si le mot de passe enregistré et le mot de passe entré sont les mêmes? J'utilise password_hash()et password_verifyquel que soit le mot de passe (correct ou non) que j'ai utilisé, je me retrouve avec le mot de passe correct
Brownman Revival
0

Ça me va. M. Atwood a écrit sur la force de MD5 contre les tables arc-en-ciel , et fondamentalement avec un long sel comme ça, vous êtes assis assez (bien que des signes de ponctuation / chiffres aléatoires, cela pourrait l'améliorer).

Vous pouvez également regarder SHA-1, qui semble devenir de plus en plus populaire ces jours-ci.

nickf
la source
6
La note au bas du message de M. Atwood (en rouge) renvoie à un autre message d'un praticien de la sécurité qui déclare que l'utilisation de MD5, SHA1 et d'autres hachages rapides pour stocker les mots de passe est très fausse.
sipwiz
2
@Matthew Scharley: Je ne suis pas d'accord pour dire que l'effort supplémentaire imposé par les algorithmes coûteux de hachage de mots de passe est une fausse sécurité. Il s'agit de se prémunir contre le forçage brutal de mots de passe facilement devinables. Si vous limitez les tentatives de connexion, vous vous protégez contre la même chose (bien qu'un peu plus efficacement). Mais si un adversaire a accès aux hachages stockés dans la base de données, il sera capable de forcer brutalement de tels mots de passe (facilement devinables) assez rapidement (en fonction de leur facilité à deviner). La valeur par défaut de l'algorithme de cryptage SHA-256 est 10000 tours, ce qui le rendrait 10000 fois plus difficile.
Inshallah
3
Les hachages lents sont en fait effectués en itérant un hachage rapide un très grand nombre de fois et en mélangeant les données entre chaque itération. Le but est de s'assurer que même si le méchant obtient une copie de vos hachages de mot de passe, il doit brûler une quantité considérable de temps processeur pour tester son dictionnaire par rapport à vos hachages.
caf
4
@caf: Je crois que l'algorithme bcrypt utilise la cherté paramétrable de la planification des clés Eksblowfish; Je ne sais pas tout à fait comment cela fonctionne, mais la planification des clés est souvent une opération très coûteuse effectuée lors de l'initialisation d'un objet de contexte de chiffrement, avant tout chiffrement.
Inshallah
3
Inshallah: C'est vrai - l'algorithme bcrypt est une conception différente, où la primitive crypto sous-jacente est un chiffrement par bloc plutôt qu'une fonction de hachage. Je faisais référence à des schémas basés sur des fonctions de hachage, comme MD5 crypt () de PHK.
caf
0

Je veux ajouter:

  • Ne limitez pas les mots de passe des utilisateurs par longueur

Pour la compatibilité avec les anciens systèmes, définissez souvent une limite pour la longueur maximale du mot de passe. C'est une mauvaise politique de sécurité: si vous définissez une restriction, ne la définissez que pour la longueur minimale des mots de passe.

  • N'envoyez pas de mots de passe utilisateur par e-mail

Pour récupérer un mot de passe oublié, vous devez envoyer l'adresse par laquelle l'utilisateur peut changer le mot de passe.

  • Mettre à jour les hachages des mots de passe des utilisateurs

Le hachage du mot de passe peut être obsolète (les paramètres de l'algorithme peuvent être mis à jour). En utilisant la fonction, password_needs_rehash()vous pouvez le vérifier.


la source