Sicheres zufälliges Token in Node.js

272

In dieser Frage muss Erik ein sicheres zufälliges Token in Node.js generieren. Es gibt die Methode crypto.randomBytes, die einen zufälligen Puffer generiert. Die Base64-Codierung im Knoten ist jedoch nicht url-sicher, sondern enthält /und +anstelle von -und _. Daher ist der einfachste Weg, ein solches Token zu generieren, das ich gefunden habe, der

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

Gibt es einen eleganteren Weg?

Hubert OG
quelle
Was ist der Rest des Codes?
Lion789
3
Es wird nichts mehr benötigt. Welchen Rest möchten Sie sehen?
Hubert OG
Nevermind, ich habe es zum Laufen gebracht, war mir nur nicht sicher, wie Sie es
hineingeworfen haben
1
Schamloser Selbststecker, ich habe noch ein weiteres npm-Paket erstellt: tokgen . Sie können zulässige Zeichen mithilfe einer Bereichssyntax angeben, die Zeichenklassen in regulären Ausdrücken ( 'a-zA-Z0-9_-') ähnelt .
Max Truxa
1
Dies kann für jeden praktisch sein, der eine bestimmte Zeichenfolgenlänge möchte. Das 3/4 soll die Basiskonvertierung übernehmen. / * gibt eine base64-codierte Zeichenfolge der Länge zurück * / function randomString (length) {return crypto.randomBytes (length * 3/4) .toString ('base64'); } Funktioniert gut für Datenbanken mit diesen Zeichenbeschränkungen.
TheUnknownGeek

Antworten:

353

Versuchen Sie crypto.randomBytes () :

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

Die 'Hex'-Codierung funktioniert in Knoten v0.6.x oder neuer.

thejh
quelle
3
Das scheint besser, danke! Eine 'base64-url'-Codierung wäre allerdings nett.
Hubert OG
2
Vielen Dank für den Tipp, aber ich denke, das OP wollte einfach den bereits standardmäßigen RFC 3548 Abschnitt 4 "Base 64-Codierung mit URL und Dateiname Safe Alphabet". IMO, das Ersetzen der Zeichen ist "elegant genug".
Natevw
8
Wenn Sie nach dem oben genannten als Bash-Einzeiler suchen, können Sie dies tunnode -e "require('crypto').randomBytes(48, function(ex, buf) { console.log(buf.toString('hex')) });"
Dmitry Minkovsky
24
Und Sie können immer buf.toString('base64')eine Base64-codierte Nummer erhalten.
Dmitry Minkovsky
1
Siehe diese Antwort unten für die Base 64-Codierung mit URL und Dateiname Safe Alphabet
Yves M.
231

Synchrone Option für den Fall, dass Sie kein JS-Experte wie ich sind. Musste einige Zeit damit verbringen, auf die Inline-Funktionsvariable zuzugreifen

var token = crypto.randomBytes(64).toString('hex');
phoenix2010
quelle
7
Auch für den Fall, dass Sie nicht alles verschachteln möchten. Vielen Dank!
Michael Ozeryansky
2
Beachten Sie jedoch, dass in den meisten Fällen die in der Antwort von thejh gezeigte asynchrone Option angezeigt werden soll.
Triforcey
1
const generateToken = (): Promise<string> => new Promise(resolve => randomBytes(48, (err, buffer) => resolve(buffer.toString('hex'))));
Yantrab
1
@Triforcey können Sie erklären, warum Sie normalerweise die asynchrone Option möchten?
Thomas
2
@thomas Die Berechnung zufälliger Daten kann je nach Hardware eine Weile dauern. In einigen Fällen, wenn dem Computer die zufälligen Daten ausgehen, wird nur etwas an seiner Stelle zurückgegeben. In anderen Fällen ist es jedoch möglich, dass der Computer die Rückgabe zufälliger Daten verzögert (was eigentlich das ist, was Sie möchten), was zu einem langsamen Anruf führt.
Triforcey
80

0. Verwenden einer Nanoid-Drittanbieter-Bibliothek [NEU!]

Ein winziger, sicherer, URL-freundlicher, eindeutiger String-ID-Generator für JavaScript

https://github.com/ai/nanoid

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


1. Base 64-Codierung mit URL und Dateiname Safe Alphabet

Seite 7 von RCF 4648 beschreibt, wie in Base 64 mit URL-Sicherheit codiert wird. Sie können eine vorhandene Bibliothek wie base64url verwenden , um die Aufgabe zu erledigen.

Die Funktion wird sein:

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

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

Anwendungsbeispiel:

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

Beachten Sie, dass die zurückgegebene Zeichenfolgenlänge nicht mit dem Größenargument übereinstimmt (Größe! = Endlänge).


2. Krypto-Zufallswerte aus einem begrenzten Zeichensatz

Beachten Sie, dass bei dieser Lösung die generierte Zufallszeichenfolge nicht gleichmäßig verteilt ist.

Sie können auch eine starke zufällige Zeichenfolge aus einer begrenzten Anzahl solcher Zeichen erstellen:

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

