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?


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.


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

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.

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


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, '');

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

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


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

@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.


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

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,'-');
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.