Token aleatório seguro no Node.js

274

Em esta questão Erik precisa para gerar um token aleatório seguro em Node.js. Existe o método crypto.randomBytesque gera um buffer aleatório. No entanto, a codificação base64 no nó não é segura para URL, inclui /e em +vez de -e _. Portanto, a maneira mais fácil de gerar esse token que encontrei é

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

Existe uma maneira mais elegante?

Hubert OG
fonte
Qual é o restante do código?
precisa saber é o seguinte
3
Não há mais nada necessário. Que descanso você gostaria de ver?
Hubert OG
Não importa, eu tenho que trabalhar, era apenas não tem certeza de como você jogou no, mas tem uma melhor compreensão do conceito
Lion789
1
Auto-plug descarado, criei outro pacote npm : tokgen . Você pode especificar caracteres permitidos usando uma sintaxe de intervalo semelhante às classes de caracteres nas expressões regulares ( 'a-zA-Z0-9_-').
precisa saber é o seguinte
1
Isso pode ser conveniente para quem deseja um comprimento de string específico. O 3/4 é para lidar com a conversão de base. / * retorna uma string de comprimento codificada em base64 * / function randomString (length) {return crypto.randomBytes (length * 3/4) .toString ('base64'); } Funciona bem para esses bancos de dados com esses limites de caracteres.
TheUnknownGeek

Respostas:

353

Tente crypto.randomBytes () :

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

A codificação 'hex' funciona no nó v0.6.x ou mais recente.

thejh
fonte
3
Parece melhor, obrigado! Uma codificação 'base64-url' seria legal, no entanto.
Hubert OG
2
Obrigado pela dica, mas acho que o OP simplesmente queria a seção 4 da RFC 3548 já padrão "Codificação da Base 64 com URL e alfabeto seguro de nome de arquivo". Na IMO, a substituição dos caracteres é "suficientemente elegante".
Natevw 7/10
8
Se você está procurando o que foi node -e "require('crypto').randomBytes(48, function(ex, buf) { console.log(buf.toString('hex')) });"
dito
24
E você sempre pode fazer buf.toString('base64')para obter um número codificado em Base64.
Dmitry Minkovsky
1
Ver este answser abaixo para codificação de base 64 com o URL e Nome do ficheiro de segurança do alfabeto
Yves M.
233

Opção síncrona, caso você não seja um especialista em JS como eu. Tive que gastar algum tempo em como acessar a variável de função inline

var token = crypto.randomBytes(64).toString('hex');
phoenix2010
fonte
7
Também, caso você não queira que tudo esteja aninhado. Obrigado!
Michael Ozeryansky
2
Enquanto isso definitivamente funciona, observe que, na maioria dos casos, você desejará a opção assíncrona demonstrada na resposta do thejh.
precisa
1
const generateToken = (): Promise<string> => new Promise(resolve => randomBytes(48, (err, buffer) => resolve(buffer.toString('hex'))));
Yantrab
1
@Triforcey, você pode explicar por que geralmente deseja a opção assíncrona?
thomas
2
Os dados aleatórios podem demorar um pouco para serem calculados, dependendo do hardware. Em alguns casos, se o computador ficar sem dados aleatórios, ele retornará algo no seu lugar. No entanto, em outros casos, é possível que o computador atrase o retorno de dados aleatórios (que é realmente o que você deseja), resultando em uma chamada lenta.
Triforcey
80

0. Usando a biblioteca de terceiros nanoid [NEW!]

Um gerador de ID de string pequeno, seguro e amigável para URL, exclusivo para JavaScript

https://github.com/ai/nanoid

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


1. Codificação Base 64 com URL e Alfabeto Seguro de Nome de Arquivo

A página 7 do RCF 4648 descreve como codificar na base 64 com segurança de URL. Você pode usar uma biblioteca existente como base64url para fazer o trabalho.

A função será:

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

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

Exemplo de uso:

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

Observe que o comprimento da string retornada não corresponderá ao argumento size (size! = Length final).


2. Valores aleatórios criptográficos a partir de um conjunto limitado de caracteres

Cuidado com esta solução, a sequência aleatória gerada não é distribuída uniformemente.

Você também pode criar uma sequência aleatória forte a partir de um conjunto limitado de caracteres assim:

var crypto = require('crypto');

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

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

  var randomBytes = crypto.randomBytes(length);
  var result = new Array(length);

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

  return result.join('');
}

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

Exemplo de uso:

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

