【问题标题】:How do I use Node.js Crypto to create a HMAC-SHA1 hash?如何使用 Node.js Crypto 创建 HMAC-SHA1 哈希?
【发布时间】:2011-11-20 18:20:14
【问题描述】:

我想创建I love cupcakes 的哈希(使用密钥abcdeg 签名)

如何使用 Node.js Crypto 创建该哈希?

【问题讨论】:

    标签: javascript node.js algorithm hash node-crypto


    【解决方案1】:

    加密文档: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')
    

    【讨论】:

    【解决方案2】:

    几年前,据说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#crypto_class_hmac。提供更多使用流式方法的示例。

    【讨论】:

    • 不是单线的,而且电话不能菊花链式...但我会使用这种方法。
    • 我这辈子都做不到。 hmac.read() 返回一个“[object SlowBuffer]”,如果我尝试使用 hmac.read().toString('hex');我没有得到预期值。如果我使用 update/digest deprecated 方法,它会返回预期的字符串。我正在使用它来验证从第三方 POST 到我的服务器的签名。有什么想法吗?
    • 也许 hmac.read 是在数据被刷新到流之前发生的?也许 hmac.read 应该由流的完成事件驱动?
    • 实际上,您发布的链接明确提到了update 的使用,而不是write。我很困惑,现在哪个是最佳做法?我找不到像你提到的那样清楚地说明这一点的资源。
    • 截至 2016 年 11 月,digestupdate被弃用,并在文档中进行了介绍:nodejs.org/api/crypto.html#crypto_class_hmac。我建议您仅在从流中读取数据时才使用流 API。
    【解决方案3】:

    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 字符串

    【讨论】:

    • 你错了,不需要添加回调。该流是同步的,并且在调用 end() 后立即可读。最令人着迷的是它是写在官方文档中的,但每个人都必须投入他们的 5 美分(弯曲)。
    • 你在拖钓吗?也许您应该阅读文档。如果您尝试在完成事件之前读取流,它将失败。
    • From [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.你在writableend读,你甚至不需要等待 可读 端变为 可读 (尽管它确实如此)。请阅读您的文档。
    • createHmac 创建一个。您在上面引用的文档行中的“ended”并不意味着 hmac.end(...) 已被调用,“ended”表示流已引发其完成事件,这就是命令接受回调的原因。调用 end() 方法后,流需要时间将数据刷新到底层系统。如果在引发完成事件之前调用 read() ,它将失败。继续将 Gwerder 的代码粘贴到 JSbin 中,然后自己看看。您应该阅读 Streams 文档以了解其工作原理。
    • 我已经在生产代码中使用它一段时间了,它非常稳定。老实说,我不知道 JSBin 是什么,但我也尝试了 nodejs 中支持的代码,只需复制粘贴,它也可以工作。您不应该想象文档的其他含义。 “结束”在文档中的任何地方总是意味着“结束”。再一次,您似乎误解了流有两个方面。并且在文档中明确指出,当 writable 端结束时,人们可以使用read(),并且与完成事件无关。
    【解决方案4】:

    尽管有所有用于签名和验证哈希算法的示例代码,但我仍然进行了一些实验和调整以使其正常工作。这是我的工作示例,我相信它涵盖了所有边缘情况。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-03-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-04
      • 1970-01-01
      相关资源
      最近更新 更多