Ризики і проблеми хеширования паролів, savepearlharbor

Безпека завжди була неоднозначною темою, що провокує численні гарячі суперечки. І все завдяки великій кількості самих різних точок зору і «ідеальних рішень», які влаштовують одних і абсолютно не підходять іншим. Я вважаю, що злом системи безпеки додатки всього лише питання часу. Через швидке зростання обчислювальних потужностей і збільшення складності безпечні сьогодні програми перестануть завтра бути такими.

  • Одностороння функція. з хеша неможливо відновити вихідні дані за допомогою будь-якого ефективного алгоритму.
  • Конвертація даних змінної довжини в дані фіксованої довжини. вхідний значення може бути «нескінченної» довжини, а вихідний - немає. Це має на увазі, що два або більше вхідних значення можуть мати однакові хеши. Чим менше довжина хеша, тим вище ймовірність колізії.

Алгоритми MD5 і SHA-1 вже не забезпечують досить високої надійності з точки зору ймовірності виникнення колізій (див. Парадокс днів народження). Тому рекомендується використовувати алгоритми, які генерують більш довгі хеші (SHA-256, SHA-512. Whirlpool і ін.), Що робить ймовірність виникнення колізії нехтує малою. Такі алгоритми ще називають «псевдовипадковими функціями», т. Е. Результати їх роботи не відрізняються від результатів роботи повноцінного генератора випадкових чисел (true random number generator. TRNG).

Той факт, що за допомогою ефективного алгоритму неможливо провести операцію, зворотну хешування, і відновити вихідні дані, не означає, що вас не можуть зламати. Якщо добре пошукати, то можна знайти бази даних з хешамі поширених слів і коротких фраз. Крім того, прості паролі можна швидко і легко Брутфорс або зламувати перебором по словнику.

Ось невелика демонстрація, як інструмент sqlmap через впровадження SQL-коду зламує паролі за допомогою брутфорса хеш, згенерованих алгоритмом MD5.

Зловмисники можуть надійти ще простіше - нагугліть конкретні хеші в онлайнових БД:

Щоб ускладнити атаки описаного виду, застосовується так звана сіль. Це стандартна програма, але в умовах сучасних обчислювальних потужностей його вже недостатньо, особливо якщо довжина солі невелика.

У загальному вигляді функцію з використанням солі можна змалювати таку картину:

f (password, salt) = hash (password + salt)

Для утруднення брутфорс-атаки сіль повинна бути довжиною не менше 64 символів. Але проблема в тому, що для подальшої аутентифікації користувачів сіль повинна зберігатися в БД у вигляді простого тексту.

if (hash ([введений пароль] + [сіль]) == [хеш]) тоді користувач аутентифікований

Завдяки унікальності солі для кожного користувача ми можемо вирішити проблему колізій простих хеш. Тепер все хеші будуть різними. Також вже не спрацюють підходи з гугленьем хеш і брутфорсом. Але якщо зловмисник через впровадження SQL-коду отримає доступ до солі або БД, то зможе успішно атакувати брутфорсом або перебором по словнику, особливо якщо користувачі вибирають поширені паролі (а-ля 123456).

Проте злом будь-якого з паролів вже не дозволить автоматично обчислити користувачів, у яких той же пароль, - адже у нас ВСЕ хеші різні.

Для генерування підходящої солі нам потрібен хороший генератор випадкових чисел. Відразу забудьте про функції rand ().

Коли від комп'ютера хочуть випадкове число, то зазвичай він бере дані з декількох джерел (наприклад, змінні середовища: дату, час, кількість записаних / лічених байтів і т. Д.), А потім робить над ними обчислення для отримання «випадкових» даних. Тому такі дані називають псевдовипадковими. А значить, якщо якимось чином відтворити набір вихідних станів на момент виконання псевдослучайной функції, то ми зможемо згенерувати те ж саме число.

Якщо псевдовипадковий генератор ще й реалізований неправильно, то в генеруються їм даних можна виявити патерни, а з їх допомогою передбачити результат генерування. Погляньте на цю картинку, що представляє собою результат роботи PHP-функції rand ():

Ризики і проблеми хеширования паролів, savepearlharbor

А тепер порівняйте з даними, згенерували повноцінним генератором випадкових чисел:

Ризики і проблеми хеширования паролів, savepearlharbor

На жаль, ні rand (), ні mt_rand () не можна вважати придатними інструментами для забезпечення високого рівня безпеки.

Якщо вам потрібно отримати випадкові дані, скористайтеся функцією openssl_random_pseudo_bytes (). яка доступна починаючи з версії 5.3.0. У неї навіть є прапор crypto_strong, який повідомить про достатній рівень безпеки.

