Token casuale sicuro in Node.js


274

In questa domanda Erik deve generare un token casuale sicuro in Node.js. C'è il metodo crypto.randomBytesche genera un buffer casuale. Tuttavia, la codifica base64 nel nodo non è sicura per l'URL, include /e +invece di -e _. Pertanto, il modo più semplice per generare tale token che ho trovato è

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

C'è un modo più elegante?


Qual è il resto del codice?
Lion789,

3
Non c'è più bisogno di altro. Che riposo ti piacerebbe vedere?
Hubert OG,

Non importa, l'ho fatto funzionare, non ero sicuro di come tu l'abbia lanciato, ma ho avuto una migliore comprensione del concetto
Lion789

1
Auto-plug spudorato, ho creato un altro pacchetto npm : tokgen . È possibile specificare i caratteri consentiti utilizzando una sintassi dell'intervallo simile alle classi di caratteri nelle espressioni regolari ( 'a-zA-Z0-9_-').
Max Truxa,

1
Questo può essere utile per chiunque desideri una lunghezza di stringa specifica. Il 3/4 è quello di gestire la conversione di base. / * restituisce una stringa codificata in base64 di lunghezza * / funzione randomString (lunghezza) {return crypto.randomBytes (lunghezza * 3/4) .toString ('base64'); } Funziona bene per quei database con quei limiti di caratteri.
TheUnnownGeek,

Risposte:


353

Prova crypto.randomBytes () :

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

La codifica "hex" funziona nel nodo v0.6.xo più recente.


3
Sembra meglio, grazie! Una codifica 'base64-url' sarebbe comunque carina.
Hubert OG,

2
Grazie per il suggerimento, ma penso che l'OP volesse semplicemente la già standard RFC 3548 sezione 4 "Codifica Base 64 con URL e nome file Alfabeto sicuro". IMO, la sostituzione dei personaggi è "abbastanza elegante".
natevw,

8
Se stai cercando quanto sopra come bash one-liner, puoi farlonode -e "require('crypto').randomBytes(48, function(ex, buf) { console.log(buf.toString('hex')) });"
Dmitry Minkovsky,

24
E puoi sempre fare buf.toString('base64')per ottenere un numero con codifica Base64.
Dmitry Minkovsky,

1
Vedi questa risposta di seguito per la codifica base 64 con URL e nome file Alfabeto sicuro
Yves M.

233

Opzione sincrona nel caso in cui non sei un esperto JS come me. Ho dovuto dedicare un po 'di tempo su come accedere alla variabile di funzione inline

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

7
Inoltre, nel caso in cui non si desideri che tutto sia nidificato. Grazie!
Michael Ozeryansky,

2
Anche se questo funziona sicuramente, nota che nella maggior parte dei casi vorrai dimostrare l'opzione asincrona nella risposta del jh.
Triforcey,

1
const generateToken = (): Promise<string> => new Promise(resolve => randomBytes(48, (err, buffer) => resolve(buffer.toString('hex'))));
yantrab,

1
@Triforcey puoi spiegare perché di solito vorresti l'opzione asincrona?
Thomas,

2
@thomas I dati casuali possono richiedere del tempo per essere calcolati a seconda dell'hardware. In alcuni casi, se il computer esaurisce i dati casuali, restituirà semplicemente qualcosa al suo posto. Tuttavia, in altri casi è possibile che il computer ritardi la restituzione di dati casuali (che in realtà è quello che vorresti) con conseguente chiamata lenta.
Triforcey,

80

0. Utilizzo della libreria di terze parti nanoide [NOVITÀ!]

Un generatore di ID stringa univoco, sicuro, intuitivo e URL-friendly per JavaScript

https://github.com/ai/nanoid

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


1. Codifica Base 64 con URL e nome file sicuro alfabeto

La pagina 7 di RCF 4648 descrive come codificare nella base 64 con sicurezza URL. È possibile utilizzare una libreria esistente come base64url per eseguire il lavoro.

La funzione sarà:

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

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

Esempio di utilizzo:

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

Si noti che la lunghezza della stringa restituita non corrisponderà all'argomento size (size! = Lunghezza finale).


2. Crypto valori casuali da un set limitato di caratteri

Attenzione che con questa soluzione la stringa casuale generata non è distribuita uniformemente.