randomString(20, 'ABCDEFG');
// Returns 'CCBAAGDGBBEGBDBECDCE' which is 20 characters length.
Yves M.
fonte
2
@ Solução Lexynux 1 (Codificação Base 64 com URL e Alfabeto Seguro de Nome de Arquivo) porque é a solução mais forte em termos de segurança. Essa solução codifica apenas a chave e não interfere no processo de produção da chave.
Yves M.
Obrigado por seu apoio. Você tem algum exemplo de trabalho para compartilhar com a comunidade? Será bem-vindo?
28415 alexventuraio
6
Cuidado que a sequência aleatória gerada não é distribuída uniformemente. Um exemplo fácil de mostrar isso é que, para um conjunto de caracteres de comprimento 255 e comprimento de seqüência de caracteres 1, a chance do primeiro caractere aparecer é duas vezes maior.
Florian Wendelborn
@Dodekeract Sim, você está falando de solução 2 .. É por isso que uma solução 1 é muito mais forte
Yves M.
Nanoid Eu adicionei biblioteca de terceiros na minha resposta github.com/ai/nanoid
Yves M.
13

A maneira correta e atualizada de fazer isso de forma assíncrona usando os padrões ES 2016 de assíncrono e aguardar (no Nó 7) seria o seguinte:

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

Isso funciona imediatamente no nó 7 sem nenhuma transformação Babel

real_ate
fonte
Eu tenho atualizado este exemplo para incorporar o novo método de passar parâmetros nomeados, conforme descrito aqui: 2ality.com/2011/11/keyword-parameters.html
real_ate
7

URL aleatória e string de nome de arquivo segura (1 liner)

Crypto.randomBytes(48).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, '');
Kedem
fonte
Uma resposta maravilhosa em sua simplicidade! Esteja ciente de que ele pode interromper o loop de eventos de maneira indeterminista (relevante apenas se for usado com frequência, em um sistema com carga horária e sensível ao tempo). Caso contrário, faça o mesmo, mas usando a versão assíncrona de randomBytes. Veja nodejs.org/api/…
Alec Thilenius
6

Verificação de saída:

var crypto = require('crypto');
crypto.randomBytes(Math.ceil(length/2)).toString('hex').slice(0,length);
sudam
fonte
Agradável! Solução absolutamente subestimada. Seria ótimo se você renomear "comprimento" para "desiredLength" e iniciar-lo com um valor antes de usá-lo :)
Florian Blum
Para quem se pergunta, as chamadas ceile slicesão necessárias para os comprimentos desejados que são ímpares. Por comprimentos uniformes, eles não mudam nada.
Seth
6

Com async / wait e promisification .

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

Gera algo semelhante a VjocVHdFiz5vGHnlnwqJKN0NdeHcz8eM

Znarkus
fonte
4

Olhe para o real_atesES2016, é mais correto.

Maneira do ECMAScript 2016 (ES7)

import crypto from 'crypto';

function spawnTokenBuf() {
    return function(callback) {
        crypto.randomBytes(48, callback);
    };
}

async function() {
    console.log((await spawnTokenBuf()).toString('base64'));
};

Maneira do gerador / rendimento

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'));
});
K - A toxicidade no SO está crescendo.
fonte
@Jeffpowrs Na verdade, o Javascript está atualizando :) Promessas de pesquisa e geradores!
K - A toxicidade no SO está crescendo.
tente aguardar, outro manipulador de promessas ECMA7
Jain
Eu acho que você deve fazer o ES 2016 o primeiro exemplo desta, uma vez que está se movendo em direção ao "caminho certo para fazê-lo" na maioria dos casos
real_ate
Adicionei uma resposta própria abaixo, específica para o Node (usando require em vez de import). Houve um motivo específico para o uso da importação? Você tem babel correndo?
real_ate
@real_ate Na verdade, eu voltei a usar o CommonJS até a importação ser oficialmente suportada.
K - A toxicidade no SO está crescendo.
2

O módulo npm anyid fornece API flexível para gerar vários tipos de ID / código de string.

Para gerar uma sequência aleatória em A-Za-z0-9 usando 48 bytes aleatórios:

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

Para gerar apenas um alfabeto de comprimento fixo, sequência preenchida por bytes aleatórios:

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

Internamente, ele usa crypto.randomBytes()para gerar aleatoriamente.

aleung
fonte
1

Aqui está uma versão assíncrona tirada literalmente de cima da resposta de @Yves M.

var crypto = require('crypto');

function createCryptoString(length, chars) { // returns a promise which renders a crypto string

    if (!chars) { // provide default dictionary of chars if not supplied

        chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    }

    return new Promise(function(resolve, reject) {

        var charsLength = chars.length;
        if (charsLength > 256) {
            reject('parm chars length greater than 256 characters' +
                        ' masks desired key unpredictability');
        }

        var randomBytes = crypto.randomBytes(length);

        var result = new Array(length);

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

        resolve(result.join(''));
    });
}

// --- now generate crypto string async using promise --- /

var wantStringThisLength = 64; // will generate 64 chars of crypto secure string

createCryptoString(wantStringThisLength)
.then(function(newCryptoString) {

    console.log(newCryptoString); // answer here

}).catch(function(err) {

    console.error(err);
});
Scott Stensland
fonte
1

Função simples que fornece a você um token que é seguro para URL e possui codificação base64! É uma combinação de 2 respostas acima.

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