Можна впровадити розтягнення пароля, це дозволяє ще більше ускладнити брутфорс-атаки. Розтягування являє собою ітеративний, або рекурсивний, алгоритм, який раз по раз обчислює хеш самого себе, десятки тисяч разів (а то і більше).

Ризики і проблеми хеширования паролів, savepearlharbor

Кількість ітерацій повинна бути такою, щоб загальний час обчислень зайняло як мінімум одну секунду. Чим триваліше виходить хешування, тим більше часу атакуючому доводиться витрачати на злом.

Для злому пароля з розтягуванням потрібно:

  1. знати точну кількість ітерацій, оскільки будь-яке відхилення буде давати інший хеш;
  2. чекати не менше секунди між кожною спробою.

Це робить атаку дуже малоймовірною ... але не неможливою. Щоб подолати секундну затримку, атакуючий повинен використовувати більш продуктивний комп'ютер, ніж той, під який був налаштований алгоритм хешування. Отже, процес злому може зажадати додаткових витрат.

Для розтягування пароля можна використовувати стандартні алгоритми, наприклад PBDKDF2, що представляє собою функцію формування ключа.

Є і більш затратні за часом і пам'яті алгоритми, наприклад bcrypt (про нього ми поговоримо нижче) або scrypt:

  • $ Cost - коефіцієнт трудомісткості;
  • $ Salt - випадкова рядок. Її можна генерувати, наприклад, за допомогою описаної вище функції secure_rand ().

Коефіцієнт трудомісткості цілком залежить від машини, на якій виконується хешування. Можете почати зі значення 09 і поступово збільшувати, поки тривалість операції не досягне однієї секунди. Починаючи з версії 5.5 можна користуватися функцією password_hash (), про це ми поговоримо далі.

На даний момент в PHP не реалізована підтримка алгоритму scrypt, але можна скористатися реалізацією від Domblack.

Багато плутаються в термінах «хешування» і «шифрування». Як було згадано вище, хеш - результат роботи псевдослучайной функції, в той час як шифрування - здійснення псевдослучайного перетворення. вхідні дані діляться на частини і обробляються таким чином, що результат стає не відрізняється від результату роботи повноцінного генератора випадкових чисел. Однак в цьому випадку можна провести зворотне перетворення і відновити вихідні дані. Перетворення здійснюється за допомогою кріптоключа, без якого неможливо провести зворотне перетворення.

Є і ще одна важлива відмінність шифрування від хешування: розмір простору вихідного повідомлення не обмежений і залежить від розміру вхідних даних в співвідношенні 1: 1. Тому немає ризику виникнення колізій.

Деякий час назад у Adobe була потужна витік користувальницької БД через неправильно реалізованого шифрування. Давайте розберемо, що у них сталося.

Ризики і проблеми хеширования паролів, savepearlharbor

Припустимо, що в таблиці зберігаються такі дані у вигляді звичайного тексту:

Ризики і проблеми хеширования паролів, savepearlharbor

Хтось в Adobe вирішив зашифрувати паролі, але при цьому зробив дві великі помилки:

  1. використовував один і той же кріптоключа;
  2. залишив поля passwordHint незашифрованими.

Припустимо, після шифрування таблиця стала виглядати так:

Ризики і проблеми хеширования паролів, savepearlharbor

Ми не знаємо, який застосовувався кріптоключа. Але якщо проаналізувати дані, то можна помітити, що в рядках 2 і 7 використовується один і той же пароль, так само як і в рядках 3 і 6.

Прийшов час звернутися до підказкою пароля. У рядку 6 це «I'm one!», Що абсолютно не інформативно. Зате завдяки рядку 3 ми можемо припустити, що пароль - queen. Рядки 2 та 7 окремо не дозволяють обчислити пароль, але якщо проаналізувати їх разом, то можна припустити, що це halloween.

Заради зниження ризику витоку даних краще використовувати різні способи хешування. А якщо вам потрібно шифрувати паролі, то зверніть увагу на настроюється шифрування:

Нехай у нас є тисячі користувачів і ми хочемо зашифрувати всі паролі. Як було показано вище, краще уникати використання єдиного кріптоключа. Але і зробити для кожного користувача унікальний ключ ми теж не можемо, оскільки зберігання ключів само по собі перетвориться в проблему. У цьому випадку досить застосовувати загальний для всіх кріптоключа, але при цьому робити «настройку», унікальну для кожного користувача. Комбінація ключа і «настройки» і буде унікальним ключем для кожного користувача.

Найпростіший варіант «настройки» - так званий первинний ключ. унікальний для кожного запису в таблиці. Не рекомендується користуватися ним в житті, тут він показаний лише для прикладу:

f (key, primaryKey) = key + primaryKey

