Перевірка складності пароля без знущання над користувачем
"Мінімум 8 символів, велика літера, цифра, спецсимвол, не менше 8 символів" — знайомо? Наче в ЖЕК прийшов. Пароль aaaaaA1! формально проходить, а "LoremIpsum LoremIpsum LoremLorem IpsumIpsum" - ні. То ж я роблю перевірку через підрахунок ентропії: оцінюється реальна складність саме підбору, а не чекбокси для секʼюріті. Довгий простий пароль може бути надійнішим за короткий "складний". Бонус - жартівливий текст помилки, щоб підбадьорити користувача йти далі.
Як рахувати ентропію?
Та як, як, потрібно просто порахувати скільки спроб потрібно зробити, щоб підібрати пароль! Тобто кількість можливих символів (базу) в степені довжини (експонента). Все. Але не зовсім. Чому? Бо якщо пароль "aaaaaa", то він формально має 6 символів, але насправді це лише 1 унікальний символ, який повторюється. Тобто потрібно враховувати не тільки довжину, а й різноманітність символів. І зараз це все зробимо.
Крок 1: Визначимо базу
Підрахуємо, які класи символів є в паролі:
| Клас | Додає до бази |
|---|---|
| Малі літери (a-z) | +26 |
| Великі літери (A-Z) | +26 |
| Цифри (0-9) | +10 |
| Інші символи (!@# тощо) | +26 |
Використав і малі, і великі, і цифри — база = 62. Тільки цифри — база = 10.
Крок 2: Експонента з урахуванням повторів
Кожен унікальний символ дає вагу 1.0, кожен повторний — 0.3. Нащо? Щоб понизити вагу однакових символів в паролі, але все ще їх рахувати, як-ніяк вони ускладнюють пароль.
Експонента = кількість_унікальних × 1.0 + кількість_повторів × 0.3
Крок 3: Поріг
Визначаємо поріг - баланс між зручністю користувача і безпекою. Я вибрав 10¹², це приблизно рівень, де брутфорс стає непрактичним для онлайн-атак. Якщо результат (база в степені експонента) менше порога — пароль слабкий, і користувача змушую придумати інший.
Приклади мінімально підходящих паролів (на 1 символ менше вже не підходять)
| Пароль | База | Унік. | Повт. | Експонента | Результат |
|---|---|---|---|---|---|
| V1@dDra | 88 | 7 | 0 | 7 | 88^7 ≈ 40e12 |
| V1@dV1@dV1@d | 88 | 4 | 8 | 6.4 | 88^6.4 ≈ 3e12 |
| VladVladVladVl | 52 | 4 | 10 | 7 | 52^7 ≈ 1e12 |
| vladvladvladvladvla | 26 | 4 | 15 | 8.5 | 26^8.5 ≈ 1e12 |
| 12345678901234567 | 10 | 10 | 7 | 12.1 | 10^12.1 ≈ 1e12 |
| 111111111111111111111111111111111111111 | 10 | 1 | 38 | 12.4 | 10^12.4 ≈ 2e12 |
| horsejump | 26 | 9 | 0 | 9 | 26^9 ≈ 5e12 |
Причому, як ви можете побачити, найперший пароль з 7 символів найскладніший. А другий пароль 12 символів - уже пройде всі перевірки складності на різноманітних сайтах з запасом, а в мене ледь-ледь укладується в поріг.
Ну і жартівлива помилка, щоб змусити користувача таки ввести складний пароль
Приклад коду
var funnyNames = []string{
"Alice", "Bob", "Gizmo", "Noodle", "Pickle", "Wombat", "Bubbles", "Sprocket", "Muffin",
"Einstein", "Tesla", "Curie", "Newton", "Turing", "Lovelace", "Hawking",
"Gandalf", "Yoda", "Sherlock", "Pikachu", "Groot", "Dobby", "Epstein",
}
func checkPasswordStrength(password string) error {
const minComplexity = 1e12
var hasLower, hasUpper, hasDigit, hasOther bool
unique := make(map[rune]struct{})
for _, r := range password {
switch {
case unicode.IsLower(r):
hasLower = true
case unicode.IsUpper(r):
hasUpper = true
case unicode.IsDigit(r):
hasDigit = true
default:
hasOther = true
}
unique[r] = struct{}{}
}
baseComplexity := 0
if hasLower {
baseComplexity += 26
}
if hasUpper {
baseComplexity += 26
}
if hasDigit {
baseComplexity += 10
}
if hasOther {
baseComplexity += 26
}
total := utf8.RuneCountInString(password)
exponent := float64(len(unique)) + float64(total-len(unique))*0.3
result := math.Pow(float64(baseComplexity), exponent)
if result < minComplexity {
name := funnyNames[rand.Intn(len(funnyNames))]
return fmt.Errorf("error, this password uses %s, choose another one", name)
}
return nil
}
Спробуй сам на цьому інтерактивному демо
Введені паролі не відправляються нікуди, чесно-чесно.
P.S. Використовуйте менеджери паролів. Потрібно памʼятати лише один пароль. А інші - довгі, складні та завжди унікальні.
Коментарі