Token aleatorio seguro en Node.js

274

En esta pregunta, Erik necesita generar un token aleatorio seguro en Node.js. Existe el método crypto.randomBytesque genera un búfer aleatorio. Sin embargo, la codificación base64 en el nodo no es segura para URL, incluye /y en +lugar de -y _. Por lo tanto, la forma más fácil de generar ese token que he encontrado es

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

¿Hay alguna forma más elegante?

Hubert OG
fuente
¿Cuál es el resto del código?
Lion789
3
No hay nada más necesario. ¿Qué descanso te gustaría ver?
Hubert OG
No importa, lo puse a trabajar, no estaba seguro de cómo lo
lanzaste
1
Autoenchufe descarado, creé otro paquete npm : tokgen . Puede especificar caracteres permitidos utilizando una sintaxis de rango similar a las clases de caracteres en expresiones regulares ( 'a-zA-Z0-9_-').
Max Truxa
1
Esto puede ser conveniente para cualquier persona que desee una longitud de cadena específica. El 3 / 4th es para manejar la conversión de base. / * devuelve una cadena codificada en base64 de longitud * / function randomString (length) {return crypto.randomBytes (length * 3/4) .toString ('base64'); } Funciona bien para esas bases de datos con esos límites de caracteres.
TheUnknownGeek

Respuestas:

353

Prueba crypto.randomBytes () :

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

La codificación 'hexadecimal' funciona en el nodo v0.6.xo posterior.

thejh
fuente
3
Eso parece mejor, gracias! Sin embargo, una codificación 'base64-url' sería buena.
Hubert OG
2
Gracias por el consejo, pero creo que el OP simplemente quería el RFC 3548 ya estándar, sección 4 "Codificación Base 64 con URL y alfabeto seguro de nombre de archivo". En mi opinión, reemplazar a los personajes es "lo suficientemente elegante".
natevw
8
Si está buscando lo anterior como un golpe de línea, puede hacerlonode -e "require('crypto').randomBytes(48, function(ex, buf) { console.log(buf.toString('hex')) });"
Dmitry Minkovsky
24
Y siempre puede hacer buf.toString('base64')para obtener un número codificado en Base64.
Dmitry Minkovsky
1
Vea esta respuesta a continuación para la codificación de base 64 con URL y alfabeto seguro de nombre de archivo
Yves M.
233

Opción síncrona en caso de que no sea un experto en JS como yo. Tuve que pasar algún tiempo sobre cómo acceder a la variable de función en línea

var token = crypto.randomBytes(64).toString('hex');
phoenix2010
fuente
77
También en caso de que no quieras tener todo anidado. ¡Gracias!
Michael Ozeryansky
2
Si bien esto definitivamente funciona, tenga en cuenta que en la mayoría de los casos querrá que la opción asíncrona se demuestre en la respuesta de thejh.
Triforcey
1
const generateToken = (): Promise<string> => new Promise(resolve => randomBytes(48, (err, buffer) => resolve(buffer.toString('hex'))));
yantrab
1
@ Triforcey, ¿puede explicar por qué generalmente desea la opción asíncrona?
Thomas
2
@thomas Los datos aleatorios pueden tardar un tiempo en calcularse según el hardware. En algunos casos, si la computadora se queda sin datos aleatorios, simplemente devolverá algo en su lugar. Sin embargo, en otros casos, es posible que la computadora retrase el retorno de datos aleatorios (que en realidad es lo que desearía), lo que resulta en una llamada lenta.
Triforcey
80

0. Uso de la biblioteca de terceros nanoid [¡NUEVO!]

Un generador de ID de cadena único, pequeño, seguro, amigable con URL para JavaScript

https://github.com/ai/nanoid

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


1. Codificación Base 64 con alfabeto seguro de URL y nombre de archivo

La página 7 de RCF 4648 describe cómo codificar en base 64 con seguridad de URL. Puede usar una biblioteca existente como base64url para hacer el trabajo.

La función será:

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

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

Ejemplo de uso:

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

Tenga en cuenta que la longitud de la cadena devuelta no coincidirá con el argumento de tamaño (tamaño! = Longitud final).


2. Valores criptográficos aleatorios de un conjunto limitado de caracteres

Tenga en cuenta que con esta solución la cadena aleatoria generada no se distribuye uniformemente.

También puede crear una cadena aleatoria fuerte a partir de un conjunto limitado de caracteres como ese:

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

