【问题标题】:Secure random token in Node.jsNode.js 中的安全随机令牌
【发布时间】:2012-02-09 23:00:12
【问题描述】:

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

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

有没有更优雅的方式?

【问题讨论】:

  • 剩下的代码是什么?
  • 没有什么需要的了。你想看什么休息?
  • 没关系,我得到它的工作,只是不确定你是如何把它扔进去的,但更好地掌握了这个概念
  • 无耻的自插件,我创建了另一个 npm 包:tokgen。您可以使用类似于正则表达式中的字符类 ('a-zA-Z0-9_-') 的范围语法来指定允许的字符。
  • 这对于任何想要特定字符串长度的人来说可能很方便。 3/4 是处理基本转换。 /*返回一个base64编码的长度字符串*/ function randomString(length){ return crypto.randomBytes(length*3/4).toString('base64');非常适合那些有字符限制的数据库。

标签: javascript node.js base64 securestring


【解决方案1】:

从 Node.js 14.18 和 15.7 开始,url-safe base64 编码支持 is built-in:

const token = crypto.randomBytes(48).toString('base64url');

如果您想使用异步版本(因为函数可能必须等待熵),可以承诺更好地与现代模式保持一致:

const randomBytesAsync = util.promisify(crypto.randomBytes);

const token = (await randomBytesAsync(48)).toString('base64url');

【讨论】:

    【解决方案2】:

    您可以使用random-token lib。它很容易使用。 :)

    var randomToken = require('random-token').create('abcdefghijklmnopqrstuvwxzyABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789');
    var token = randomToken(16);
    

    而且你也不能使用不同的盐

    var randomToken = require('random-token');
    var token = randomToken(16); // output -> d8d4kd29c40f021 ```
    

    【讨论】:

      【解决方案3】:

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


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

      https://github.com/ai/nanoid

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

      2。使用 URL 和文件名安全字母进行 Base 64 编码


      Page 7 of RCF 4648 描述了如何在具有 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.
      

      请注意,返回的字符串长度不会与 size 参数匹配(size != final 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.
      

      【讨论】:

      • @Lexynux 解决方案 1(Base 64 Encoding with URL and Filename Safe Alphabet),因为它是安全性最强的解决方案。该方案只对密钥进行编码,不干扰密钥生成过程。
      • 感谢您的支持。您有任何工作示例可以与社区分享吗?会受到欢迎吗?
      • 注意生成的随机字符串不是均匀分布的。一个简单的例子来说明这一点,对于长度为 255 的字符集和长度为 1 的字符串,第一个字符出现的机会是两倍。
      • @Dodekeract 是的,你说的是解决方案 2。这就是解决方案 1 更强大的原因
      • 我在回复github.com/ai/nanoid 中添加了 nanoid 第三方库
      【解决方案4】:

      crypto-random-string 是一个很好的模块。

      const cryptoRandomString = require('crypto-random-string');
       
      cryptoRandomString({length: 10});
      // => '2cf05d94db'
       
      cryptoRandomString({length: 10, type: 'base64'});
      // => 'YMiMbaQl6I'
       
      cryptoRandomString({length: 10, type: 'url-safe'});
      // => 'YN-tqc8pOw'
       
      cryptoRandomString({length: 10, type: 'numeric'});
      // => '8314659141'
       
      cryptoRandomString({length: 6, type: 'distinguishable'});
      // => 'CDEHKM'
       
      cryptoRandomString({length: 10, type: 'ascii-printable'});
      // => '`#Rt8$IK>B'
       
      cryptoRandomString({length: 10, type: 'alphanumeric'});
      // => 'DMuKL8YtE7'
       
      cryptoRandomString({length: 10, characters: 'abc'});
      // => 'abaaccabac'
      

      cryptoRandomString.async(options) 如果您想获得promise,请添加.async

      【讨论】:

        【解决方案5】:

        一个简单的函数,为您提供一个 URL 安全且具有 base64 编码的令牌!这是上面2个答案的组合。

        const randomToken = () => {
            crypto.randomBytes(64).toString('base64').replace(/\//g,'_').replace(/\+/g,'-');
        }
        

        【讨论】:

          【解决方案6】:

          使用 async/await 和 promisification

          const crypto = require('crypto')
          const randomBytes = Util.promisify(crypto.randomBytes)
          const plain = (await randomBytes(24)).toString('base64').replace(/\W/g, '')
          

          生成类似于VjocVHdFiz5vGHnlnwqJKN0NdeHcz8eM的东西

          【讨论】:

            【解决方案7】:

            使用 ES 2016 异步和等待标准(从节点 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 转换

            【讨论】:

            【解决方案8】:

            real_atesES2016的方式,比较正确。

            ECMAScript 2016 (ES7) 方式

            import crypto from 'crypto';
            
            function spawnTokenBuf() {
                return function(callback) {
                    crypto.randomBytes(48, callback);
                };
            }
            
            async function() {
                console.log((await spawnTokenBuf()).toString('base64'));
            };
            

            发电机/屈服方式

            var crypto = require('crypto');
            var co = require('co');
            
            function spawnTokenBuf() {
                return function(callback) {
                    crypto.randomBytes(48, callback);
                };
            }
            
            co(function* () {
                console.log((yield spawnTokenBuf()).toString('base64'));
            });
            

            【讨论】:

            • @Jeffpowrs 确实,Javascript 正在升级 :) 查找承诺和生成器!
            • 尝试等待,另一个 ECMA7 承诺处理程序
            • 我认为你应该让 ES 2016 成为这方面的第一个例子,因为它在大多数情况下正朝着“正确的方式”发展
            • 我在下面添加了一个我自己的特定于 Node 的答案(使用 require 而不是 import)。您使用导入是否有特殊原因?你有 babel 跑吗?
            • @real_ate 我确实是,在正式支持导入之前,我已经恢复使用 CommonJS。
            【解决方案9】:

            退房:

            var crypto = require('crypto');
            crypto.randomBytes(Math.ceil(length/2)).toString('hex').slice(0,length);
            

            【讨论】:

            • 不错!绝对被低估的解决方案。如果您将“length”重命名为“desiredLength”并在使用它之前用一个值启动它会很棒:)
            • 对于任何想知道的人来说,ceilslice 调用对于所需的奇数长度是必要的。对于偶数长度,它们不会改变任何东西。
            【解决方案10】:

            npm 模块anyid 提供灵活的API 来生成各种字符串ID/代码。

            使用 48 个随机字节在 A-Za-z0-9 中生成随机字符串:

            const id = anyid().encode('Aa0').bits(48 * 8).random().id();
            // G4NtiI9OYbSgVl3EAkkoxHKyxBAWzcTI7aH13yIUNggIaNqPQoSS7SpcalIqX0qGZ
            

            要生成仅由随机字节填充的固定长度字母字符串:

            const id = anyid().encode('Aa').length(20).random().id();
            // qgQBBtDwGMuFHXeoVLpt
            

            在内部它使用crypto.randomBytes() 来生成随机数。

            【讨论】:

              【解决方案11】:

              试试crypto.randomBytes():

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

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

              【讨论】:

              • 这似乎更好,谢谢!不过,“base64-url”编码会很好。
              • 感谢您的提示,但我认为 OP 只是想要已经标准的 RFC 3548 第 4 节“使用 URL 和文件名安全字母进行 Base 64 编码”。 IMO,替换字符“足够优雅”。
              • 如果你正在寻找上述作为 bash 单行的,你可以做node -e "require('crypto').randomBytes(48, function(ex, buf) { console.log(buf.toString('hex')) });"
              • 您可以随时使用buf.toString('base64') 获取 Base64 编码的数字。
              • 德米特里出色的单线的稍微更紧凑的版本:node -p "require('crypto').randomBytes(48).toString('hex');"(如果需要,可以使用 base64 替换 hex
              【解决方案12】:

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

              Crypto.randomBytes(48).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, '');
              

              【讨论】:

              • 简单的绝妙答案!请注意,它可能会以不确定的方式停止事件循环(仅在经常使用时才相关,在负载较大的时间敏感系统中)。否则,做同样的事情,但使用随机字节的异步版本。见nodejs.org/api/…
              【解决方案13】:

              https://www.npmjs.com/package/crypto-extra 有一个方法:)

              var value = crypto.random(/* desired length */)
              

              【讨论】:

              • 太棒了!但不是.randomString (length, charset)(见documentation)。所以你可以使用例如crypto.randomString(12)
              【解决方案14】:

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

              var token = crypto.randomBytes(64).toString('hex');
              

              【讨论】:

              • 如果你不想让所有东西都嵌套。谢谢!
              • 虽然这绝对有效,但请注意,在大多数情况下,您需要在 jh 的答案中演示 async 选项。
              • const generateToken = (): Promise&lt;string&gt; =&gt; new Promise(resolve =&gt; randomBytes(48, (err, buffer) =&gt; resolve(buffer.toString('hex'))));
              • @Triforcey 你能解释一下为什么你通常需要异步选项吗?
              • @thomas 随机数据可能需要一段时间来计算,具体取决于硬件。在某些情况下,如果计算机用完随机数据,它只会在原位返回一些东西。但是在其他情况下,计算机可能会延迟随机数据的返回(这实际上是您想要的),从而导致调用缓慢。
              猜你喜欢
              • 2020-08-29
              • 1970-01-01
              • 2011-11-20
              • 1970-01-01
              • 1970-01-01
              • 2018-11-12
              • 2015-08-17
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多