Bảo mật mã thông báo ngẫu nhiên trong Node.js


272

Trong câu hỏi này, Erik cần tạo mã thông báo ngẫu nhiên an toàn trong Node.js. Có phương pháp crypto.randomBytestạo Bộ đệm ngẫu nhiên. Tuy nhiên, mã hóa base64 trong nút không an toàn cho url, nó bao gồm /+thay vì -_. Do đó, cách dễ nhất để tạo mã thông báo như vậy tôi đã tìm thấy là

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

Có cách nào thanh lịch hơn?


Phần còn lại của mã là gì?
Lion789

3
Không có gì cần thiết hơn. Những gì còn lại bạn muốn xem?
Hubert OG

Không bao giờ, tôi đã làm cho nó hoạt động, chỉ không chắc chắn về cách bạn đã ném nó vào, nhưng đã hiểu rõ hơn về khái niệm này
Lion789

1
Không biết tự cắm, tôi đã tạo một gói npm khác : tokgen . Bạn có thể chỉ định các ký tự được phép bằng cách sử dụng cú pháp phạm vi tương tự như các lớp ký tự trong các biểu thức thông thường ( 'a-zA-Z0-9_-').
Max Truxa

1
Điều này có thể thuận tiện cho bất cứ ai muốn có độ dài chuỗi cụ thể. 3/4 là để xử lý chuyển đổi cơ sở. / * trả về một chuỗi độ dài được mã hóa base64 * / function RandomString (length) {return crypto.randomBytes (length * 3/4) .toString ('base64'); } Hoạt động tốt cho những cơ sở dữ liệu với các giới hạn ký tự.
TheUn UnknownGeek

Câu trả lời:


353

Hãy thử crypto.randomBytes () :

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

Mã hóa 'hex' hoạt động trong nút v0.6.x hoặc mới hơn.


3
Điều đó có vẻ tốt hơn, cảm ơn! Mặc dù vậy, mã hóa 'base64-url' sẽ tốt.
Hubert OG

2
Cảm ơn vì tiền boa, nhưng tôi nghĩ OP chỉ đơn giản muốn RFC 3548 đã được chuẩn hóa phần 4 "Mã hóa cơ sở 64 với URL và tên tệp bảng chữ cái an toàn". IMO, thay thế các ký tự là "đủ thanh lịch".
natevw

8
Nếu bạn đang tìm kiếm những thứ ở trên dưới dạng bash one-liner, bạn có thể làmnode -e "require('crypto').randomBytes(48, function(ex, buf) { console.log(buf.toString('hex')) });"
Dmitry Minkovsky

24
Và bạn luôn có thể làm buf.toString('base64')để có được số được mã hóa Base64.
Dmitry Minkovsky

1
Xem người trả lời này bên dưới để mã hóa cơ sở 64 với URL và Tên tệp Bảng chữ cái an toàn
Yves M.

231

Tùy chọn đồng bộ trong trường hợp nếu bạn không phải là chuyên gia về JS như tôi. Phải dành một chút thời gian để làm thế nào để truy cập vào biến chức năng nội tuyến

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

7
Ngoài ra trong trường hợp bạn không muốn mọi thứ được lồng vào nhau. Cảm ơn!
Michael Ozeryansky

2
Mặc dù điều này chắc chắn hoạt động, xin lưu ý rằng trong hầu hết các trường hợp, bạn sẽ muốn tùy chọn async được thể hiện trong câu trả lời của thejh.
Triforcey

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

1
@Triforcey bạn có thể giải thích lý do tại sao bạn thường muốn tùy chọn async không?
thomas

2
@thomas Dữ liệu ngẫu nhiên có thể mất một lúc để tính toán tùy thuộc vào phần cứng. Trong một số trường hợp nếu máy tính hết dữ liệu ngẫu nhiên, nó sẽ trả lại một cái gì đó ở vị trí của nó. Tuy nhiên, trong các trường hợp khác, có thể máy tính sẽ trì hoãn việc trả lại dữ liệu ngẫu nhiên (đó thực sự là những gì bạn muốn) dẫn đến một cuộc gọi chậm.
Triforcey

80

0. Sử dụng thư viện bên thứ ba nanoid [MỚI!]

Trình tạo ID chuỗi duy nhất, an toàn, thân thiện với URL, dành cho JavaScript

https://github.com/ai/nanoid

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


1. Mã hóa cơ sở 64 với URL và tên tệp Bảng chữ cái an toàn

Trang 7 của RCF 4648 mô tả cách mã hóa trong cơ sở 64 với an toàn URL. Bạn có thể sử dụng một thư viện hiện có như base64url để thực hiện công việc.

Chức năng sẽ là:

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

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

Ví dụ sử dụng:

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

Lưu ý rằng độ dài chuỗi được trả về sẽ không khớp với đối số kích thước (size! = Chiều dài cuối cùng).


2. Giá trị ngẫu nhiên của tiền điện tử từ bộ ký tự giới hạn

Coi chừng với giải pháp này, chuỗi ngẫu nhiên được tạo ra không được phân phối đồng đều.

Bạn cũng có thể xây dựng một chuỗi ngẫu nhiên mạnh mẽ từ một bộ ký tự giới hạn như thế:

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

Ví dụ sử dụng:

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

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

2
@Lexynux Giải pháp 1 (Mã hóa cơ sở 64 với URL và tên bảng chữ cái an toàn) vì đây là giải pháp mạnh nhất về bảo mật. Giải pháp này chỉ mã hóa khóa và không can thiệp vào quy trình sản xuất khóa.
Yves M.

Cảm ơn sự hỗ trợ của bạn. Bạn có bất kỳ ví dụ làm việc để chia sẻ với cộng đồng? Nó sẽ được hoan nghênh?
alexventuraio

6
Coi chừng chuỗi ngẫu nhiên được tạo không được phân phối đồng đều. Một ví dụ dễ dàng cho thấy điều này là, đối với một bộ ký tự có độ dài 255 và độ dài chuỗi là 1, khả năng ký tự đầu tiên xuất hiện cao gấp đôi.
Florian Wendelborn

@Dodekeract Vâng, bạn đang nói về giải pháp 2 .. Đó là lý do tại sao giải pháp 1 mạnh hơn nhiều
Yves M.

Tôi đã thêm thư viện bên thứ ba nanoid trong phản hồi của mình github.com/ai/nanoid
Yves M.

13

Cách cập nhật đúng để thực hiện điều này một cách không đồng bộ bằng cách sử dụng các tiêu chuẩn không đồng bộ ES 2016 và chờ đợi (kể từ Nút 7) sẽ như sau:

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

Điều này hoạt động ra khỏi hộp trong Nút 7 mà không có bất kỳ biến đổi Babel nào


Tôi đã cập nhật ví dụ này để kết hợp phương thức truyền tham số mới hơn như được mô tả ở đây: 2ality.com/2011/11/keyword-parameter.html
real_ate

7

URL ngẫu nhiên và chuỗi tên tệp an toàn (1 lớp lót)

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

Một câu trả lời tuyệt vời trong sự đơn giản của nó! Chỉ cần lưu ý rằng nó có thể ngăn chặn vòng lặp sự kiện theo cách không xác định (chỉ liên quan nếu nó được sử dụng thường xuyên, trong một hệ thống nhạy cảm với thời gian, có phần tải). Mặt khác, làm điều tương tự, nhưng sử dụng phiên bản async của RandomBytes. Xem nodejs.org/api/ Kẻ
Alec Thilenius

6

Thủ tục thanh toán:

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

Đẹp! Giải pháp hoàn toàn bị đánh giá thấp. Sẽ thật tuyệt nếu bạn đổi tên "độ dài" thành "chiều dài mong muốn" và khởi tạo nó với một giá trị trước khi sử dụng :)
Florian Blum

Đối với bất cứ ai tự hỏi, các cuộc gọi ceilslicelà cần thiết cho độ dài mong muốn là số lẻ. Trong thời gian dài, họ không thay đổi bất cứ điều gì.
Seth

6

Với async / chờ đợi và hứa hẹn .

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

Tạo ra một cái gì đó tương tự như VjocVHdFiz5vGHnlnwqJKN0NdeHcz8eM


4

Nhìn vào real_atescách ES2016, nó đúng hơn.

Cách ECMAScript 2016 (ES7)

import crypto from 'crypto';

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

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

Cách tạo / năng suất

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 Thật vậy, Javascript đang nâng cấp :) Tra cứu và tạo ra!
K - Độc tính trong SO đang tăng lên.

Hãy thử chờ đợi, một trình xử lý lời hứa ECMA7 khác
Jain

Tôi nghĩ rằng bạn nên biến ES 2016 thành ví dụ đầu tiên về điều này vì nó đang tiến tới "cách làm đúng" trong hầu hết các trường hợp
real_ate

Tôi đã thêm một câu trả lời của riêng tôi dưới đây dành riêng cho Node (sử dụng yêu cầu thay vì nhập). Có một lý do cụ thể tại sao bạn sử dụng nhập khẩu? Bạn có babel chạy không?
real_ate

@real_ate Thật vậy, tôi đã trở lại sử dụng CommonJS cho đến khi việc nhập khẩu được hỗ trợ chính thức.
K - Độc tính trong SO đang tăng lên.


2

Mô-đun npm anyid cung cấp API linh hoạt để tạo các loại ID / mã chuỗi khác nhau.

Để tạo chuỗi ngẫu nhiên trong A-Za-z0-9 bằng 48 byte ngẫu nhiên:

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

Để tạo bảng chữ cái có độ dài cố định, chỉ có chuỗi được điền bởi các byte ngẫu nhiên:

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

Trong nội bộ, nó sử dụng crypto.randomBytes()để tạo ngẫu nhiên.


1

Đây là phiên bản không đồng bộ được lấy nguyên văn từ câu trả lời của @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

Hàm đơn giản giúp bạn nhận được mã thông báo là URL an toàn và có mã hóa base64! Đó là sự kết hợp của 2 câu trả lời từ phía trên.

const randomToken = () => {
    crypto.randomBytes(64).toString('base64').replace(/\//g,'_').replace(/\+/g,'-');
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.