Забезпечити випадковий маркер у Node.js


273

У цьому питанні Еріку потрібно створити захищений випадковий маркер у Node.js. Існує метод, crypto.randomBytesякий генерує випадковий буфер. Однак кодування base64 у вузлі не є безпечним для URL, воно включає /і +замість, -і _. Тому найпростіший спосіб створити такий маркер, який я знайшов, - це

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

Чи є більш елегантний спосіб?


Що є рештою коду?
Lion789

3
Більше нічого не потрібно. Який відпочинок ви хотіли б побачити?
Hubert OG

Не забудьте, я змусив його працювати, просто не знав, як ви його кинули, але краще зрозумів концепцію
Lion789,

1
Безсоромний самовідвід, я створив ще один пакет npm: tokgen . Ви можете вказати дозволені символи, використовуючи синтаксис діапазону, подібний класам символів у звичайних виразах ( 'a-zA-Z0-9_-').
Макс Трукса

1
Це може бути зручно для всіх, хто бажає певної довжини рядка. 3/4-ї - це обробка базової конверсії. / * повертає кодовану рядок base64 довжиною * / функція randomString (length) {return crypto.randomBytes (length * 3/4) .toString ('base64'); } Добре працює для цих баз даних з обмеженнями символів.
TheUnknownGeek

Відповіді:


353

Спробуйте crypto.randomBytes () :

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

"Шістнадцяткове" кодування працює у вузлі v0.6.x або новішому.


3
Це здається краще, дякую! Хоча було б добре кодування 'base64-url'.
Хуберт ОГ

2
Дякую за підказку, але я думаю, що ОП просто хотів уже стандартного RFC 3548 розділу 4 "Основа 64 Кодування з URL-адресою та ім'ям файлу Safe Alphabet". IMO, заміна символів "досить елегантна".
natevw

8
Якщо ви шукаєте вищезазначене як башмайн-лайнер, можете зробитиnode -e "require('crypto').randomBytes(48, function(ex, buf) { console.log(buf.toString('hex')) });"
Дмитро Міньковський,

24
І ви завжди можете зробити buf.toString('base64')номер, кодований Base64.
Дмитро Міньковський

1
Дивіться цей answser нижче для базового кодування 64 із URL-адресою та ім'ям файлу Безпечний алфавіт
Yves M.

232

Синхронний варіант на випадок, якщо ви не експерт з JS, як я. Довелося витратити деякий час на доступ до змінної функції вбудованої

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

7
Також у випадку, якщо ви не хочете, щоб все вклалося. Дякую!
Михайло Озерянський

2
Хоча це безумовно працює, зауважте, що у більшості випадків ви хочете, щоб параметр асинхронізації був продемонстрований у відповіді thejh.
Triforcey

1
const generateToken = (): Promise<string> => new Promise(resolve => randomBytes(48, (err, buffer) => resolve(buffer.toString('hex'))));
янтраб

1
@Triforcey, чи можете ви пояснити, чому ви зазвичай хочете параметр async?
thomas

2
@thomas Випадкові дані можуть обчислити деякий час, залежно від обладнання. У деяких випадках, якщо на комп’ютері не вистачає випадкових даних, він просто поверне щось на своє місце. Однак в інших випадках можливо, що комп'ютер затримає повернення випадкових даних (що насправді ви хочете), що призведе до повільного дзвінка.
Triforcey

80

0. Використання наноїдної сторонньої бібліотеки [НОВО!]

Крихітний, безпечний, унікальний для URL-адрес унікальний генератор ідентифікаторів рядків для JavaScript

https://github.com/ai/nanoid

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


1. База 64 Кодування з URL-адресою та ім'ям файлу Безпечний алфавіт

Сторінка 7 RCF 4648 описує, як кодувати в базі 64 безпеку URL-адреси. Ви можете використовувати існуючу бібліотеку, наприклад base64url, щоб виконати роботу.

Функція буде:

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

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

Приклад використання:

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

Зауважте, що довжина повернутого рядка не збігатиметься з аргументом розміру (size! = Остаточна довжина).


2. Крипто випадкові значення з обмеженого набору символів

Будьте уважні, що за допомогою цього рішення згенерований випадковий рядок розподілений не рівномірно.

