Появление паролей как основного механизма аутентификации практически сразу привело к развитию методов их взлома. На протяжении десятилетий атакующие совершенствовали способы получения паролей: от простого перебора до сложных криптоаналитических техник. Именно история атак сформировала современные требования к библиотекам хеширования паролей, включая использование соли, адаптивных алгоритмов и устойчивости к GPU-ускоренному перебору.
Ранние операционные системы часто хранили пароли в открытом виде. Это означало, что любой пользователь с доступом к файлу базы данных мог моментально увидеть учетные данные всех остальных пользователей.
Позже появились криптографические хеш-функции. Вместо хранения самого пароля система сохраняла результат вычисления:
hash = H(password)
Во время входа пользователь вводил пароль, система снова вычисляла хеш и сравнивала его с сохранённым значением.
Такой подход устранял необходимость хранить пароль напрямую, однако быстро выяснилось, что обычное хеширование не обеспечивает достаточной защиты.
Брутфорс (brute force) — полный перебор возможных комбинаций символов до нахождения совпадения.
Простейший сценарий:
Если:
hash(candidate) === stolenHash
то пароль найден.
const crypto = require('crypto')
const targetHash =
'5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8'
const chars = 'abcdefghijklmnopqrstuvwxyz1234567890'
function sha256(value) {
return crypto
.createHash('sha256')
.update(value)
.digest('hex')
}
function bruteForce(length, current = '') {
if (current.length === length) {
if (sha256(current) === targetHash) {
console.log('Найден пароль:', current)
process.exit()
}
return
}
for (const char of chars) {
bruteForce(length, current + char)
}
}
bruteForce(4)
Скорость брутфорса напрямую зависит от производительности оборудования.
| Период | Скорость перебора |
|---|---|
| 1970-е | сотни хешей/сек |
| 1990-е | тысячи хешей/сек |
| 2000-е | миллионы хешей/сек |
| Современные GPU | миллиарды хешей/сек |
Алгоритмы вроде:
создавались для скорости, а не для защиты паролей.
Для файловой целостности высокая скорость полезна:
быстрее вычисление → лучше производительность
Для паролей это катастрофа:
быстрее вычисление → быстрее перебор
Современная видеокарта может вычислять миллиарды MD5-хешей в секунду.
Большинство пользователей не создают случайные пароли. Вместо этого используются:
Примеры:
password
qwerty
admin123
welcome
123456
Словарная атака использует заранее подготовленный список популярных паролей.
const crypto = require('crypto')
const fs = require('fs')
const dictionary = fs
.readFileSync('passwords.txt', 'utf8')
.split('\n')
const targetHash =
'5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8'
function sha256(value) {
return crypto
.createHash('sha256')
.update(value)
.digest('hex')
}
for (const password of dictionary) {
if (sha256(password.trim()) === targetHash) {
console.log('Пароль найден:', password)
break
}
}
Словарная атака намного эффективнее полного перебора.
Люди склонны:
password → p@ssw0rd
После крупных утечек появились коллекции реальных паролей:
Эти базы стали основой современных словарей.
Позже словарные атаки объединились с мутациями:
admin
admin1
admin123
Admin123!
Такие техники называются rule-based attacks.
Популярные инструменты:
Если два пользователя имеют одинаковый пароль:
password123
то при обычном хешировании получится одинаковый результат:
sha256("password123")
↓
ef92b778...
Это позволяет заранее вычислить огромные базы соответствий:
пароль → хеш
Радужная таблица (rainbow table) — заранее подготовленная база:
хеш → исходный пароль
Вместо вычисления хеша во время атаки злоумышленник просто ищет совпадение в таблице.
До их появления атакующий должен был:
Радужные таблицы позволили:
вычислить всё заранее
После этого взлом становился почти мгновенным.
password1 → hash1
password2 → hash2
password3 → hash3
Во время атаки:
украденный hash2 → поиск → password2
Полная таблица для всех возможных комбинаций слишком велика.
Например:
8 символов ASCII
дают десятки триллионов вариантов.
Для уменьшения размера использовались:
Радужные таблицы строились как цепочки:
пароль → хеш → преобразование → новый пароль → новый хеш
Хранились только начало и конец цепочки.
Это резко уменьшало размер таблицы.
Решением стала соль — случайная строка, добавляемая к паролю перед хешированием.
hash = H(password + salt)
const crypto = require('crypto')
const password = 'secret123'
const salt = crypto.randomBytes(16).toString('hex')
const hash = crypto
.createHash('sha256')
.update(password + salt)
.digest('hex')
console.log({ salt, hash })
Даже если два пользователя имеют одинаковый пароль:
secret123
разные соли создадут разные хеши:
hash(secret123 + salt1)
hash(secret123 + salt2)
Соль уничтожает эффективность радужных таблиц.
Теперь злоумышленнику необходимо:
генерировать таблицу отдельно для каждой соли
Это делает предвычисления практически бесполезными.
Современные атаки выполняются не на CPU, а на:
GPU способны одновременно вычислять тысячи потоков хеширования.
Алгоритмы вроде SHA256 чрезвычайно хорошо распараллеливаются.
Пример:
1 GPU → миллиарды SHA256/сек
Даже длинные пароли становятся уязвимыми при слабом алгоритме хеширования.
Чтобы усложнить перебор, появились специальные password hashing algorithms:
Они специально делают вычисление дорогим.
bcrypt основан на Blowfish и включает:
Пример:
const bcrypt = require('bcrypt')
async function run() {
const hash = await bcrypt.hash('secret123', 12)
console.log(hash)
const valid = await bcrypt.compare(
'secret123',
hash
)
console.log(valid)
}
run()
Параметр cost:
bcrypt.hash(password, 12)
означает:
2^12 раундов
Увеличение cost:
GPU отлично справляются с вычислениями, но имеют ограничения памяти.
Это привело к появлению memory-hard алгоритмов.
scrypt требует значительный объём памяти при вычислении.
Это делает массовый параллельный перебор значительно дороже.
Argon2 — победитель Password Hashing Competition.
Варианты:
| Версия | Назначение |
|---|---|
| Argon2d | защита от GPU |
| Argon2i | защита от side-channel |
| Argon2id | комбинированный вариант |
const argon2 = require('argon2')
async function run() {
const hash = await argon2.hash('secret123')
console.log(hash)
const valid = await argon2.verify(
hash,
'secret123'
)
console.log(valid)
}
run()
Утекло более 32 миллионов паролей.
Главная проблема:
пароли хранились в открытом виде
Эта утечка стала основой огромного количества словарей.
Использовался SHA1 без соли.
Последствия:
Adobe использовала слабое шифрование и плохие практики хранения.
Исследователи смогли:
После утечек злоумышленники начали массово проверять:
email + пароль
на других сервисах.
Причина успеха:
повторное использование паролей
Вместо перебора множества паролей для одного аккаунта используется:
один популярный пароль для множества аккаунтов
Например:
Summer2025!
Это помогает обходить блокировки.
Самый опасный сценарий.
Атакующий получает:
После этого перебор выполняется локально без ограничений сервера.
Современные библиотеки решают сразу несколько задач:
| Угроза | Механизм защиты |
|---|---|
| Радужные таблицы | Соль |
| GPU-брутфорс | Медленные алгоритмы |
| Массовый перебор | Cost factor |
| ASIC/GPU | Memory-hard вычисления |
| Коллизии | Современные алгоритмы |
Опасный вариант:
const hash = crypto
.createHash('sha256')
.update(password)
.digest('hex')
Причины:
Некоторые разработчики пытаются усложнить алгоритм:
sha256(md5(password + secret))
Это не делает систему безопасной.
Криптография требует:
Слишком маленький cost:
bcrypt.hash(password, 4)
делает перебор дешёвым.
Современные рекомендации обычно находятся в диапазоне:
10–14+
в зависимости от мощности серверов.
История атак полностью изменила подход к хранению паролей.
Современные требования включают:
Именно из-за развития:
появились специализированные библиотеки password hashing, без которых безопасная аутентификация стала невозможной.