Mise à jour du hachage du mot de passe sans forcer un nouveau mot de passe pour les utilisateurs existants

32

Vous gérez une application existante avec une base d'utilisateurs établie. Au fil du temps, il est décidé que la technique de hachage de mot de passe actuelle est obsolète et doit être mise à niveau. De plus, pour des raisons UX, vous ne voulez pas que les utilisateurs existants soient obligés de mettre à jour leur mot de passe. La mise à jour complète du mot de passe doit se faire derrière l'écran.

Supposons un modèle de base de données «simpliste» pour les utilisateurs qui contient:

  1. ID
  2. Email
  3. Mot de passe

Comment peut-on résoudre une telle exigence?


Mes pensées actuelles sont:

  • créer une nouvelle méthode de hachage dans la classe appropriée
  • mettre à jour la table d'utilisateurs dans la base de données pour contenir un champ de mot de passe supplémentaire
  • Une fois qu'un utilisateur s'est connecté avec succès en utilisant le hachage de mot de passe obsolète, remplissez le deuxième champ de mot de passe avec le hachage mis à jour

Cela me laisse avec le problème que je ne peux pas raisonnablement faire la différence entre les utilisateurs qui ont et ceux qui n'ont pas mis à jour leur mot de passe hash et seront donc obligés de vérifier les deux. Cela semble terriblement défectueux.

De plus, cela signifie que l'ancienne technique de hachage pourrait être forcée de rester indéfiniment jusqu'à ce que chaque utilisateur ait mis à jour son mot de passe. Ce n’est qu’à ce moment-là que je peux supprimer l’ancienne vérification de hachage et supprimer le champ de base de données superflue.

Je cherche principalement des conseils de conception ici, car ma "solution" actuelle est sale, incomplète et non, mais si du code est nécessaire pour décrire une solution possible, n'hésitez pas à utiliser n'importe quel langage.

