Генерируйте криптографически стойкие случайные токены, API-ключи, секреты и nonce в браузере. Выберите длину, набор символов и количество.
Случайные, непредсказуемые токены. Вся случайность поступает из crypto.getRandomValues браузера — того же источника, что используется браузерами для TLS-ключей. Ничего не покидает ваше устройство.
Что делает токен безопасным
Случайный — не значит непредсказуемый
Большинство языков поставляют два очень разных генератора случайных чисел. Math.random(), rand() и компания — быстрые псевдослучайные генераторы, инициализированные чем-то дёшевым вроде часов. Они годятся для перемешивания плейлиста и катастрофичны для токена, ведь злоумышленник, понаблюдав за несколькими выходами, часто может восстановить внутреннее состояние и воспроизвести каждое значение, которое генератор когда-либо выдаст.
Криптографически стойкий генератор устроен так, что прошлые выходы ничего не говорят о будущих. В браузере это crypto.getRandomValues(), который берёт из пула энтропии операционной системы. Этот инструмент использует только его. Если вы генерируете токены в своём коде, аналоги — secrets в Python, crypto/rand в Go и java.security.SecureRandom в Java — никогда не используйте обычный модуль random или Math.random().
Как избежать смещения по модулю
Хорошие случайные байты — лишь половина дела. Наивный способ выбрать символ — bytes[i] % alphabet.length, который незаметно искажает результат, когда размер алфавита не делит 256 поровну. С 62-символьным алфавитом первые 8 символов появляются чуть чаще остальных — небольшое смещение, но оно снижает реальную энтропию и полностью устранимо.
Исправление — выборочное отбраковывание: отбрасывать любой байт, попавший в неполный последний блок, и брать заново. Этот инструмент так и делает, поэтому каждый символ выбранного алфавита равновероятен.
Выбор длины
Энтропия равна log2(alphabet_size) * length бит. 62-символьный алфавит даёт около 5,95 бит на символ, поэтому 22-символьный токен несёт примерно 131 бит — с комфортом за пределами перебора. Для всего, что охраняет реальный доступ, 128 бит — обычный нижний предел; ниже примерно 64 бит решительный злоумышленник с быстрым эндпоинтом становится реальной угрозой.
Выбор алфавита — в основном вопрос того, куда попадёт токен. Base64url и hex проходят через URL, имена файлов и заголовки без искажений. Однозначный алфавит убирает символы вроде 0/O и 1/l/I и стоит того для всего, что человек будет читать вслух или заново набирать с экрана.
Часто задаваемые вопросы
Токен генерируется на моём устройстве?
Да, полностью в браузере с помощью crypto.getRandomValues(). Ничего на сервер не отправляется и ничего не логируется, поэтому значение, которое вы видите, знаете только вы.
Какой длины должен быть токен?
Для всего, что охраняет доступ, стремитесь минимум к 128 бит энтропии: 22 символа на 62-символьном алфавите или 32 hex-символа. Более короткие значения подходят для не защищённых идентификаторов.
Почему бы не использовать здесь Math.random()?
Это быстрый псевдослучайный генератор, инициализированный чем-то дёшевым вроде часов. Наблюдение за горсткой выходов может хватить, чтобы восстановить его состояние и предсказать все будущие значения.
Для чего опция «без неоднозначных символов»?
Она убирает 0/O и 1/l/I, чтобы токен переживал чтение вслух, запись на бумаге или повторный набор с экрана без ошибок переписывания.
Можно ли использовать это для ссылок сброса пароля?
Да, при условии что вы храните только хеш токена, задаёте короткое время жизни и аннулируете его после однократного использования. Случайность здесь подходящая; жизненный цикл — задача вашего приложения.
Может ли тот же токен когда-либо повториться?
При 128 бит энтропии повтор астрономически маловероятен, но инструмент не ведёт историю, поэтому не может проверить уникальность между генерациями за вас.