Puoi anche creare una forte stringa casuale da un set limitato di caratteri come quello:

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

Esempio di utilizzo:

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

randomString(20, 'ABCDEFG');
// Returns 'CCBAAGDGBBEGBDBECDCE' which is 20 characters length.

2
@Lexynux Solution 1 (codifica Base 64 con URL e nome file Safe Alphabet) perché è la soluzione più efficace in termini di sicurezza. Questa soluzione codifica solo la chiave e non interferisce con il processo di produzione della chiave.
Yves M.,

Grazie per il vostro sostegno. Hai qualche esempio funzionante da condividere con la community? Sarà accolto?
alexventuraio,

6
Attenzione che la stringa casuale generata non è distribuita uniformemente. Un semplice esempio per mostrare questo è che, per un set di caratteri di lunghezza 255 e una lunghezza di stringa di 1, la probabilità che appaia il primo carattere appare due volte più alta.
Florian Wendelborn,

@Dodekeract Sì, stai parlando della soluzione 2 .. Ecco perché la soluzione 1 è molto più forte
Yves M.

Ho aggiunto una libreria nanoide di terze parti nella mia risposta github.com/ai/nanoid
Yves M.

13

Il modo giusto aggiornato per farlo in modo asincrono usando gli standard ES 2016 di asincrono e attesa (a partire dal Nodo 7) sarebbe il seguente:

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

Funziona immediatamente nel Nodo 7 senza alcuna trasformazione di Babel


Ho aggiornato questo esempio per incorporare il metodo più recente di passaggio dei parametri denominati come descritto qui: 2ality.com/2011/11/keyword-parameters.html
real_ate

7

URL casuale e stringa del nome file sicuri (1 linea)

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

Una risposta meravigliosa nella sua semplicità! Basta essere consapevoli del fatto che potrebbe bloccare il loop degli eventi in modo indeterminato (rilevante solo se utilizzato spesso, in un sistema un po 'carico, sensibile al tempo). Altrimenti, fai la stessa cosa, ma usando la versione asincrona di randomBytes. Vedi nodejs.org/api/…
Alec Thilenius,

6

Check-out:

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

Bello! Soluzione assolutamente sottovalutata. Sarebbe bello se rinominassi "lunghezza" in "desiderataLunghezza" e lo avvii con un valore prima di usarlo :)
Florian Blum,

Per chiunque si chieda, le chiamate ceile slicesono necessarie per le lunghezze desiderate che sono strane. Per lunghezze pari, non cambiano nulla.
Seth

6

Con asincrono / attendi e promisifica .

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

Genera qualcosa di simile a VjocVHdFiz5vGHnlnwqJKN0NdeHcz8eM


4

Guarda il real_atesmodo ES2016, è più corretto.

Modo ECMAScript 2016 (ES7)

import crypto from 'crypto';

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

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

Modo generatore / 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 In effetti, Javascript si sta aggiornando :) Promesse e generatori di ricerca!
K - La tossicità in SO sta crescendo.

prova ad aspettare, un altro gestore delle promesse dell'ECMA7
Jain

Penso che dovresti fare di ES 2016 il primo esempio su questo in quanto si sta muovendo verso il "modo giusto di farlo" nella maggior parte dei casi
real_ate

Di seguito ho aggiunto una mia risposta specifica per Nodo (utilizzando il comando request anziché import). C'è stato un motivo particolare per cui stai usando l'importazione? Hai una babele che corre?
real_ate

@real_ate Effettivamente, sono tornato a utilizzare CommonJS fino a quando l'importazione non è ufficialmente supportata.
K - La tossicità in SO sta crescendo.


2

Il modulo npm comunque fornisce API flessibile per generare vari tipi di ID / codice stringa.

Per generare una stringa casuale in A-Za-z0-9 usando 48 byte casuali:

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

Per generare l'alfabeto a lunghezza fissa solo stringa riempita da byte casuali:

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

Internamente utilizza crypto.randomBytes()per generare casuale.


1

Ecco una versione asincrona presa alla lettera dalla risposta di @Yves M. sopra

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

Semplice funzione che ti fornisce un token sicuro per gli URL e con codifica base64! È una combinazione di 2 risposte dall'alto.

const randomToken = () => {
    crypto.randomBytes(64).toString('base64').replace(/\//g,'_').replace(/\+/g,'-');
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.