Ejemplo de uso:

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

randomString(20, 'ABCDEFG');
// Returns 'CCBAAGDGBBEGBDBECDCE' which is 20 characters length.
Yves M.
fuente
2
@Lexynux Solution 1 (codificación Base 64 con URL y alfabeto seguro de nombre de archivo) porque es la solución más sólida en términos de seguridad. Esta solución solo codifica la clave y no interfiere con el proceso de producción de la clave.
Yves M.
Gracias por su apoyo. ¿Tienes algún ejemplo de trabajo para compartir con la comunidad? Será bien recibido?
alexventuraio
66
Tenga en cuenta que la cadena aleatoria generada no está distribuida uniformemente. Un ejemplo sencillo para mostrar esto es que, para un conjunto de caracteres de longitud 255 y una longitud de cadena de 1, la posibilidad de que aparezca el primer carácter es el doble.
Florian Wendelborn
@Dodekeract Sí, estás hablando de la solución 2 .. Es por eso que la solución 1 es mucho más fuerte
Yves M.
Agregué una biblioteca de terceros nanoid en mi respuesta github.com/ai/nanoid
Yves M.
13

La forma correcta y actualizada de hacer esto de forma asíncrona utilizando los estándares ES 2016 de asíncrono y espera (a partir del Nodo 7) sería la siguiente:

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

Esto funciona de forma inmediata en el Nodo 7 sin ninguna transformación de Babel

real_ate
fuente
He actualizado este ejemplo para incorporar el método más nuevo de pasar parámetros con nombre como se describe aquí: 2ality.com/2011/11/keyword-parameters.html
real_ate
7

URL aleatoria y cadena de nombre de archivo segura (1 liner)

Crypto.randomBytes(48).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, '');
Kedem
fuente
¡Una respuesta maravillosa en su simplicidad! Solo tenga en cuenta que podría detener el bucle de eventos de una manera indeterminada (solo relevante si se usa a menudo, en un sistema algo cargado y sensible al tiempo). De lo contrario, haga lo mismo, pero utilizando la versión asincrónica de randomBytes. Ver nodejs.org/api/…
Alec Thilenius
6

Revisa:

var crypto = require('crypto');
crypto.randomBytes(Math.ceil(length/2)).toString('hex').slice(0,length);
sudam
fuente
¡Agradable! Solución absolutamente subestimada. Sería genial si cambias el nombre de "longitud" a "deseadoLongitud" y lo inicias con un valor antes de usarlo :)
Florian Blum
Para cualquiera que se pregunte, las llamadas ceily sliceson necesarias para las longitudes deseadas que son extrañas. Para longitudes pares, no cambian nada.
Seth
6

Con asíncrono / espera y promisificación .

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

Genera algo similar a VjocVHdFiz5vGHnlnwqJKN0NdeHcz8eM

Znarkus
fuente
4

Mire la real_atesforma ES2016, es más correcto.

Forma ECMAScript 2016 (ES7)

import crypto from 'crypto';

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

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

Generador / Rendimiento

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 - La toxicidad en SO está creciendo.
fuente
@Jeffpowrs De hecho, Javascript se está actualizando :) ¡Promesas de búsqueda y generadores!
K - La toxicidad en SO está creciendo.
intente esperar, otro controlador de promesa ECMA7
Jain
Creo que debería hacer del ES 2016 el primer ejemplo de esto, ya que se está moviendo hacia la "forma correcta de hacerlo" en la mayoría de los casos
real_ate
Agregué una respuesta propia que era específica para Node (usando require en lugar de import). ¿Hubo alguna razón en particular por la que está utilizando la importación? ¿Tienes a Babel corriendo?
real_ate
@real_ate De hecho, lo hice, volví a usar CommonJS hasta que se admite oficialmente la importación.
K - La toxicidad en SO está creciendo.
2

El módulo nid anyid proporciona una API flexible para generar varios tipos de ID / código de cadena.

Para generar una cadena aleatoria en A-Za-z0-9 usando 48 bytes aleatorios:

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

Para generar una cadena de alfabeto de longitud fija solo llena por bytes aleatorios:

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

Internamente se utiliza crypto.randomBytes()para generar al azar.

aleung
fuente
1

Aquí hay una versión asíncrona tomada literalmente de arriba Respuesta 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
fuente
1

¡Función simple que le proporciona un token que es seguro para URL y tiene codificación base64! Es una combinación de 2 respuestas de arriba.

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