Тут ключ і первинний ключ просто зчіплюються разом. Але для забезпечення безпеки слід застосувати до них алгоритм хешування або функцію формування ключа (key derivation function). Також замість первинного ключа можна для кожного запису використовувати одноразовий ключ (аналог солі).

Якщо ми застосуємо до нашої таблиці настроюється шифрування, то вона буде виглядати так:

Ризики і проблеми хеширования паролів, savepearlharbor

Звичайно, потрібно буде ще щось зробити з підказками паролів, але все-таки вже вийшло хоч щось адекватне.

Зверніть увагу, що шифрування - не ідеальна рішення для зберігання паролів. У зв'язку з погрозами впровадження коду краще уникати цього методу захисту. Для зберігання паролів найнадійніше використовувати алгоритм bcrypt. Але не можна забувати і про те, що навіть найкращі і перевірені рішення мають уразливими.

Сьогодні оптимальним способом хешування паролів вважається використання bcrypt. Але багато розробників все ще віддають перевагу старим і слабші алгоритми начебто MD5 і SHA-1. А деякі при хешування навіть не користуються сіллю. У PHP 5.5 був представлений новий API для хешування, який не тільки заохочує застосування bcrypt, а й істотно полегшує роботу з ним. Давайте розберемо основи використання цього нового API.

Тут застосовуються чотири прості функції:

  • password_hash () - хешування пароля;
  • password_verify () - порівняння пароля з хешем;
  • password_needs_rehash () - перехешірованіе пароля;
  • password_get_info () - повернення назви алгоритму хешування і застосовувалися в ході хешування опцій.

Незважаючи на високий рівень безпеки, який забезпечується функцією crypt (), багато хто вважає її надто складною, через що програмісти часто припускаються помилок. Замість неї деякі розробники використовують для генерування хеш комбінації слабких алгоритмів і слабких солей:

Функція password_hash () істотно полегшує розробнику життя і підвищує безпеку коду. Для хешування пароля досить згодувати його функції, і вона поверне хеш, який можна помістити в БД:

І все! Перший аргумент - пароль у вигляді рядка, другий аргумент задає алгоритм генерування хеша. За замовчуванням використовується bcrypt, але при необхідності можна додати і сильніший алгоритм, який дозволить генерувати рядки більшої довжини. Якщо в своєму проекті ви використовуєте PASSWORD_DEFAULT, то упевніться, що ширина колонки для зберігання хеш не менше 60 символів. Краще відразу поставити 255 знаків. В якості другого аргументу можна використовувати PASSWORD_BCRYPT. В цьому випадку хеш завжди буде довжиною в 60 символів.

Зверніть увагу, що вам не потрібно задавати значення солі або вартісного параметра. Новий API все зробить за вас. Оскільки сіль є частиною хеша, то вам не доведеться зберігати її окремо. Якщо вам все-таки потрібно задати своє значення солі (або вартості), то це можна зробити за допомогою третього аргументу:

Все це дозволить вам використовувати найсвіжіші засоби забезпечення безпеки. Якщо в подальшому в PHP з'явиться сильніший алгоритм хешування, то ваш код стане використовувати його автоматично.

Тепер розглянемо функцію порівняння пароля з хешем. Перший вводиться користувачем, а другий ми беремо з БД. Пароль і хеш використовуються в якості двох аргументів функції password_verify (). Якщо хеш відповідає паролю, то функція повертає true.

Пам'ятайте, що сіль є частиною хеша, тому вона не задається тут окремо.

Якщо ви захочете підвищити рівень безпеки, додавши більш сильну сіль або збільшивши вартісний параметр, або зміниться алгоритм хешування за замовчуванням, то ви, ймовірно, захочете перехешіровать всі наявні паролі. Ця функція допоможе перевірити кожен хеш на предмет того, який алгоритм і параметри використовувалися при його створенні:

Не забувайте, що вам потрібно буде це робити в той момент, коли користувач намагається залогінитися, оскільки це єдиний раз, коли у вас буде доступ до паролю у вигляді простого тексту.

Ця функція бере хеш і повертає асоціативний масив з трьох елементів:

  • algo - константа, що дозволяє ідентифікувати алгоритм;
  • algoName - назва використовувався алгоритму;
  • options - значення різних опцій, які застосовувались при хешування.

Як бачите, працювати з новим API не в приклад легше, ніж з незграбною функцією crypt (). Якщо ж ви використовуєте більш ранні версії PHP, то рекомендую звернути увагу на бібліотеку password_compact. Вона емулює даний API і автоматично відключається, коли ви оновлюєтеся до версії 5.5.

На жаль, до сих пір не існує ідеального рішення для захисту даних. До того ж завжди є ризик злому вашої системи безпеки. Однак боротьба снаряда і броні не припиняється. Наприклад, наш арсенал засобів захисту відносно недавно поповнився так званими функціями губки.