web-dev-qa-db-fra.com

Comment générer des hachages cryptographiques de courte longueur fixe?

J'essaie de mettre en œuvre une sorte de système de vérification des e-mails avec un serveur node.js sans état.

La stratégie

  1. L'utilisateur envoie son e-mail au serveur
  2. Le serveur génère un code à 4 chiffres basé sur l'adresse e-mail et l'envoie à l'utilisateur par e-mail.
  3. L'utilisateur renvoie le code reçu par e-mail + l'adresse e-mail au serveur
  4. Le serveur recrée le code à 4 chiffres basé sur l'e-mail et le compare avec le code envoyé par l'utilisateur.

Mon implémentation pour générer le code à 4 chiffres

  1. Créer un condensé HEX à l'aide de la fonction de hachage HMAC SHA-256
  2. Prenez les 3 premiers caractères du résumé
  3. Les convertir en un entier
  4. Si longueur <4, concatène un ou plusieurs 0 à la fin
const crypto = require('crypto')

const get4DigitsCode = (message) => {
  const hash = crypto
    .createHmac('sha256', Buffer.from(SECRET_KEY, 'hex'))
    .update(message)
    .digest('hex')

  const first3HexCharacters = hash.slice(0, 3)

  const int = parseInt(first3HexCharacters, 16)

  let code = int.toString()
  code =
    Array(4 - code.length)
      .fill(0)
      .join("") + code

  return code
}

Après avoir généré des codes pour 8293 adresses e-mail, j'ai remarqué que j'avais 4758 doublons. Est-il normal d'avoir autant de doublons pour un code de ce type? Ma stratégie et mon implémentation sont-elles sécurisées (capacité à deviner le code)?

Le service est une application mobile basée sur le courrier électronique (une application "mail to self"). Je veux un code à 4 chiffres pour des raisons UX. L'utilisateur peut lire le code de la notification du client de messagerie, le mémoriser facilement et le taper dans l'application (qu'il ne quitte jamais). Pas de copier-coller fastidieux, ne pas quitter l'application, il suffit de lire et de taper. Je sais que plusieurs courriels généreraient le même code, mais cela n'a pas d'importance car c'est juste pour valider les courriels. Je protégerai également les API contre la force brute.

Quelqu'un peut-il deviner le code avec cette stratégie (quels sont les risques ou les attaques possibles?) Et mon implémentation actuelle est-elle correcte?

[~ # ~] mise à jour [~ # ~]

Une meilleure implémentation grâce à @ réponse duskwuff :

const crypto = require('crypto')

const get4DigitsCode = (message) => {
    const hash = crypto
        .createHmac('sha256', Buffer.from(SECRET, 'hex'))
        .update(message)
        .digest('hex');
    const first4HexCharacters = hash.slice(0, 4);
    const int = parseInt(first4HexCharacters, 16) % 10000;
    let code = int.toString();
    code =
        Array(4 - code.length)
            .fill(0)
            .join('') + code;
    return code;
};
9
Thomas

La fonction de hachage cryptographique avec une taille de sortie n a une collision √n Avec une probabilité de 50% en raison du paradoxe d'anniversaire.

Vous avez pris 3 chiffres hexadécimaux, ce qui signifie que vous obtenez 12 bits. Après 26= 64 générations de hachage, vous verrez une collision avec une probabilité de 50%.

La collision dans les fonctions de hachage est inévitable en raison de l'espace de sortie de taille fixe mais de la longueur arbitraire de l'espace d'entrée. Pigeonhole Principle nous dit que la collision est inévitable.

Si vous voulez une faible probabilité de collision, vous devez utiliser une sortie plus grande en 128 bits. Pour 128 bits, nous prévoyons au moins une collision sur 264 hachages avec une probabilité de 50%.

Nous n'acceptons pas que le système soit secret, nous supposons qu'il est connu. Les e-mails ne sont pas secrets et tout attaquant peut créer son code en utilisant votre code source. Un attaquant ou un groupe d'attaquants peut tenter d'attaquer votre système en raison d'un jeton court. L'atténuation augmente la taille du jeton.

Pour la plupart des applications, l'utilisation de read("/dev/urandom", 16) conviendra parfaitement ou vous pouvez utiliser HMAC($email, $key) sans recadrage. Le copier-coller ne devrait pas être un problème pour l'utilisateur.

Si vous avez besoin d'une résistance plus élevée, vous pouvez utiliser des signatures numériques; hachez d'abord l'e-mail puis signez.

20
kelalaka

Il y a quelques problèmes assez graves avec cette approche qui augmenteront le risque de collisions au-delà de ce qui serait normalement attendu pour une valeur aléatoire à 4 chiffres.

  1. Prenez les 3 premiers caractères du résumé
  2. Les convertir en un entier

Trois chiffres hexadécimaux vous donnent une plage de 0 à 4095 (0x000 - 0xfff). Cela vous donnera beaucoup plus de collisions que prévu pour un nombre à 4 chiffres, car vous n'utilisez que 40% des valeurs possibles.

Utilisez un plus grand extrait du résumé et utilisez une opération de module (% 10000) pour forcer le résultat à être composé de quatre chiffres décimaux. Pour des raisons numériques, l'utilisation des 10 premiers chiffres hexadécimaux (40 bits) est assez proche de l'idéal. (Les multiples de 10 bits vous donnent des puissances approximatives de 1000, et 40 bits est grand mais inférieur à l'entier sûr maximum de 253.)

  1. Si longueur <4, concatène un ou plusieurs 0 à la fin

Cela introduit encore plus collisions. Les valeurs de 1 à 4, 10 à 40 et 100 à 400 sont toutes mappées sur la même plage de 1000 à 4000, et il existe des collisions secondaires pour des ensembles de valeurs comme 5, 50 et 500.

Si vous devez remplir avec des zéros, faites-le sur le côté gauche, pas sur la droite.

11
duskwuff -inactive-