Zabezpiecz losowy token w Node.js


273

W tym pytaniu Erik musi wygenerować bezpieczny losowy token w Node.js. Istnieje metoda, crypto.randomBytesktóra generuje losowy bufor. Jednak kodowanie base64 w węźle nie jest bezpieczne dla adresu URL, obejmuje /i +zamiast -i _. Dlatego najprostszym sposobem na wygenerowanie takiego tokena, jaki znalazłem, jest

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

Czy istnieje bardziej elegancki sposób?


Jaka jest reszta kodu?
Lion789,

3
Nic więcej nie jest potrzebne. Co chciałbyś zobaczyć?
Hubert OG

Nieważne, udało mi się go uruchomić, po prostu nie byłem pewien, jak go wrzuciłeś, ale lepiej zrozumiałem tę koncepcję
Lion789,

1
Bezwstydna wtyczka, stworzyłem kolejny pakiet npm: tokgen . Dozwolone znaki można określić, używając składni zakresu podobnej do klas znaków w wyrażeniach regularnych ( 'a-zA-Z0-9_-').
Max Truxa,

1
Może to być wygodne dla każdego, kto chciałby mieć określoną długość łańcucha. 3/4 służy do obsługi konwersji podstawowej. / * zwraca zakodowany ciąg base64 o długości * / function randomString (długość) {return crypto.randomBytes (długość * 3/4) .toString ('base64'); } Działa dobrze dla tych baz danych z tymi limitami znaków.
TheUnknownGeek

Odpowiedzi:


353

Spróbuj crypto.randomBytes () :

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

Kodowanie „hex” działa w węźle v0.6.x lub nowszym.


3
To wydaje się lepsze, dzięki! Przydałoby się jednak kodowanie „base64-url”.
Hubert OG

2
Dziękuję za podpowiedź, ale myślę, że OP po prostu chciał standardowej już sekcji RFC 3548 4 „Kodowanie Base 64 z URL i bezpiecznym alfabetem nazwy pliku”. IMO zastąpienie znaków jest „wystarczająco eleganckie”.
natevw

8
Jeśli szukasz powyższego jako bash one-liner, możesz to zrobićnode -e "require('crypto').randomBytes(48, function(ex, buf) { console.log(buf.toString('hex')) });"
Dmitry Minkovsky

24
I zawsze możesz zrobić, buf.toString('base64')aby uzyskać numer zakodowany w Base64.
Dmitrij Minkowski

1
Zobacz ten automat poniżej dla kodowania podstawowego 64 za pomocą adresu URL i bezpiecznego alfabetu nazwy pliku
Yves M.

232

Opcja synchroniczna w przypadku, jeśli nie jesteś ekspertem JS takim jak ja. Musiałem poświęcić trochę czasu na dostęp do zmiennej funkcji wbudowanej

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

7
Również w przypadku, gdy nie chcesz mieć wszystkiego zagnieżdżonego. Dzięki!
Michael Ozeryansky

2
Chociaż to zdecydowanie działa, należy pamiętać, że w większości przypadków opcja asynchroniczna będzie pokazywana w odpowiedzi thejh.
Triforcey

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

1
@Triforcey czy możesz wyjaśnić, dlaczego zwykle chcesz mieć opcję asynchroniczną?
Thomas

2
@ thomas Obliczanie losowych danych może zająć trochę czasu w zależności od sprzętu. W niektórych przypadkach, jeśli komputerowi zabraknie przypadkowych danych, po prostu zwróci coś na swoim miejscu. Jednak w innych przypadkach możliwe jest, że komputer opóźni powrót losowych danych (co jest właściwie tym, czego chcesz), co spowoduje wolne połączenie.
Triforcey

80

0. Korzystanie z biblioteki stron trzecich nanoid [NOWOŚĆ!]

Mały, bezpieczny, przyjazny dla URL, unikalny generator ciągów znaków dla JavaScript

https://github.com/ai/nanoid

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


1. Kodowanie podstawowe 64 za pomocą bezpiecznego alfabetu URL i nazwy pliku

Strona 7 RCF 4648 opisuje sposób kodowania w bazie 64 z bezpieczeństwem adresów URL. Do wykonania zadania możesz użyć istniejącej biblioteki, takiej jak base64url .

Funkcja będzie:

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

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

Przykład użycia:

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

Zauważ, że zwrócona długość łańcucha nie będzie zgodna z argumentem size (size! = Długość końcowa).


2. Losowe wartości kryptograficzne z ograniczonego zestawu znaków

Uwaga: dzięki temu rozwiązaniu wygenerowany ciąg losowy nie jest równomiernie rozłożony.

