Jeton aléatoire sécurisé dans Node.js

274

Dans cette question, Erik doit générer un jeton aléatoire sécurisé dans Node.js. Il y a la méthode crypto.randomBytesqui génère un tampon aléatoire. Cependant, le codage base64 dans le nœud n'est pas sûr pour les URL, il inclut /et +au lieu de -et _. Par conséquent, le moyen le plus simple de générer un tel jeton que j'ai trouvé est

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

Existe-t-il une manière plus élégante?

Hubert OG
la source
Quel est le reste du code?
Lion789
3
Il n'y a rien de plus nécessaire. Quel repos aimeriez-vous voir?
Hubert OG
Peu importe
1
Auto-plug sans vergogne , j'ai créé un autre package npm: tokgen . Vous pouvez spécifier des caractères autorisés à l'aide d'une syntaxe de plage similaire aux classes de caractères dans les expressions régulières ( 'a-zA-Z0-9_-').
Max Truxa
1
Cela peut être pratique pour tous ceux qui souhaitent une longueur de chaîne spécifique. Le 3 / 4ème est de gérer la conversion de base. / * renvoie une chaîne encodée en base64 de longueur * / fonction randomString (longueur) {return crypto.randomBytes (longueur * 3/4) .toString ('base64'); } Fonctionne bien pour ces bases de données avec ces limites de caractères.
TheUnknownGeek

Réponses:

353

Essayez crypto.randomBytes () :

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

L'encodage «hexadécimal» fonctionne dans le nœud v0.6.x ou plus récent.

thejh
la source
3
Cela semble mieux, merci! Un encodage 'base64-url' serait bien, cependant.
Hubert OG
2
Merci pour l'astuce, mais je pense que l'OP voulait simplement le RFC 3548 section 4 "Encodage Base 64 avec URL et nom de fichier Safe Alphabet" déjà standard. OMI, remplacer les caractères est "assez élégant".
natevw
8
Si vous recherchez ce qui précède comme un bash one-liner, vous pouvez le fairenode -e "require('crypto').randomBytes(48, function(ex, buf) { console.log(buf.toString('hex')) });"
Dmitry Minkovsky
24
Et vous pouvez toujours le faire buf.toString('base64')pour obtenir un numéro codé en Base64.
Dmitry Minkovsky
1
Voir ce répondeur ci-dessous pour l' encodage base 64 avec URL et nom de fichier Safe Alphabet
Yves M.
233

Option synchrone au cas où vous n'êtes pas un expert JS comme moi. J'ai dû passer un peu de temps sur la façon d'accéder à la variable de fonction en ligne

var token = crypto.randomBytes(64).toString('hex');
phoenix2010
la source
7
Même si vous ne voulez pas que tout soit imbriqué. Merci!
Michael Ozeryansky
2
Bien que cela fonctionne définitivement, notez que dans la plupart des cas, vous souhaiterez que l'option async soit démontrée dans la réponse de theh.
Triforcey
1
const generateToken = (): Promise<string> => new Promise(resolve => randomBytes(48, (err, buffer) => resolve(buffer.toString('hex'))));
yantrab
1
@Triforcey pouvez-vous expliquer pourquoi vous souhaitez généralement l'option asynchrone?
thomas
2
@thomas Les données aléatoires peuvent prendre un certain temps à calculer en fonction du matériel. Dans certains cas, si l'ordinateur manque de données aléatoires, il retournera simplement quelque chose à sa place. Cependant, dans d'autres cas, il est possible que l'ordinateur retarde le retour de données aléatoires (ce qui est en fait ce que vous souhaitez), ce qui entraîne un appel lent.
Triforcey
80

0. Utilisation d'une bibliothèque tierce nanoïde [NOUVEAU!]

Un minuscule générateur d'ID de chaîne unique, sécurisé, convivial et compatible URL pour JavaScript

https://github.com/ai/nanoid

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


1. Encodage Base 64 avec URL et nom de fichier Safe Alphabet

La page 7 du RCF 4648 décrit comment coder en base 64 avec la sécurité des URL. Vous pouvez utiliser une bibliothèque existante comme base64url pour faire le travail.

La fonction sera:

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

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

Exemple d'utilisation:

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

Notez que la longueur de chaîne retournée ne correspondra pas à l'argument taille (taille! = Longueur finale).


2. Valeurs aléatoires cryptographiques d'un ensemble limité de caractères

Attention, avec cette solution, la chaîne aléatoire générée n'est pas uniformément distribuée.

Vous pouvez également créer une chaîne aléatoire forte à partir d'un ensemble limité de caractères comme celui-ci:

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

