Относительно недавно я проводил ревизию зависимостей в нашем бэкенде на ноде и внимание моё привлекла одна маленькая библиотечка — generate-sms-verification-code.
Как понятно из названия, единственная её задача — генерировать цифровые смс коды для верификации.
Сама по себе, библиотека очень простая, исходный код помещается в 20 строчек, и она использует Math.random. Все популярные библиотеки, которые я полистал на npm, были построены именно на старом добром методе получения псевдослучайного числа.
В ноде, начиная с версии 12.19, во встроенном модуле crypto, имеется функция randomInt, которая позволяет получать случайное целое число в указанном диапазоне, и, для целей OTP (one time password) алгоритм, используемый в crypto, подходит гораздо лучше.
Отличия алгоритмов randomInt и Math.random
Я не спец в C++, но из того что я смог понять из исходников Node.js — реализация Math.random основана на алгоритме xorshift128+, в то время как, crypto.randomInt базируется на RAND_bytes из OpenSSL.
Алгоритм получения псевдослучайного числа в randomInt является криптостойким, в отличие от Math.random. Таким образом, предугадать следующее число будет чрезвычайно затруднительно.
Итак, генерация смс–кодов без зависимостей
В большинстве случаев, когда вам надо генерировать цифровые коды, будет вполне достаточно такой простой функции:
Генерация по алфавиту
Помимо таких простых случаев с цифровыми кодами, иногда может потребоваться генерировать что–то посерьёзнее, например, буквенно–цифровые коды, и тут мы сталкиваемся с необходимостью заводить словарь и выбирать случайные символы из этого словаря.
Это можно сделать точно так же, вручную, но зачем, когда для решения подобных проблем я уже разработал готовую библиотеку — node-verification-code
Основные отличия от других:
- Использует crypto.randomInt, а не Math.random
- Использует преаллоцированный буфер для размещения результатов
- Не имеет ограничений* по количеству символов для генерации последовательностей (*результат, очевидно, ограничен максимальным размером буфера)
- Позволяет использовать собственный алфавит, а не только цифровые последовательности
Библиотека не тащит за собой никаких зависимостей и предлагает чудовищно простой интерфейс для работы.
По факту имеется всего 3 функции:
- getDigitalCode – возвращает буфер с рандомным числовым кодом
- sequenceFromAlphabet – создаёт функцию–последовательность из указанного алфавита
- createGenerator – создаёт функцию для получения случайного значения из функции–последовательности
С getDigitalCode, думаю, всё понятно — передаём размерность и получаем результат в виде объекта Buffer.
sequenceFromAlphabet/createGenerator
Библиотека строится на двух основных понятиях — функция–генератор и функция–последовательность.
Функция–последовательность принимает количество символов, которые требуется составить в строку и возвращает результат.
Логика работы абсолютно такая же, как я демонстрировал выше в примере с generateOTPCode:
sequenceFromAlphabet создаёт такую же функцию, но вместо возврата рандомного числа, возвращает рандомный элемент из переданного алфавита.
createGenerator принимает в качестве аргумента функцию–последовательность и создаёт, собственно, сам генератор случайных данных.
Под капотом эта функция пре–аллоцирует в памяти буфер, разбивает свою работу на несколько итераций, на каждой из которых дополняет данными буфер, и в итоге возвращает усечённый буфер с результатом.
Git репозиторий:
Npm пакет:
Полезные ссылки:
Криптостойкость алгоритма: https://ru.wikipedia.org/wiki/%D0%9A%D1%80%D0%B8%D0%BF%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F_%D1%81%D1%82%D0%BE%D0%B9%D0%BA%D0%BE%D1%81%D1%82%D1%8C
Разбор алгоритма xorshift+: https://hackernoon.com/how-does-javascripts-math-random-generate-random-numbers-ef0de6a20131
Взлом xorshift+: https://habr.com/ru/company/dcmiran/blog/584692/
Описание RAND_bytes в OpenSSL: https://www.openssl.org/docs/manmaster/man3/RAND_bytes.html
Git репозиторий с исходниками Node.js: https://github.com/nodejs/node