Willem
la source
4
Pourquoi seriez-vous incapable de distinguer entre un hachage secondaire non défini et un ensemble? Il suffit de rendre la colonne de base de données nullable et de rechercher la valeur null.
Kilian Foth
6
Choisissez un type de hachage comme nouvelle colonne au lieu d’un champ pour le nouveau hachage. Lorsque vous mettez à jour le hachage, modifiez le champ de type. Ainsi, lorsque cela se reproduira à l'avenir, vous serez déjà en mesure de traiter et il n'y a aucune chance que vous conserviez un vieux hachage (probablement moins sécurisé).
Michael Kohne
1
Je crois que tu es sur le bon chemin. Dans 6 mois ou un an à partir de maintenant, vous pouvez forcer tous les utilisateurs restants à modifier leur mot de passe. Un an plus tard ou peu importe, vous pouvez vous débarrasser de l'ancien champ.
GlenPeterson
3
Pourquoi ne pas simplement hacher le vieux hachage?
Siyuan Ren
parce que vous voulez que le mot de passe soit haché (pas l'ancien), de sorte que le déchaîner (c'est-à-dire en le comparant à un haché donné par l'utilisateur) vous donne ... le mot de passe - pas une autre valeur qui doit ensuite être "décochée" sous l'ancienne méthode. Possible mais pas plus propre.
Michael Durrant

Réponses:

25

Je suggère d'ajouter un nouveau champ, "hash_method", avec peut-être un 1 pour indiquer l'ancienne méthode et un 2 pour indiquer la nouvelle méthode.

Raisonnablement, si ce genre de chose vous tient à cœur et que votre application a une durée de vie relativement longue (ce qui est apparemment déjà le cas), cela va probablement se reproduire car la cryptographie et la sécurité de l’information sont un domaine en constante évolution, plutôt imprévisible. Il fut un temps où une simple exécution via MD5 était standard, si le hachage était utilisé du tout! Alors, on pourrait penser qu’ils devraient utiliser SHA1, et maintenant il y a le salage, le sel global + le sel aléatoire individuel, SHA3, différentes méthodes de génération de nombres aléatoires crypto-prêts ... cela ne va pas simplement "arrêter", vous pouvez donc bien le réparer de manière extensible et répétable.

Alors, disons maintenant que vous avez quelque chose comme (j'espère en pseudo-javascript pour plus de simplicité):

var user = getUserByID(id);
var tryPassword = hashPassword(getInputPassword());


if (user.getPasswordHash() == tryPassword)
{
    // Authenticated!
}

function hashPassword(clearPassword)
{
    // TODO: Learn what "hash" means
    return clearPassword + "H@$I-I";
}

Maintenant, réalisant qu'il existe une meilleure méthode, il vous suffit de donner une refactorisation mineure:

var user = getUserByID(id);
var tryPassword = hashPassword(getInputPassword(), user.getHashingMethod());

if (user.getPasswordHash() == tryPassword)
{
    // Authenticated!
}

function hashPassword(clearPassword, hashMethod)
{
    // Note: Hash doesn't mean what we thought it did. Oops...

    var hash;
    if (hashMethod == 1)
    {
        hash = clearPassword + "H@$I-I";
    }
    else if (hashMethod == 2)
    {
        // Totally gonna get it right this time.
        hash = SuperMethodTheNSASaidWasAwesome(clearPassword);
    }
    return hash;
}

Aucun agent secret ni programmeur n'a été blessé dans la production de cette réponse.

BrianH
la source
+1 Cela semble assez abstrait, merci. Bien que je puisse finir par déplacer les champs hash et hashmethod vers une table séparée lorsque je l'implémente.
Willem
1
Le problème avec cette technique est que, si les comptes ne se connectent pas, vous ne pouvez pas mettre à jour les hachages. D'après mon expérience, la plupart des utilisateurs ne se connecteront pas avant des années (à moins que vous ne supprimiez des utilisateurs inactifs). Votre abstraction ne fonctionne pas non plus, car elle ne prend pas en compte les sels. L'abstraction standard a deux fonctions, une pour la vérification et l'autre pour la création.
CodesInChaos
Le hachage des mots de passe a à peine évolué au cours des 15 dernières années. Bcrypt a été publié en 1999 et fait toujours partie des valeurs recommandées. Une seule amélioration significative est intervenue depuis lors: le hachage dur en mémoire séquentiel, mis au point par scrypt.
CodesInChaos
Cela laisse les vieux hachages vulnérables (par exemple, si quelqu'un vole la BD). Hachant chaque ancien hachage avec la nouvelle méthode plus sûre atténue ce dans une certaine mesure (vous aurez toujours besoin du type de hachage pour distinguer entre newHash(oldHash, salt)ounewHash(password, salt)
dbkk
42

Si vous vous souciez suffisamment de déployer le nouveau schéma de hachage à tous les utilisateurs aussi rapidement que possible (par exemple parce que l'ancien est vraiment peu sécurisé), il existe en fait un moyen de "migrer" instantanément chaque mot de passe.

L'idée est fondamentalement de hacher le hachage . Plutôt que d'attendre que les utilisateurs fournissent leur mot de passe existant ( p) lors de la prochaine connexion, vous utilisez immédiatement le nouvel algorithme de hachage ( H2) sur le hachage existant déjà produit par l'ancien algorithme ( H1):

hash = H2(hash)  # where hash was previously equal to H1(p) 

Après cette conversion, vous pouvez toujours effectuer une vérification du mot de passe. il vous suffit de calculer à la H2(H1(p'))place de la précédente H1(p')lorsque l'utilisateur tente de se connecter avec un mot de passe p'.

En théorie, cette technique pourrait être appliquée à de multiples migrations ( H3, H4, etc.). En pratique, vous voudriez vous débarrasser de l'ancienne fonction de hachage, à la fois pour des raisons de performance et de lisibilité. Heureusement, cela est assez facile: lors de la prochaine connexion réussie, calculez simplement le nouveau hachage du mot de passe de l'utilisateur et remplacez le hachage existant par celui-ci:

hash = H2(p)

Vous aurez également besoin d'une colonne supplémentaire pour vous souvenir du hachage que vous stockez: celui du mot de passe ou celui de l'ancien. Dans le cas malheureux d'une fuite de base de données, cette colonne ne devrait pas faciliter le travail du pirate informatique; quelle que soit sa valeur, l'attaquant devrait tout de même inverser l' H2algorithme sécurisé plutôt que l'ancien H1.

Xion
la source
3
Un géant +1: H2 (H1 (p)) est généralement aussi sécurisé que d'utiliser H2 (p) directement, même si H1 est terrible, car (1) le sel et l'étirement sont pris en charge par H2 et (2) les problèmes avec des hachages "cassés" comme MD5 n'affecte pas le hachage du mot de passe Connexes: crypto.stackexchange.com/questions/2945/…
orip
Intéressant, j'aurais pensé que le hachage de l'ancien hachage créerait un problème de sécurité. Il semble que je me suis trompé.
Willem
4
si H1 est vraiment terrible, cela ne sera pas sécurisé. Être terrible dans ce contexte, c'est surtout perdre beaucoup d'entropie de la part. Même les MD4 et MD5 sont presque parfaits à cet égard. Cette approche n’est donc sûre que pour certains hachages homebrew ou avec une sortie très courte (inférieure à 80 bits).
CodesInChaos
1
Dans ce cas, probablement votre option est à seulement admettre que vous foiré dur et juste aller de l' avant et le mot de passe remis à zéro pour tous les utilisateurs. À ce stade, il est moins question de migration que de contrôle des dommages.
Xion
8

Votre solution (colonne supplémentaire dans la base de données) est parfaitement acceptable. Son seul problème est celui que vous avez déjà mentionné: l'ancien hachage est toujours utilisé pour les utilisateurs qui ne se sont pas authentifiés depuis le changement.

Afin d'éviter cette situation, vous pouvez attendre que vos utilisateurs les plus actifs basculent vers le nouvel algorithme de hachage, puis:

  1. Supprimez les comptes des utilisateurs qui ne se sont pas authentifiés depuis trop longtemps. Il n'y a rien de mal à supprimer le compte d'un utilisateur qui n'est jamais venu sur le site Web pendant trois ans.

  2. Envoyez un courrier électronique aux utilisateurs restants, parmi ceux qui ne se sont pas authentifiés depuis un moment, en indiquant qu'ils pourraient être intéressés par quelque chose de nouveau. Cela contribuera à réduire encore le nombre de comptes utilisant le hachage plus ancien.

    Attention: si vous avez une option anti-spam, les utilisateurs peuvent vérifier sur votre site web, n'envoyez pas l'e-mail aux utilisateurs qui l'ont coché.

  3. Enfin, quelques semaines après l’étape 2, détruisez le mot de passe des utilisateurs qui utilisent encore l’ancienne technique. Quand et s'ils essaient de s'authentifier, ils verront que leur mot de passe est invalide. S'ils sont toujours intéressés par votre service, ils peuvent le réinitialiser.

Arseni Mourzenko
la source
1

Vous n'aurez probablement jamais plus que quelques types de hachage, vous pouvez donc résoudre ce problème sans étendre votre base de données.

Si les méthodes ont des longueurs de hachage différentes et que la longueur de chaque méthode est constante (par exemple, passer de md5 à hmac-sha1), vous pouvez distinguer la méthode de la longueur de hachage. S'ils ont la même longueur, vous pouvez calculer le hachage en utilisant d'abord la nouvelle méthode, puis l'ancienne méthode si le premier test a échoué. Si vous avez un champ (non affiché) indiquant la dernière mise à jour / création de l'utilisateur, vous pouvez l'utiliser comme indicateur de la méthode à utiliser.

ÉvénementHorizon
la source
1

J'ai fait quelque chose comme ça et il y a un moyen de savoir si c'est le nouveau hachage ET le rendre encore plus sécurisé. Je suppose que plus de sécurité est une bonne chose pour vous si vous prenez le temps de mettre à jour votre hash ;-)

Ajoutez une nouvelle colonne à votre table de base de données appelée "salt" et utilisez-la comme sel généré aléatoirement par utilisateur. (à peu près empêche les attaques de table arc-en-ciel)

De cette façon, si mon mot de passe est "pass123" et que le sel aléatoire est 3333, mon nouveau mot de passe serait le hachage de "pass1233333".

Si l'utilisateur a un sel, vous savez que c'est le nouveau hash. Si le sel de l'utilisateur est nul, vous savez que c'est l'ancien hachage.

Ben
la source
0

Quelle que soit la qualité de votre stratégie de migration, si la base de données a déjà été volée (et vous ne pouvez pas prouver négativement que cela ne s'est jamais produit), les mots de passe stockés avec des fonctions de hachage non sécurisées peuvent déjà être compromis. La seule solution envisageable consiste à obliger les utilisateurs à modifier leurs mots de passe.

Ce n'est pas un problème qui peut être résolu (uniquement) en écrivant du code. La solution consiste à informer les utilisateurs et les clients des risques auxquels ils ont été exposés. Une fois que vous êtes prêt à rester ouvert à ce sujet, vous pouvez effectuer la migration en réinitialisant simplement le mot de passe de chaque utilisateur, car il s'agit de la solution la plus simple et la plus sécurisée. Toutes les alternatives visent vraiment à cacher le fait que vous avez foiré.

Florian Winter
la source
1
Un compromis que je l' ai fait dans le passé est de garder les anciens mots de passe pour une période de temps, les ressasser que les gens se connectent, mais après que le temps vous est passé ensuite forcer une réinitialisation de mot de passe sur tous ceux qui ne se connecter pendant cet instant. Vous avez donc une période pendant laquelle vous avez toujours les mots de passe moins sécurisés, mais le temps est limité et vous minimisez également les perturbations pour les utilisateurs qui se connectent régulièrement.
Sean Burton