【发布时间】:2011-11-20 18:20:14
【问题描述】:
我想创建I love cupcakes 的哈希(使用密钥abcdeg 签名)
如何使用 Node.js Crypto 创建该哈希?
【问题讨论】:
标签: javascript node.js algorithm hash node-crypto
我想创建I love cupcakes 的哈希(使用密钥abcdeg 签名)
如何使用 Node.js Crypto 创建该哈希?
【问题讨论】:
标签: javascript node.js algorithm hash node-crypto
加密文档: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')
【讨论】:
crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b)): stackoverflow.com/questions/31095905/…
几年前,据说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。提供更多使用流式方法的示例。
【讨论】:
update 的使用,而不是write。我很困惑,现在哪个是最佳做法?我找不到像你提到的那样清楚地说明这一点的资源。
digest 和 update 已不被弃用,并在文档中进行了介绍:nodejs.org/api/crypto.html#crypto_class_hmac。我建议您仅在从流中读取数据时才使用流 API。
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 字符串
【讨论】:
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.你在writable端end读,你甚至不需要等待 可读 端变为 可读 (尽管它确实如此)。请阅读您的文档。
hmac.end(...) 已被调用,“ended”表示流已引发其完成事件,这就是命令接受回调的原因。调用 end() 方法后,流需要时间将数据刷新到底层系统。如果在引发完成事件之前调用 read() ,它将失败。继续将 Gwerder 的代码粘贴到 JSbin 中,然后自己看看。您应该阅读 Streams 文档以了解其工作原理。
read(),并且与完成事件无关。
尽管有所有用于签名和验证哈希算法的示例代码,但我仍然进行了一些实验和调整以使其正常工作。这是我的工作示例,我相信它涵盖了所有边缘情况。
它是 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);
【讨论】: