Проблема тут полягає в основному в проблемі ентропії. Тож давайте почнемо шукати там:
Ентропія на персонажа
Кількість бітів ентропії на байт:
- Шестигранні символи
- Біти: 4
- Цінності: 16
- Ентропія за 72 символи: 288 біт
- Буквено-числові
- Біти: 6
- Значення: 62
- Ентропія за 72 символи: 432 біти
- "Загальні" символи
- Біти: 6.5
- Цінності: 94
- Ентропія за 72 символи: 468 біт
- Повні байти
- Біти: 8
- Значення: 255
- Ентропія за 72 символи: 576 біт
Отже, як ми будемо діяти, залежить від того, який тип персонажів ми очікуємо.
Перша проблема
Перша проблема з вашим кодом полягає в тому, що ваш хеш "перець" видає шістнадцяткові символи (оскільки для четвертого параметра значення hash_hmac()
не встановлено).
Таким чином, перемішуючи ваш перець, ви фактично скорочуєте максимальну ентропію, доступну для пароля, у 2 рази (з 576 до 288 можливих бітів).
Друга проблема
Однак, насамперед sha256
передбачає лише 256
біти ентропії. Отже, ви ефективно скорочуєте можливі 576 біт до 256 біт. Ваш хеш-крок * негайно * за самим визначенням втрачає
щонайменше 50% можливої ентропії в паролі.
Ви можете частково вирішити цю проблему, переключившись на SHA512
, де ви лише зменшите доступну ентропію приблизно на 12%. Але це все-таки не несуттєва різниця. Ці 12% зменшують кількість перестановок у коефіцієнт 1.8e19
. Це велике число ... І це фактор, який зменшує його на ...
Основна проблема
Основна проблема полягає в тому, що існує три типи паролів довжиною до 72 символів. Вплив цієї системи стилів на них буде дуже різним:
Примітка: з цього моменту я припускаю, що ми порівнюємо з системою перцю, яка використовує SHA512
вихідний вихід (не шістнадцятковий).
Високі ентропійні випадкові паролі
Це ваші користувачі, які використовують генератори паролів, які генерують величину великих ключів для паролів. Вони випадкові (генеровані, а не вибрані людиною) і мають високу ентропію на персонажа. Ці типи використовують великі байти (символи> 127) та деякі контрольні символи.
Для цієї групи ваша функція хешування значно зменшить їх доступну ентропію bcrypt
.
Дозвольте мені це ще раз сказати. Для користувачів, які використовують високу ентропію, довгі паролі, ваше рішення значно зменшує надійність їх пароля на помірну величину. (62 біти ентропії втрачено для пароля на 72 символи та більше для довших паролів)
Випадкові паролі середньої ентропії
Ця група використовує паролі, що містять загальні символи, але не мають великих байтів або контрольних символів. Це ваші паролі, які можна ввести.
Для цієї групи ви збираєтеся трохи розблокувати більше ентропії (не створювати її, але дозволити більшій ентропії поміститися в паролі bcrypt). Коли я кажу трохи, я маю на увазі трохи. Беззбитковість виникає, коли ви перевищуєте 512 біт, які має SHA512. Отже, пік становить 78 символів.
Дозвольте мені це ще раз сказати. Для цього класу паролів ви можете зберегти лише додаткові 6 символів, перш ніж вичерпається ентропія.
Низькі ентропійні невипадкові паролі
Це група, яка використовує буквено-цифрові символи, які, ймовірно, не генеруються випадковим чином. Щось на зразок біблійної цитати чи подібного. Ці фрази мають приблизно 2,3 біта ентропії на символ.
Для цієї групи ви можете значно розблокувати більше ентропії (не створювати її, але дозволити більшій кількості вміщуватися у введення пароля bcrypt) шляхом хешування. Беззбитковість становить близько 223 символів, перш ніж ви закінчите ентропію.
Скажімо ще раз. Для цього класу паролів попереднє хешування безумовно значно підвищує рівень безпеки.
Назад до реального світу
Такі види обчислення ентропії насправді не мають великого значення в реальному світі. Важливим є вгадування ентропії. Це те, що безпосередньо впливає на те, що можуть зробити зловмисники. Це те, що ви хочете максимізувати.
Хоча досліджень, пов’язаних із здогадуванням ентропії, мало, є деякі моменти, на які я хотів би звернути увагу.
Шанси випадково вгадати 72 правильних символи поспіль надзвичайно низькі. Швидше за все виграєте у лотерею Powerball 21 раз, ніж у цьому зіткненні ... Ось про яке велике число ми говоримо.
Але ми не можемо натрапити на це статистично. У випадку фраз шанс перших 72 символів бути однаковими набагато вищий, ніж для випадкового пароля. Але він все ще тривіально низький (ви, швидше за все, виграєте в лотерею Powerball 5 разів, виходячи з 2,3 біта на персонажа).
Практично
Практично це не має значення. Шанси когось вгадати перші 72 символи правильно, де останні мають суттєву різницю, настільки низькі, що не варто турбуватися. Чому?
Ну, припустимо, ви берете фразу. Якщо людина може правильно ввести перші 72 символи , їй або по- справжньому пощастило (малоймовірно), або це звичайна фраза. Якщо це загальноприйнята фраза, єдиною змінною є тривалість її створення.
Візьмемо приклад. Візьмемо цитату з Біблії (просто тому, що це поширене джерело довгого тексту, а не з будь-якої іншої причини):
Не бажай дому свого ближнього. Не бажай дружини свого сусіда, ані його невільниці, ані невільниці, ані вола, ані осла, ані чогось, що належить сусідові.
Це 180 символів. 73-й символ - g
другий neighbor's
. Якщо ви так багато здогадалися, ви, швидше за все, не зупиняєтесь nei
, а продовжуєте з рештою вірша (оскільки саме так, швидше за все, буде використовуватися пароль). Тому ваш "хеш" не додав багато.
До речі: Я АБСОЛЮТНО НЕ виступаю за використання біблійних цитат. Насправді, якраз навпаки.
Висновок
Ви насправді не збираєтеся багато допомагати людям, які використовують довгі паролі, спершу хешуючи. Деяким групам ви точно можете допомогти. Деякі ви точно можете нашкодити.
Але зрештою, жодне з них не є надмірно значущим. Цифри, з якими ми маємо справу, просто НАДАЛЬКО високі. Різниця в ентропії не буде великою.
Вам краще залишити bcrypt як є. Ви, швидше за все, зіпсуєте хешування (буквально, ви вже це зробили, і ви не перший чи останній припустився цієї помилки), ніж атака, яку ви намагаєтеся запобігти, відбудеться.
Зосередьтеся на забезпеченні решти сайту. І додайте вимірювач ентропії пароля до вікна пароля при реєстрації, щоб вказати міцність пароля (і вкажіть, якщо пароль довгий, що користувач може побажати його змінити) ...
Це мої $ 0,02 принаймні (або, можливо, більше, ніж 0,02 $) ...
Щодо використання "таємного" перцю:
Буквально немає досліджень щодо введення однієї хеш-функції в bcrypt. Отже, незрозуміло в кращому випадку, якщо додавання "перфорованого" хешу в bcrypt коли-небудь спричинить невідомі вразливості (ми знаємо, що це hash1(hash2($value))
може виявити значні вразливості навколо стійкості до зіткнень та атак перед зображеннями).
Враховуючи, що ви вже розглядаєте можливість зберігати секретний ключ ("перець"), чому б не використовувати його добре вивченим та зрозумілим способом? Чому б не зашифрувати хеш перед його зберіганням?
В основному, після того, як ви хешуєте пароль, передайте весь хеш-результат у сильний алгоритм шифрування. Потім збережіть зашифрований результат.
Тепер атака SQL-Injection не видасть нічого корисного, оскільки вони не мають ключа шифру. І якщо ключ просочився, зловмисники не в кращому випадку, ніж якби ви використовували звичайний хеш (що можна довести, щось із перцем "попередній хеш" не передбачає).
Примітка: якщо ви вирішите це зробити, скористайтеся бібліотекою. Для PHP я настійно рекомендую Zend\Crypt
пакет Zend Framework 2 . Насправді це єдиний, який я б рекомендував на даний момент. Це було ретельно переглянуто, і воно приймає всі рішення за вас (що дуже добре) ...
Щось на зразок:
use Zend\Crypt\BlockCipher;
public function createHash($password) {
$hash = password_hash($password, PASSWORD_BCRYPT, ["cost"=>$this->cost]);
$blockCipher = BlockCipher::factory('mcrypt', array('algo' => 'aes'));
$blockCipher->setKey($this->key);
return $blockCipher->encrypt($hash);
}
public function verifyHash($password, $hash) {
$blockCipher = BlockCipher::factory('mcrypt', array('algo' => 'aes'));
$blockCipher->setKey($this->key);
$hash = $blockCipher->decrypt($hash);
return password_verify($password, $hash);
}
І це вигідно, оскільки ви використовуєте всі алгоритми добре зрозумілими та добре вивченими способами (принаймні відносно). Запам’ятайте:
Будь-яка людина, від самого невідомого аматора до найкращого криптографа, може створити алгоритм, який він сам не може зламати.