Amankan token acak di Node.js

274

Dalam pertanyaan ini Erik perlu membuat token acak aman di Node.js. Ada metode crypto.randomBytesyang menghasilkan Buffer acak. Namun, pengkodean base64 dalam node tidak aman-url, itu termasuk /dan +bukannya -dan _. Karena itu, cara termudah untuk menghasilkan token semacam itu yang saya temukan adalah

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

Apakah ada cara yang lebih elegan?

Hubert OG
sumber
Apa sisa kode?
Lion789
3
Tidak ada yang lebih dibutuhkan. Istirahat apa yang ingin Anda lihat?
Hubert OG
Nevermind, saya berhasil, hanya tidak yakin bagaimana Anda memasukkannya, tetapi mendapat pemahaman yang lebih baik tentang konsep
Lion789
1
Shameless self-plug, saya membuat paket npm lainnya: tokgen . Anda dapat menentukan karakter yang diizinkan menggunakan sintaks rentang yang mirip dengan kelas karakter dalam ekspresi reguler ( 'a-zA-Z0-9_-').
Max Truxa
1
Ini mungkin nyaman bagi siapa saja yang menginginkan panjang string tertentu. 3/4 adalah untuk menangani konversi basis. / * mengembalikan string panjang yang disandikan base64 * / function randomString (length) {return crypto.randomBytes (length * 3/4) .toString ('base64'); } Berfungsi bagus untuk database itu dengan batasan karakter itu.
TheUnknownGeek

Jawaban:

353

Coba crypto.randomBytes () :

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

Pengkodean 'hex' bekerja di node v0.6.x atau lebih baru.

thejh
sumber
3
Tampaknya lebih baik, terima kasih! Pengkodean 'base64-url' akan lebih baik.
Hubert OG
2
Terima kasih atas tipnya, tapi saya pikir OP hanya menginginkan RFC 3548 bagian 4 yang sudah standar "Basis 64 Pengkodean dengan URL dan Nama File Alfabet Aman". IMO, mengganti karakter adalah "cukup elegan".
natevw
8
Jika Anda mencari yang di atas sebagai bash one-liner, Anda dapat melakukannyanode -e "require('crypto').randomBytes(48, function(ex, buf) { console.log(buf.toString('hex')) });"
Dmitry Minkovsky
24
Dan Anda selalu dapat melakukannya buf.toString('base64')untuk mendapatkan nomor yang disandikan Base64.
Dmitry Minkovsky
1
Lihat penjawab ini di bawah ini untuk pengkodean basis 64 dengan URL dan Nama File Alfabet Aman
Yves M.
233

Opsi sinkron kalau-kalau Anda bukan ahli JS seperti saya. Harus meluangkan waktu untuk mengakses variabel fungsi inline

var token = crypto.randomBytes(64).toString('hex');
phoenix2010
sumber
7
Juga jika Anda tidak ingin semuanya bersarang. Terima kasih!
Michael Ozeryansky
2
Meskipun ini benar-benar berfungsi, harap dicatat bahwa dalam kebanyakan kasus Anda ingin opsi async ditunjukkan dalam jawaban thejh.
Triforcey
1
const generateToken = (): Promise<string> => new Promise(resolve => randomBytes(48, (err, buffer) => resolve(buffer.toString('hex'))));
yantrab
1
@Triforcey dapatkah Anda menjelaskan mengapa Anda biasanya menginginkan opsi async?
thomas
2
@ Thomas data acak dapat memakan waktu cukup lama untuk dihitung tergantung pada perangkat keras. Dalam beberapa kasus jika komputer kehabisan data acak itu hanya akan mengembalikan sesuatu di tempatnya. Namun dalam kasus lain, ada kemungkinan komputer akan menunda pengembalian data acak (yang sebenarnya Anda inginkan) yang mengakibatkan panggilan lambat.
Triforcey
80

0. Menggunakan perpustakaan pihak ketiga nanoid [BARU!]

Generator ID string kecil, aman, ramah URL, unik untuk JavaScript

https://github.com/ai/nanoid

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


1. Basis 64 Pengkodean dengan URL dan Nama File Alfabet Aman

Halaman 7 dari RCF 4648 menjelaskan cara menyandikan di pangkalan 64 dengan keamanan URL. Anda dapat menggunakan perpustakaan yang ada seperti base64url untuk melakukan pekerjaan itu.

Fungsinya adalah:

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

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

Contoh penggunaan:

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

Perhatikan bahwa panjang string yang dikembalikan tidak akan cocok dengan argumen ukuran (ukuran! = Panjang akhir).


2. Nilai acak Crypto dari kumpulan karakter terbatas

Waspadalah bahwa dengan solusi ini string acak yang dihasilkan tidak terdistribusi secara merata.

Anda juga dapat membuat string acak yang kuat dari serangkaian karakter terbatas seperti itu:

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

