G-PASS TERMINAL
Киберпанк-генератор паролей: разбор под капотом
Короче, запилил генератор паролей. Но не очередную поделку “возьми Math.random и склей буковки”, а что-то более серьёзное. С криптографией, энтропией и тремя режимами на выбор. Давайте разберём, что там внутри.
Три режима — три философии
1. Стандартный режим
Классика жанра: берём пул символов, кидаем кости, получаем Xk#9mP$2nL. Но есть нюансы.
Гарантия категорий. Если выбраны нижний регистр, верхний, цифры и символы — пароль гарантированно будет содержать минимум по одному символу из каждой категории. Никаких “опс, а цифр-то и нет”.
// Сначала берём по одному из каждой категории
let chars: string[] = categories.map((c) => getSecureRandomChar(c.chars));
// Потом добиваем из общего пула
while (chars.length < length) {
chars.push(getSecureRandomChar(pool));
}
// И перемешиваем Fisher-Yates
for (let i = chars.length - 1; i > 0; i--) {
const j = secureRandomInt(i + 1);
[chars[i], chars[j]] = [chars[j], chars[i]];
}Поддержка кириллицы. Переключаешь тумблер — и вместо 52 латинских букв получаешь 66 кириллических. Пул символов увеличивается, энтропия растёт. А ещё это ломает мозг злоумышленнику, который ожидает латиницу.
2. XKCD-режим
Помните тот самый комикс? correct horse battery staple — легко запомнить, сложно подобрать. Реализовал именно этот подход.
Словарь — ~180 слов из кибер/техно-тематики:
correct horse battery staple system hacking cyber neural matrix logic
ghost shell neon laser future data access denied granted protocol...Размер словаря = 180 слов → ~7.49 бит на слово.
5 слов = ~37.5 бит только от слов. Добавляем случайную капитализацию (+5 бит) и число 00-99 (+6.6 бит) — получаем ~49 бит. Для онлайн-атак более чем достаточно.
3. Фонетический режим
Идея: генерировать не случайную кашу и не настоящие слова, а произносимые псевдослова. Bakemi-Toruva-Sinelo-42 — не существует ни в одном языке, но ты можешь это произнести и запомнить.
Четыре паттерна слогов:
- CV — согласная + гласная (ба, ке, ми) — самый простой
- CVC — согласная + гласная + согласная (бак, кем, мир)
- CVCC — согласная + гласная + две согласных (банк, керн)
- CCVC — две согласных + гласная + согласная (стар, крик)
const generateSyllable = (): string => {
switch (phoneticPattern) {
case "cv":
return getSecureRandomChar(consonants) + getSecureRandomChar(vowels);
case "cvc":
return getSecureRandomChar(consonants) +
getSecureRandomChar(vowels) +
getSecureRandomChar(consonants);
// ...
}
};Латинские согласные: 18 штук (bcdfghjklmnprstvwz) Латинские гласные: 5 штук (aeiou)
Энтропия слога CV = log₂(18) + log₂(5) ≈ 4.17 + 2.32 = 6.49 бит
Три слова по три слога = 9 слогов = ~58 бит. Неплохо для чего-то, что можно произнести!
И да, тут тоже есть кириллица. Кириллические согласные/гласные дают чуть больше энтропии за счёт большего алфавита.
Криптографически безопасный RNG
Math.random() — это PRNG (псевдослучайный генератор). Для паролей не годится. Используем Web Crypto API:
const secureRandomInt = (max: number): number => {
if (max <= 0) return 0;
const randomBuffer = new Uint32Array(1);
const maxValid = Math.floor(0xffffffff / max) * max;
do {
crypto.getRandomValues(randomBuffer);
} while (randomBuffer[0] >= maxValid);
return randomBuffer[0] % max;
};Rejection sampling — важная штука. Если max не делит 2³² нацело, простой % max даёт смещение (bias). Например, для max = 3:
- 0xFFFFFFFF % 3 = 0
- Числа 0, 1, 2 будут выпадать не с равной вероятностью
Решение: отбрасываем значения ≥ maxValid и генерируем заново. Да, теоретически может зациклиться навечно. Практически — вероятность даже одного retry около 0.0000002%.
Расчёт энтропии
Энтропия — это не “сколько символов в пароле”, а “сколько бит информации нужно угадать”. Чем больше — тем лучше.
Стандартный режим
Наивный подход: length × log₂(poolSize). Но это неточно, потому что мы гарантируем символы из каждой категории.
Правильный расчёт:
// 1. Биты от обязательных символов
for (const cat of categories) {
totalBits += Math.log2(cat.size);
}
// 2. Биты от остальных символов (из общего пула)
if (L > k) {
totalBits += (L - k) * Math.log2(poolSize);
}
// 3. Биты от перестановок — C(L, k) способов расставить обязательные
if (k > 0 && L > k) {
totalBits += logBinomial(L, k);
}Функция logBinomial считает log₂(C(n,k)) без переполнения:
const logBinomial = (n: number, k: number): number => {
let result = 0;
for (let i = 0; i < k; i++) {
result += Math.log2(n - i) - Math.log2(i + 1);
}
return result;
};XKCD и фонетический режимы
Тут проще:
- Слова/слоги независимы → умножаем количество на биты каждого
- Случайная капитализация: +1 бит на слово
- Число: +log₂(range) бит
Оценка времени взлома
Беру пессимистичный сценарий: офлайн-атака со скоростью 10¹⁰ хешей/сек (мощная GPU-ферма).
const combinations = Math.pow(2, entropyBits);
const speed = 1e10; // 10 миллиардов в секунду
const seconds = combinations / speed / 2; // В среднем находим за половину перебораПотом форматирую в человекочитаемый вид:
if (seconds < universe) return `${(seconds / millennium).toFixed(0)} тысячелетий`;
return `${(seconds / universe).toFixed(2)}x возраст Вселенной`;80 бит энтропии = ~1.9 миллиарда лет при 10¹⁰/сек. Думаю, достаточно.
Мелкие радости
QR-код — сгенерировал пароль, показал QR, отсканировал телефоном. Удобно для переноса на устройства без синхронизации.
История в localStorage — последние 20 паролей сохраняются локально. Нажал — скопировал. Закрыл вкладку — история осталась.
Визуальная шкала энтропии — от красного (< 20 бит, взлом мгновенный) до белого (256+ бит, AES-256 уровень). С маркерами на 50, 80, 128, 192 бита.
Что можно улучшить?
Больший словарь для XKCD. 180 слов — это ~7.5 бит. EFF wordlist даёт 7776 слов = ~12.9 бит на слово. Но тогда слова будут менее “тематические”.
Passphrase с грамматикой.
The lazy purple robot hacks silently— ещё запоминаемее, но сложнее реализовать.Проверка на утечки. Интеграция с Have I Been Pwned API (k-anonymity, конечно).
Экспорт в KeePass/Bitwarden. Генерируешь — сразу в менеджер.
Итого
Генератор получился с тремя режимами под разные задачи:
- Стандартный — для систем с жёсткими требованиями “минимум 1 цифра, 1 символ…”
- XKCD — когда нужно запомнить без менеджера
- Фонетический — компромисс между случайностью и произносимостью
P.S. Да, я знаю про 1Password и Bitwarden. Но иногда хочется понимать, как это работает под капотом. И сделать своё.