Exemple d'utilisation:

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

randomString(20, 'ABCDEFG');
// Returns 'CCBAAGDGBBEGBDBECDCE' which is 20 characters length.
Yves M.
la source
2
@Lexynux Solution 1 (Encodage Base 64 avec URL et nom de fichier Safe Alphabet) car c'est la solution la plus solide en terme de sécurité. Cette solution n'encode que la clé et n'interfère pas avec le processus de production de la clé.
Yves M.
Merci pour votre aide. Avez-vous un exemple de travail à partager avec la communauté? Il sera le bienvenu?
alexventuraio
6
Attention, la chaîne aléatoire générée n'est pas uniformément distribuée. Un exemple simple pour le montrer est que, pour un jeu de caractères de longueur 255 et une longueur de chaîne de 1, la probabilité que le premier caractère apparaisse est deux fois plus élevée.
Florian Wendelborn
@Dodekeract Oui, vous parlez de la solution 2 .. C'est pourquoi la solution 1 est beaucoup plus forte
Yves M.
J'ai ajouté une bibliothèque tierce nanoid dans ma réponse github.com/ai/nanoid
Yves M.
13

La bonne façon à jour de le faire de manière asynchrone en utilisant les normes ES 2016 d'async et d'attente (à partir du nœud 7) serait la suivante:

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

Cela fonctionne hors de la boîte dans le nœud 7 sans aucune transformation Babel

real_ate
la source
J'ai mis à jour cet exemple pour incorporer la nouvelle méthode de transmission des paramètres nommés comme décrit ici: 2ality.com/2011/11/keyword-parameters.html
real_ate
7

URL aléatoire et chaîne de nom de fichier sécurisée (1 doublure)

Crypto.randomBytes(48).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, '');
Kedem
la source
Une merveilleuse réponse dans sa simplicité! Sachez simplement que cela pourrait bloquer la boucle d'événements de manière indéterministe (uniquement pertinent s'il est utilisé souvent, dans un système quelque peu chargé et sensible au temps). Sinon, faites la même chose, mais en utilisant la version asynchrone de randomBytes. Voir nodejs.org/api/…
Alec Thilenius
6

Check-out:

var crypto = require('crypto');
crypto.randomBytes(Math.ceil(length/2)).toString('hex').slice(0,length);
sudam
la source
Agréable! Solution absolument sous-estimée. Ce serait génial si vous renommez «longueur» en «longueur souhaitée» et l'initiez avec une valeur avant de l'utiliser :)
Florian Blum
Pour tous ceux qui se demandent, les appels ceilet slicesont nécessaires pour les longueurs désirées qui sont impaires. Pour des longueurs égales, ils ne changent rien.
Seth
6

Avec async / wait et promisification .

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

Génère quelque chose de similaire à VjocVHdFiz5vGHnlnwqJKN0NdeHcz8eM

Znarkus
la source
4

Regardez la real_atesmanière ES2016, c'est plus correct.

ECMAScript 2016 (ES7) façon

import crypto from 'crypto';

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

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

Générateur / rendement

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 toxicité du SO augmente.
la source
@Jeffpowrs En effet, Javascript met à jour :) Promesses de recherche et générateurs!
K - La toxicité du SO augmente.
essayez d'attendre, un autre gestionnaire de promesses ECMA7
Jain
Je pense que vous devriez faire de l'ES 2016 le premier exemple à ce sujet car il se dirige vers la "bonne façon de le faire" dans la plupart des cas
real_ate
J'ai ajouté ma propre réponse ci-dessous qui était spécifique à Node (en utilisant require au lieu d'importer). Y avait-il une raison particulière pour laquelle vous utilisez l'importation? Avez-vous babel en cours d'exécution?
real_ate
@real_ate En effet, j'étais revenu à l'utilisation de CommonJS jusqu'à ce que l'importation soit officiellement prise en charge.
K - La toxicité du SO augmente.
2

Le module npm anyid fournit une API flexible pour générer différents types d'ID / code de chaîne.

Pour générer une chaîne aléatoire dans A-Za-z0-9 en utilisant 48 octets aléatoires:

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

Pour générer uniquement une chaîne d'alphabet de longueur fixe remplie d'octets aléatoires:

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

En interne, il utilise crypto.randomBytes()pour générer de façon aléatoire.

aleung
la source
1

Voici une version asynchrone prise mot pour mot d'en haut @Yves M.'s answer

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
la source
1

Fonction simple qui vous permet d'obtenir un jeton sûr pour les URL et doté d'un encodage base64! C'est une combinaison de 2 réponses d'en haut.

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