Contoh penggunaan:

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

randomString(20, 'ABCDEFG');
// Returns 'CCBAAGDGBBEGBDBECDCE' which is 20 characters length.
Yves M.
sumber
2
@Lexynux Solution 1 (Basis 64 Pengkodean dengan URL dan Nama File Alfabet Aman) karena merupakan solusi terkuat dalam hal keamanan. Solusi ini hanya menyandikan kunci dan tidak mengganggu proses produksi kunci.
Yves M.
Terima kasih atas dukunganmu. Apakah Anda memiliki contoh kerja untuk dibagikan dengan komunitas? Itu akan disambut?
alexventuraio
6
Berhati-hatilah karena string acak yang dihasilkan tidak terdistribusi secara seragam. Contoh mudah untuk menunjukkan ini adalah, bahwa untuk rangkaian karakter panjang 255, dan panjang string 1, peluang karakter pertama muncul dua kali lebih tinggi.
Florian Wendelborn
@Dodekeract Ya, Anda berbicara tentang solusi 2 .. Itu sebabnya solusi 1 jauh lebih kuat
Yves M.
Saya telah menambahkan perpustakaan pihak ketiga nanoid di respons saya github.com/ai/nanoid
Yves M.
13

Cara tepat terkini untuk melakukan hal ini secara tidak sinkron menggunakan standar ES 2016 untuk async dan menunggu (mulai dari Node 7) adalah sebagai berikut:

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

Ini berfungsi di luar kotak di Node 7 tanpa transformasi Babel

real_ate
sumber
Saya telah memperbarui contoh ini untuk menggabungkan metode yang lebih baru untuk melewatkan parameter bernama seperti dijelaskan di sini: 2ality.com/2011/11/keyword-parameters.html
real_ate
7

URL acak dan string nama file aman (1 liner)

Crypto.randomBytes(48).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, '');
Kedem
sumber
Jawaban yang bagus dalam kesederhanaannya! Ketahuilah bahwa ini dapat menghentikan perulangan acara dengan cara yang tidak ditentukan (hanya relevan jika sering digunakan, dalam sistem yang agak sensitif dan sensitif waktu). Jika tidak, lakukan hal yang sama, tetapi gunakan versi async dari RandomBytes. Lihat nodejs.org/api/…
Alec Thilenius
6

Periksa:

var crypto = require('crypto');
crypto.randomBytes(Math.ceil(length/2)).toString('hex').slice(0,length);
sudam
sumber
Bagus! Solusi yang benar-benar diremehkan. Akan lebih bagus jika Anda mengganti nama "length" menjadi "diinginkanLength" dan memulai dengan nilai sebelum menggunakannya :)
Florian Blum
Bagi siapa pun yang bertanya-tanya, panggilan ceildan slicediperlukan untuk panjang yang diinginkan yang aneh. Bahkan untuk jangka panjang, mereka tidak mengubah apa pun.
Seth
6

Dengan async / menunggu dan promisifikasi .

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

Menghasilkan sesuatu yang mirip VjocVHdFiz5vGHnlnwqJKN0NdeHcz8eM

Znarkus
sumber
4

Lihatlah real_atescara ES2016, itu lebih benar.

Cara 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 / Cara Menghasilkan

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 - Toksisitas dalam SO meningkat.
sumber
@Jeffpowrs Memang, Javascript sedang ditingkatkan :) Janji Pencarian dan Generator!
K - Keracunan dalam SO tumbuh.
coba tunggu, penangan janji ECMA7 lainnya
Jain
Saya pikir Anda harus menjadikan ES 2016 contoh pertama tentang ini karena bergerak menuju "cara yang tepat untuk melakukannya" dalam banyak kasus
real_ate
Saya menambahkan jawaban saya sendiri di bawah ini yang khusus untuk Node (menggunakan memerlukan bukan impor). Apakah ada alasan khusus mengapa Anda menggunakan impor? Apakah Anda memiliki babel berjalan?
real_ate
@ real_ate Memang, saya telah kembali menggunakan CommonJS sampai impor didukung secara resmi.
K - Keracunan dalam SO tumbuh.
2

Modul npm anyid menyediakan API fleksibel untuk menghasilkan berbagai jenis string ID / kode.

Untuk menghasilkan string acak di A-Za-z0-9 menggunakan 48 byte acak:

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

Untuk menghasilkan alfabet panjang tetap, hanya string yang diisi oleh byte acak:

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

Secara internal itu digunakan crypto.randomBytes()untuk menghasilkan acak.

aleung
sumber
1

Ini adalah versi async yang diambil secara verbatim dari jawaban @Yves M. di atas

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

Fungsi sederhana yang memberi Anda token yang aman URL dan memiliki encoding base64! Ini kombinasi dari 2 jawaban dari atas.

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