231 votes

Comment utiliser Node.js Crypto pour créer un hachage HMAC-SHA1 ?

Je veux créer un hachage de I love cupcakes (signé avec la clé abcdeg )

Comment puis-je créer ce hachage à l'aide de Node.js Crypto ?

407voto

Ricardo Tomasi Points 13398

Documentation pour la cryptographie : http://nodejs.org/api/crypto.html

const crypto = require('crypto')

const text = 'I love cupcakes'
const key = 'abcdeg'

crypto.createHmac('sha1', key)
  .update(text)
  .digest('hex')

0 votes

'hex' n'est pas toujours nécessaire, par exemple pour faire l'équivalent du digest hmac de ruby.

9 votes

Et pour vérifier un hachage, vous devez utiliser crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b)) : stackoverflow.com/questions/31095905/

1 votes

La boucle est bouclée : nodejs.org/api/crypto.html#crypto_crypto

103voto

Adam Griffiths Points 259

Il y a quelques années, on disait que update() y digest() étaient des méthodes héritées du passé et la nouvelle approche de l'API de diffusion en continu a été introduite. Aujourd'hui, la documentation indique que les deux méthodes peuvent être utilisées. Par exemple :

var crypto    = require('crypto');
var text      = 'I love cupcakes';
var secret    = 'abcdeg'; //make this your secret!!
var algorithm = 'sha1';   //consider using sha256
var hash, hmac;

// Method 1 - Writing to a stream
hmac = crypto.createHmac(algorithm, secret);    
hmac.write(text); // write in to the stream
hmac.end();       // can't read from the stream until you call end()
hash = hmac.read().toString('hex');    // read out hmac digest
console.log("Method 1: ", hash);

// Method 2 - Using update and digest:
hmac = crypto.createHmac(algorithm, secret);
hmac.update(text);
hash = hmac.digest('hex');
console.log("Method 2: ", hash);

Testé sur les nœuds v6.2.2 et v7.7.2

Véase https://nodejs.org/api/crypto.html#crypto_class_hmac . Donne d'autres exemples d'utilisation de l'approche par flux.

0 votes

Il ne s'agit pas d'un one-liner, et les appels ne peuvent pas être enchaînés... mais j'utiliserai cette approche.

2 votes

Hmac.read() renvoie un "[objet SlowBuffer]" et si j'essaie de lire le contenu en utilisant hmac.read().toString('hex') ; je n'obtiens pas la valeur attendue. Si j'utilise l'approche update/digest deprecated, elle renvoie la chaîne attendue. J'utilise ceci pour valider une signature d'un POST tiers sur mes serveurs. Une idée de ce qui se passe ?

0 votes

Peut-être que le hmac.read se produit avant que les données n'aient été transférées dans le flux ? Peut-être que hmac.read devrait être piloté par l'événement de fin du flux ?

22voto

Dave Points 81

La solution de Gwerder ne fonctionne pas parce que hash = hmac.read(); se produit avant que le flux ne soit finalisé. D'où les problèmes rencontrés par AngraX. De même, le hmac.write n'est pas nécessaire dans cet exemple.

Au lieu de cela, faites ceci :

var crypto    = require('crypto');
var hmac;
var algorithm = 'sha1';
var key       = 'abcdeg';
var text      = 'I love cupcakes';
var hash;

hmac = crypto.createHmac(algorithm, key);

// readout format:
hmac.setEncoding('hex');
//or also commonly: hmac.setEncoding('base64');

// callback is attached as listener to stream's finish event:
hmac.end(text, function () {
    hash = hmac.read();
    //...do something with the hash...
});

Plus formellement, si vous le souhaitez, la ligne

hmac.end(text, function () {

pourrait être écrit

hmac.end(text, 'utf8', function () {

car dans cet exemple le texte est une chaîne utf

0 votes

Vous avez tort, il n'est pas nécessaire d'ajouter un rappel. Ce flux est synchrone et est lisible juste après l'appel de end(). Le plus fascinant, c'est que c'est écrit dans la documentation officielle, mais tout le monde doit y mettre ses 5 centimes (courbés).

0 votes

Vous trollez ? Vous devriez peut-être lire la documentation. Si vous essayez de lire le flux avant l'événement de fin, cela échouera.

1 votes

De [ [nodejs.org/api/crypto.html#crypto_class_hmac]](https://nodejs.org/api/crypto.html#crypto_class_hmac]) It is a stream that is both readable and writable. The written data is used to compute the hmac. Once the writable side of the stream is ended, use the read() method to get the computed digest. Vous l'avez lu lorsque inscriptible côté terminé Vous n'avez même pas besoin d'attendre le moment où l'on vous demandera d'utiliser votre carte de crédit. lisible devient lisible (même si c'est certainement le cas). Lisez votre documentation s'il vous plaît.

0voto

Johnny Oshika Points 15580

Malgré tous les exemples de code pour la signature et la vérification des algorithmes de hachage, j'ai dû expérimenter et modifier pas mal de choses pour que cela fonctionne. Voici mon exemple de travail qui, je pense, couvre tous les cas de figure.

Il est sécurisé au niveau de l'URL (c'est-à-dire qu'il n'a pas besoin d'être encodé), il prend un délai d'expiration et ne lèvera pas d'exception de manière inattendue. Il y a une dépendance à l'égard de Jour.js mais vous pouvez la remplacer par une autre bibliothèque de dates ou créer votre propre comparaison de dates.

Écrit en TypeScript :

// signature.ts
import * as crypto from 'crypto';
import * as dayjs from 'dayjs';

const key = 'some-random-key-1234567890';

const replaceAll = (
  str: string,
  searchValue: string,
  replaceValue: string,
) => str.split(searchValue).join(replaceValue);

const swap = (str: string, input: string, output: string) => {
  for (let i = 0; i < input.length; i++)
    str = replaceAll(str, input[i], output[i]);

  return str;
};

const createBase64Hmac = (message: string, expiresAt: Date) =>
  swap(
    crypto
      .createHmac('sha1', key)
      .update(`${expiresAt.getTime()}${message}`)
      .digest('hex'),
    '+=/', // Used to avoid characters that aren't safe in URLs
    '-_,',
  );

export const sign = (message: string, expiresAt: Date) =>
  `${expiresAt.getTime()}-${createBase64Hmac(message, expiresAt)}`;

export const verify = (message: string, hash: string) => {
  const matches = hash.match(/(.+?)-(.+)/);
  if (!matches) return false;

  const expires = matches[1];
  const hmac = matches[2];

  if (!/^\d+$/.test(expires)) return false;

  const expiresAt = dayjs(parseInt(expires, 10));
  if (expiresAt.isBefore(dayjs())) return false;

  const expectedHmac = createBase64Hmac(message, expiresAt.toDate());
  // Byte lengths must equal, otherwise crypto.timingSafeEqual will throw an exception
  if (hmac.length !== expectedHmac.length) return false;

  return crypto.timingSafeEqual(
    Buffer.from(hmac),
    Buffer.from(expectedHmac),
  );
};

Vous pouvez l'utiliser comme suit :

import { sign, verify } from './signature';

const message = 'foo-bar';
const expiresAt = dayjs().add(1, 'day').toDate();
const hash = sign(message, expiresAt);

const result = verify(message, hash);

expect(result).toBe(true);

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X