Ви також можете створити міцний випадковий рядок із обмеженого набору символів, таких як:

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

Приклад використання:

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

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

2
@Lexynux Рішення 1 (Base 64 Кодування з URL-адресою та ім'ям файлу Безпечний алфавіт), оскільки це найсильніше рішення з точки зору безпеки. Це рішення лише кодує ключ і не заважає процесу виготовлення ключів.
Ів М.

Дякую за твою підтримку. Чи є у вас якийсь робочий приклад, яким ви можете поділитися з громадою? Це буде вітатися?
alexventuraio

6
Слідкуйте за тим, щоб генерований випадковий рядок розподілений не рівномірно. Простим прикладом цього є те, що для набору символів довжиною 255 та довжиною рядка 1 шанс появи першого символу вдвічі більший.
Флоріан Вендельборн

@Dodekeract Так, ви говорите про рішення 2 .. Ось чому рішення 1 набагато сильніше
Ів М.

У свою відповідь я додав сторонній бібліотеку наноїдів github.com/ai/nanoid
Ів М.

13

Актуальним правильним способом зробити це асинхронно за допомогою стандартів ES 2016 про асинхронізацію та очікування (станом на Вузол 7) буде такий:

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

Це спрацьовує з поля в Вузлі 7 без будь-яких перетворень Бабеля


Я оновив цей приклад, щоб включити новіший метод передачі названих параметрів, як описано тут: 2ality.com/2011/11/keyword-parameters.html
real_ate

7

Випадковий рядок URL-адреси та імені файлу (1 вкладиш)

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

Чудова відповідь у своїй простоті! Просто пам’ятайте, що він може зупинити цикл подій недетермінованим способом (актуально лише, якщо він використовується часто, у дещо завантаженій, залежній від часу системі). Інакше зробіть те саме, але використовуючи асинхронну версію randomBytes. Дивіться nodejs.org/api/…
Alec Thilenius

6

Перевіряти:

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

Приємно! Абсолютно недооцінене рішення. Було б чудово, якщо ви перейменовуєте "length" на "desireLength" і ініціюєте його зі значенням, перш ніж використовувати його :)
Florian Blum

Для всіх, хто цікавиться, дзвінки ceilта sliceдзвінки потрібні для бажаної довжини, яка є непарною. Для рівних довжин вони нічого не змінюють.
Сет

6

З асинхронізацією / очікуванням та перспективізацією .

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

Породжує щось подібне до VjocVHdFiz5vGHnlnwqJKN0NdeHcz8eM


4

Подивіться на real_atesспосіб ES2016, це правильніше.

ECMAScript 2016 (ES7) спосіб

import crypto from 'crypto';

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

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

Генератор / Вихідний шлях

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 Дійсно, Javascript оновлюєсь :) Обіцянки пошуку та генератори!
K - Токсичність в SO зростає.

спробуйте почекати, ще один обробник обіцянок ECMA7
Джайн

Я думаю, що ви повинні зробити ES 2016 першим прикладом цього, оскільки він у більшості випадків рухається до "правильного способу зробити це"
real_ate

Нижче я додав власну відповідь, яка була специфічною для Node (використовуючи вимагати замість імпорту). Чи була певна причина, чому ви використовуєте імпорт? У вас дівочий біг?
real_ate

@real_ate Насправді я був, я повернувся до використання CommonJS, поки імпорт офіційно не підтримується.
K - Токсичність в SO зростає.


2

Модуль npm anyid забезпечує гнучкий API для генерації різних видів рядкових ідентифікаторів / коду.

Для генерації випадкових рядків у A-Za-z0-9 за допомогою 48 випадкових байтів:

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

Щоб генерувати лише алфавіт фіксованої довжини, заповнений випадковими байтами:

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

Внутрішньо він використовує crypto.randomBytes()для генерації випадкових випадків.


1

Ось асинхронна версія, взята дословно зверху на відповідь @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

Проста функція, яка надає вам маркер, захищений URL-адресою та має кодування base64! Це поєднання 2 відповідей зверху.

const randomToken = () => {
    crypto.randomBytes(64).toString('base64').replace(/\//g,'_').replace(/\+/g,'-');
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.