Node.js 中的安全随机令牌

IT技术 javascript node.js base64 securestring
2021-02-22 15:40:43

这个问题中, Erik 需要在 Node.js 中生成一个安全的随机令牌。有一种crypto.randomBytes生成随机缓冲区的方法但是,node 中的 base64 编码不是 url-safe,它包括/and+而不是-and _因此,我发现生成此类令牌的最简单方法是

require('crypto').randomBytes(48, function(ex, buf) {
    token = buf.toString('base64').replace(/\//g,'_').replace(/\+/g,'-');
});

有没有更优雅的方式?

6个回答

尝试crypto.randomBytes()

require('crypto').randomBytes(48, function(err, buffer) {
  var token = buffer.toString('hex');
});

“十六进制”编码适用于节点 v0.6.x 或更新版本。

感谢您的提示,但我认为 OP 只是想要已经标准的 RFC 3548 第 4 节“带有 URL 和文件名安全字母表的 Base 64 编码”。IMO,替换字符是“足够优雅”。
2021-05-02 15:40:43
如果您正在寻找上述作为 bash one-liner 的内容,您可以这样做 node -e "require('crypto').randomBytes(48, function(ex, buf) { console.log(buf.toString('hex')) });"
2021-05-03 15:40:43
好像好多了,谢谢!不过,“base64-url”编码会很好。
2021-05-18 15:40:43
你总是buf.toString('base64')可以得到一个 Base64 编码的数字。
2021-05-18 15:40:43
Dmitry 出色的 one-liner 稍微更紧凑的版本:(如果需要node -p "require('crypto').randomBytes(48).toString('hex');"替换base64hex
2021-05-20 15:40:43

如果您不是像我这样的 JS 专家,则可以使用同步选项。不得不花一些时间在如何访问内联函数变量上

var token = crypto.randomBytes(64).toString('hex');
const generateToken = (): Promise<string> => new Promise(resolve => randomBytes(48, (err, buffer) => resolve(buffer.toString('hex'))));
2021-04-25 15:40:43
@thomas 随机数据可能需要一段时间来计算,具体取决于硬件。在某些情况下,如果计算机用完随机数据,它只会返回一些东西。但是在其他情况下,计算机可能会延迟随机数据的返回(这实际上是您想要的),从而导致调用缓慢。
2021-04-25 15:40:43
@Triforcey 你能解释为什么你通常想要异步选项吗?
2021-04-28 15:40:43
虽然这绝对有效,但请注意,在大多数情况下,您需要在 jh 的答案中演示 async 选项。
2021-05-04 15:40:43
另外,如果您不想嵌套所有内容。谢谢!
2021-05-14 15:40:43

1.使用nanoid第三方库[新!]


一个小巧的、安全的、URL 友好的、独特的 JavaScript 字符串 ID 生成器

https://github.com/ai/nanoid

import { nanoid } from "nanoid";
const id = nanoid(48);

2. 带有 URL 和文件名安全字母表的 Base 64 编码


RCF 4648 的第 7 页描述了如何在具有 URL 安全性的 base 64 中进行编码。您可以使用像base64url这样的现有库来完成这项工作。

该功能将是:

var crypto = require('crypto');
var base64url = require('base64url');

/** Sync */
function randomStringAsBase64Url(size) {
  return base64url(crypto.randomBytes(size));
}

用法示例:

randomStringAsBase64Url(20);
// Returns 'AXSGpLVjne_f7w5Xg-fWdoBwbfs' which is 27 characters length.

请注意,返回的字符串长度将与大小参数不匹配(大小!= 最终长度)。


3. 来自有限字符集的加密随机值


请注意,使用此解决方案生成的随机字符串不是均匀分布的。

您还可以从一组有限的字符中构建一个强大的随机字符串,如下所示:

var crypto = require('crypto');

/** Sync */
function randomString(length, chars) {
  if (!chars) {
    throw new Error('Argument \'chars\' is undefined');
  }

  const charsLength = chars.length;
  if (charsLength > 256) {
    throw new Error('Argument \'chars\' should not have more than 256 characters'
      + ', otherwise unpredictability will be broken');
  }

  const randomBytes = crypto.randomBytes(length);
  let result = new Array(length);

  let cursor = 0;
  for (let i = 0; i < length; i++) {
    cursor += randomBytes[i];
    result[i] = chars[cursor % charsLength];
  }

  return result.join('');
}

/** Sync */
function randomAsciiString(length) {
  return randomString(length,
    'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789');
}

用法示例:

randomAsciiString(20);
// Returns 'rmRptK5niTSey7NlDk5y' which is 20 characters length.

randomString(20, 'ABCDEFG');
// Returns 'CCBAAGDGBBEGBDBECDCE' which is 20 characters length.
注意生成的随机字符串不是均匀分布的。一个简单的例子来说明这一点,对于长度为 255 的字符集和长度为 1 的字符串,第一个字符出现的几率是原来的两倍。
2021-04-25 15:40:43
谢谢你的支持。您有任何工作示例可以与社区分享吗?会受欢迎吗?
2021-04-26 15:40:43
我在响应github.com/ai/nanoid 中添加了 nanoid 第三方库
2021-05-06 15:40:43
@Dodekeract 是的,您在谈论解决方案 2 .. 这就是解决方案 1 更强大的原因
2021-05-16 15:40:43
@Lexynux解决方案 1(Base 64 Encoding with URL and Filename Safe Alphabet)因为它是安全性最强的解决方案。该方案只对密钥进行编码,不会干扰密钥的制作过程。
2021-05-17 15:40:43

使用 ES 2016 标准的 async 和 await(从 Node 7 开始)异步执行此操作的最新正确方法如下:

const crypto = require('crypto');

function generateToken({ stringBase = 'base64', byteLength = 48 } = {}) {
  return new Promise((resolve, reject) => {
    crypto.randomBytes(byteLength, (err, buffer) => {
      if (err) {
        reject(err);
      } else {
        resolve(buffer.toString(stringBase));
      }
    });
  });
}

async function handler(req, res) {
   // default token length
   const newToken = await generateToken();
   console.log('newToken', newToken);

   // pass in parameters - adjust byte length
   const shortToken = await generateToken({byteLength: 20});
   console.log('newToken', shortToken);
}

这在 Node 7 中开箱即用,无需任何 Babel 转换

我已经更新了这个例子,以合并这里描述的传递命名参数的新方法:2ality.com/2011/11/keyword-parameters.html
2021-05-12 15:40:43

随机 URL 和文件名字符串安全(1 行)

Crypto.randomBytes(48).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, '');
一个很好的答案,因为它很简单!请注意,它可能会以不确定的方式停止事件循环(仅当它经常使用时才相关,在有些负载的时间敏感的系统中)。否则,做同样的事情,但使用 randomBytes 的异步版本。nodejs.org/api/...
2021-04-23 15:40:43