Как использовать Node.js Crypto для создания хэша HMAC-SHA1?

Я хочу создать хеш I love cupcakes (подписано ключом abcdeg)

Как я могу создать этот хэш, используя Node.js Crypto?

4 ответа

Решение

Документация для шифрования: http://nodejs.org/api/crypto.html

var crypto = require('crypto')
  , text = 'I love cupcakes'
  , key = 'abcdeg'
  , hash

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

Несколько лет назад было сказано, что update() а также digest() были устаревшие методы, и был представлен новый подход потокового API. Теперь в документах говорится, что любой метод может быть использован. Например:

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);

Протестировано на узле v6.2.2 и v7.7.2

См. https://nodejs.org/api/crypto.html. Дает больше примеров использования потокового подхода.

Решение Gwerder не будет работать, потому что hash = hmac.read(); происходит до завершения потока Таким образом, проблемы AngraX. Так же hmac.write утверждение не является необходимым в этом примере.

Вместо этого сделайте это:

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...
});

Более формально, если хотите, строка

hmac.end(text, function () {

может быть написано

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

потому что в этом примере текст является строкой UTF

Несмотря на весь пример кода для подписи и проверки алгоритмов хэширования, мне все же пришлось немного поэкспериментировать и настроить, чтобы заставить его работать. Вот мой рабочий образец, который, я считаю, охватывает все крайние случаи.

Он безопасен для URL (т. е. его не нужно кодировать), он требует времени истечения срока действия и не будет неожиданно вызывать исключение. Существует зависимость от Day.js, но вы можете заменить ее другой библиотекой дат или выполнить собственное сравнение дат.

Написано на 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),
  );
};

Вы можете использовать его следующим образом:

      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);
Другие вопросы по тегам