Możesz również zbudować silny ciąg losowy z ograniczonego zestawu takich znaków:

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

Przykład użycia:

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

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

2
@Lexynux Solution 1 (kodowanie podstawowe 64 z URL i bezpiecznym alfabetem nazwy pliku ), ponieważ jest to najsilniejsze rozwiązanie pod względem bezpieczeństwa. To rozwiązanie koduje tylko klucz i nie zakłóca procesu produkcji klucza.
Yves M.

Dziękuję za wsparcie. Czy masz jakiś działający przykład, którym możesz podzielić się ze społecznością? Będzie mile widziane?
alexventuraio

6
Uważaj, aby wygenerowany ciąg losowy nie był równomiernie rozłożony. Łatwym przykładem na to jest to, że dla zestawu znaków o długości 255 i długości ciągu 1 szansa na pojawienie się pierwszego znaku jest dwa razy większa.
Florian Wendelborn,

@Dodekeract Tak, mówisz o rozwiązaniu 2 .. Właśnie dlatego rozwiązanie 1 jest o wiele silniejsze
Yves M.

Dodałem bibliotekę zewnętrzną nanoid w mojej odpowiedzi github.com/ai/nanoid
Yves M.

13

Aktualny właściwy sposób, aby to zrobić asynchronicznie przy użyciu standardów asynchronicznych i oczekujących na ES 2016 (od Node 7) byłby następujący:

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

Działa to natychmiast po wyjęciu z pudełka w węźle 7 bez żadnych transformacji Babel


Zaktualizowałem ten przykład, aby uwzględnić nowszą metodę przekazywania nazwanych parametrów, jak opisano tutaj: 2ality.com/2011/11/keyword-parameters.html
real_ate

7

Bezpieczny ciąg losowego adresu URL i nazwy pliku (1 linijka)

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

Cudowna odpowiedź w swojej prostocie! Pamiętaj tylko, że może on zablokować pętlę zdarzeń w sposób nieokreślony (dotyczy tylko sytuacji, gdy jest często używany, w nieco obciążonym, wrażliwym na czas systemie). W przeciwnym razie zrób to samo, ale używając asynchronicznej wersji randomBytes. Zobacz nodejs.org/api/…
Alec Thilenius

6

Sprawdzić:

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

Miły! Absolutnie niedocenione rozwiązanie. Byłoby wspaniale, gdyby zmienić nazwę „długość” na „pożądana długość” i zainicjować ją wartością przed użyciem :)
Florian Blum

Dla każdego, kto się zastanawia, połączenia ceili slicesą konieczne dla pożądanych długości, które są nieparzyste. W przypadku równych długości nic nie zmieniają.
Seth

6

Z asynchronizacją / czekaniem i promisification .

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

Generuje coś podobnego do VjocVHdFiz5vGHnlnwqJKN0NdeHcz8eM


4

Spójrz na real_atessposób ES2016, jest bardziej poprawny.

Sposób ECMAScript 2016 (ES7)

import crypto from 'crypto';

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

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

Generator / Yield Way

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 Rzeczywiście, JavaScript się aktualizuje :) Obietnice wyszukiwania i generatory!
K - Toksyczność w SO rośnie.

spróbuj poczekać, inny moduł obsługi obietnic ECMA7
Jain

Myślę, że powinieneś uczynić ES 2016 pierwszym przykładem, ponieważ w większości przypadków zmierza ona w kierunku „właściwego sposobu na zrobienie tego”
data rzeczywista

Dodałem własną odpowiedź poniżej, która była specyficzna dla Węzła (użycie wymaga zamiast importu). Czy był jakiś konkretny powód, dla którego korzystasz z importu? Czy masz uruchomioną babel?
real_ate

@real_ate Rzeczywiście byłem, wróciłem do używania CommonJS, dopóki import nie jest oficjalnie obsługiwany.
K - Toksyczność w SO rośnie.


2

Moduł npid anyid zapewnia elastyczny interfejs API do generowania różnego rodzaju ciągów znaków / kodów.

Aby wygenerować losowy ciąg znaków w A-Za-z0-9 przy użyciu 48 losowych bajtów:

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

Aby wygenerować ciąg alfabetu o stałej długości wypełniony losowymi bajtami:

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

Wewnętrznie używa crypto.randomBytes()do generowania losowego.


1

Oto wersja asynchroniczna wzięta dosłownie z góry odpowiedzi @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

Prosta funkcja, która daje tokenowi bezpieczny adres URL i kodowanie base64! To połączenie 2 odpowiedzi z góry.

const randomToken = () => {
    crypto.randomBytes(64).toString('base64').replace(/\//g,'_').replace(/\+/g,'-');
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.