Anwendungsbeispiel:

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

randomString(20, 'ABCDEFG');
// Returns 'CCBAAGDGBBEGBDBECDCE' which is 20 characters length.
Yves M.
quelle
2
@ Lexynux- Lösung 1 (Base 64-Codierung mit URL und Dateiname Safe Alphabet), da dies die sicherste Lösung ist. Diese Lösung codiert nur den Schlüssel und stört den Schlüsselproduktionsprozess nicht.
Yves M.
Danke für deine Unterstützung. Haben Sie ein Arbeitsbeispiel, das Sie mit der Community teilen können? Es wird begrüßt?
Alexventuraio
6
Beachten Sie, dass die generierte Zufallszeichenfolge nicht gleichmäßig verteilt ist. Ein einfaches Beispiel dafür ist, dass bei einem Zeichensatz mit einer Länge von 255 und einer Zeichenfolgenlänge von 1 die Wahrscheinlichkeit, dass das erste Zeichen erscheint, doppelt so hoch ist.
Florian Wendelborn
@Dodekeract Ja, Sie sprechen über Lösung 2. Deshalb ist Lösung 1 viel stärker
Yves M.
Ich habe in meiner Antwort github.com/ai/nanoid
Yves M.
13

Der aktuell richtige Weg , dies asynchron unter Verwendung der ES 2016-Standards für Async und Warten (ab Knoten 7) zu tun, wäre der folgende:

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

Dies funktioniert sofort in Knoten 7 ohne Babel-Transformationen

real_ate
quelle
Ich habe dieses Beispiel aktualisiert, um die neuere Methode zum Übergeben benannter Parameter zu integrieren, wie hier beschrieben: 2ality.com/2011/11/keyword-parameters.html
real_ate
7

Zufällige URL und Dateinamenzeichenfolge sicher (1 Liner)

Crypto.randomBytes(48).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, '');
Kedem
quelle
Eine wunderbare Antwort in seiner Einfachheit! Beachten Sie jedoch, dass die Ereignisschleife auf unbestimmte Weise blockiert werden kann (nur relevant, wenn sie häufig in einem etwas belasteten, zeitkritischen System verwendet wird). Andernfalls machen Sie dasselbe, aber verwenden Sie die asynchrone Version von randomBytes. Siehe nodejs.org/api/…
Alec Thilenius
6

Auschecken:

var crypto = require('crypto');
crypto.randomBytes(Math.ceil(length/2)).toString('hex').slice(0,length);
Sudam
quelle
Nett! Absolut unterschätzte Lösung. Wäre toll, wenn Sie "Länge" in "gewünschte Länge" umbenennen und mit einem Wert initiieren, bevor Sie es verwenden :)
Florian Blum
Für alle, die sich fragen, sind die Aufrufe ceilund slicefür die gewünschten ungeraden Längen erforderlich. Für gerade Längen ändern sie nichts.
Seth
6

Mit Async / Warten und Versprechen .

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

Erzeugt etwas Ähnliches wie VjocVHdFiz5vGHnlnwqJKN0NdeHcz8eM

Znarkus
quelle
4

Schauen Sie sich real_atesES2016 an, es ist korrekter.

ECMAScript 2016 (ES7) Weg

import crypto from 'crypto';

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

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

Generator / Ertragsweg

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 - Die Toxizität in SO nimmt zu.
quelle
@ Jeffpowrs In der Tat wird Javascript aktualisiert :) Lookup-Versprechen und Generatoren!
K - Die Toxizität in SO nimmt zu.
Versuchen Sie zu warten, ein weiterer ECMA7 Versprechen Handler
Jain
Ich denke, dass Sie die ES 2016 zum ersten Beispiel dafür machen sollten, da sie sich in den meisten Fällen in Richtung des "richtigen Weges"
bewegt
Ich habe unten eine eigene Antwort hinzugefügt, die spezifisch für Node war (mit require anstelle von import). Gab es einen bestimmten Grund, warum Sie den Import verwenden? Hast du Babel am Laufen?
Real_ate
@real_ate In der Tat habe ich wieder CommonJS verwendet, bis der Import offiziell unterstützt wird.
K - Die Toxizität in SO nimmt zu.
2

Das npm-Modul anyid bietet eine flexible API zum Generieren verschiedener Arten von Zeichenfolgen-IDs / .

So generieren Sie eine zufällige Zeichenfolge in A-Za-z0-9 mit 48 zufälligen Bytes:

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

So generieren Sie nur eine Zeichenfolge mit fester Länge, die mit zufälligen Bytes gefüllt ist:

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

Intern wird es verwendet crypto.randomBytes(), um zufällig zu generieren.

Aleung
quelle
1

Hier ist eine asynchrone Version, die wörtlich aus der Antwort von @Yves M. stammt

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
quelle
1

Einfache Funktion, mit der Sie ein Token erhalten, das URL-sicher ist und Base64-Codierung hat! Es ist eine Kombination aus 2 Antworten von oben.

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