Як я можу безпечно зберігати паролі своїх користувачів?


169

Наскільки це безпечніше, ніж звичайний MD5 ? Я щойно почав вивчати безпеку паролів. Я досить новачок у PHP.

$salt = 'csdnfgksdgojnmfnb';

$password = md5($salt.$_POST['password']);
$result = mysql_query("SELECT id FROM users
                       WHERE username = '".mysql_real_escape_string($_POST['username'])."'
                       AND password = '$password'");

if (mysql_num_rows($result) < 1) {
    /* Access denied */
    echo "The username or password you entered is incorrect.";
} 
else {
    $_SESSION['id'] = mysql_result($result, 0, 'id');
    #header("Location: ./");
    echo "Hello $_SESSION[id]!";
}

Примітка, в php 5.4+ є вбудована версія
Бенджамін Грюнбаум

Також дивіться рамку хешування паролів PHP Openwall (PHPass). Його портативний та загартований проти низки поширених атак на паролі користувачів.
jww

1
Обов’язкове "використання PDO замість строкової інтерполяції" для людей, які сьогодні стикаються з цим питанням.
Фонд позову Моніки

Відповіді:


270

Найпростіший спосіб захистити схему зберігання паролів - це використовувати стандартну бібліотеку .

Оскільки безпека, як правило, набагато складніше і з більш невидимими можливостями накручування, ніж більшість програмістів може вирішити самостійно, використання стандартної бібліотеки майже завжди є найпростішим і найбезпечнішим (якщо не єдиним) доступним варіантом.


Новий API для пароля PHP (5.5.0+)

Якщо ви використовуєте PHP версії 5.5.0 або новішої, ви можете використовувати новий спрощений API хешування паролів

Приклад коду за допомогою API пароля PHP:

<?php
// $hash is what you would store in your database
$hash = password_hash($_POST['password'], PASSWORD_DEFAULT, ['cost' => 12]);

// $hash would be the $hash (above) stored in your database for this user
$checked = password_verify($_POST['password'], $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}

(Якщо ви все ще використовуєте спадщину 5.3.7 або новішу версію, ви можете встановити ircmaxell / password_compat, щоб мати доступ до вбудованих функцій)


Поліпшення на солоних хешах: додайте перець

Якщо ви хочете отримати додатковий захист, люди із безпеки зараз (2017) рекомендують додати « перець » до (автоматично) солених хешей паролів.

Існує простий, класний, який надійно реалізує цю модель, я рекомендую: Netsilik / PepperedPasswords ( github ).
Він постачається з ліцензією MIT, тому ви можете користуватися нею як завгодно, навіть у фірмових проектах.

Приклад коду з використанням Netsilik/PepperedPasswords:

<?php
use Netsilik/Lib/PepperedPasswords;

// Some long, random, binary string, encoded as hexadecimal; stored in your configuration (NOT in your Database, as that would defeat the entire purpose of the pepper).
$config['pepper'] = hex2bin('012345679ABCDEF012345679ABCDEF012345679ABCDEF012345679ABCDEF');

$hasher = new PepperedPasswords($config['pepper']);

// $hash is what you would store in your database
$hash = $hasher->hash($_POST['password']);

// $hash would be the $hash (above) stored in your database for this user
$checked = $hasher->verify($_POST['password'], $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}


Стандартна бібліотека OLD

Зверніть увагу: вам цього більше не потрібно! Це лише для історичних цілей.

Погляньте на: Портативний фреймворк хешування паролів PHP : phpass і переконайтеся, що ви використовуєте CRYPT_BLOWFISHалгоритм, якщо це можливо.

Приклад коду за допомогою phpass (v0.2):

<?php
require('PasswordHash.php');

$pwdHasher = new PasswordHash(8, FALSE);

// $hash is what you would store in your database
$hash = $pwdHasher->HashPassword( $password );

// $hash would be the $hash (above) stored in your database for this user
$checked = $pwdHasher->CheckPassword($password, $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}

PHPass був реалізований у деяких досить відомих проектах:

  • phpBB3
  • WordPress 2.5+, а також bbPress
  • випуск Drupal 7, (модуль доступний для Drupal 5 і 6)
  • інші

Хороша річ у тому, що вам не потрібно турбуватися про деталі, ці деталі були запрограмовані людьми з досвідом та переглянуті багатьма людьми в Інтернеті.

Для отримання додаткової інформації про схеми зберігання паролів читайте в блозі Джеффа : Ви, ймовірно, зберігаєте паролі неправильно

Що б ви не робили, якщо ви йдете на підхід « Я зроблю це сам, дякую », не використовуйте MD5або SHA1більше . Вони є хорошим алгоритмом хешування, але вважаються порушеними з метою безпеки .

Наразі, використовуючи крипту , CRYPT_BLOWFISH - найкраща практика.
CRYPT_BLOWFISH у PHP - це реалізація хеша Bcrypt. Bcrypt базується на блоковому шифрі Blowfish, використовуючи дорогі налаштування ключів для уповільнення роботи алгоритму.


29

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


1
Є хороша стаття про безпеку PHP на Nettuts +, також згадується засолювання паролів. Можливо, варто поглянути на: net.tutsplus.com/tutorials/php/…
Fábio Antunes

3
Nettuts + - дуже погана стаття, яку можна використовувати як модель - вона включає в себе використання MD5, який може бути змушений дуже грубо навіть з сіллю. Замість цього, просто використовувати бібліотеку PHPass , яка набагато, набагато краще , ніж будь-який код , який ви можете знайти на сайті підручник, тобто ця відповідь: stackoverflow.com/questions/1581610 / ...
RichVel

11

Кращим способом було б для кожного користувача унікальну сіль.

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

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


3
Солі зазвичай зберігаються разом з хешем пароля (наприклад, вихід crypt()функції). А оскільки вам все одно доведеться отримати хеш паролів, використання конкретної для користувача солі не зробить процедуру дорожчою. (Або ви мали на увазі добування нової випадкової солі дорого? Я насправді не думаю.) Інакше +1.
Іншалла

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

@Inshallah - якщо всі користувачі мають однакову сіль, то ви можете повторно використовувати атаку словника, яку ви використовуєте на user1, проти користувача2. Але якщо кожен користувач має унікальну сіль, вам потрібно буде створити новий словник для кожного користувача, якого ви хочете атакувати.
R Самуїл Клачко

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

@Inshallah - Я думав про випадок, коли у вас база даних перевірена, чи добре хешований пароль (тоді у вас є одне завантаження db для отримання солі та другий доступ db для перевірки хешованого пароля). Ви маєте рацію про випадок, коли ви завантажуєте сіль / хеш-пароль в одному завантаженні, а потім виконуєте порівняння на клієнті. Вибачте за непорозуміння.
R Самуїл Клачко

11

З PHP 5.5 (те, що я описую, доступне і для більш ранніх версій, див. Нижче) я б хотів запропонувати використовувати його нове вбудоване рішення: password_hash()і password_verify(). Він надає кілька варіантів для досягнення необхідного рівня захисту пароля (наприклад, за допомогою параметра "вартість" через $optionsмасив)

<?php
var_dump(password_hash("my-secret-password", PASSWORD_DEFAULT));

$options = array(
    'cost' => 7, // this is the number of rounds for bcrypt
    // 'salt' => 'TphfsM82o1uEKlfP9vf1f', // you could specify a salt but it is not recommended
);
var_dump(password_hash("my-secret-password", PASSWORD_BCRYPT, $options));
?>

повернеться

string(60) "$2y$10$w2LxXdIcqJpD6idFTNn.eeZbKesdu5y41ksL22iI8C4/6EweI7OK."
string(60) "$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d."

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

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

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

Перевірка таких робіт:

var_dump(password_verify("my-secret-password", '$2y$10$BjHJbMCNWIJq7xiAeyFaHOGaO0jjNoE11e0YAer6Zu01OZHN/gk6K'));
var_dump(password_verify("wrong-password", '$2y$10$BjHJbMCNWIJq7xiAeyFaHOGaO0jjNoE11e0YAer6Zu01OZHN/gk6K'));

var_dump(password_verify("my-secret-password", '$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d.'));
var_dump(password_verify("wrong-password", '$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d.'));

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

Існує невелика бібліотека (один PHP-файл), яка надасть вам PHP 5.5 password_hashу PHP 5.3.7+: https://github.com/ircmaxell/password_compat


2
У більшості випадків краще опустити параметр солі. Функція створює сіль з випадкового джерела операційної системи, є дуже мало шансів, що ви можете забезпечити кращу сіль самостійно.
martinstoeckli

1
Це я написав, чи не так? "якщо не вказана сіль, вона генерується випадковим чином, тому бажано не вказувати сіль"
akirk

Більшість прикладів показує, як додати обидва параметри, навіть коли не рекомендується додавати сіль, тому мені цікаво чому? Якщо чесно, я читав лише коментар за кодом, а не в наступному рядку. У будь-якому випадку, чи не було б краще, коли приклад показує, як найкраще використовувати цю функцію?
martinstoeckli

Ти маєш рацію, я згоден. Я відповідно змінив свою відповідь і прокоментував рядок. Спасибі
akirk

як я повинен перевірити, чи збережені та введені паролі однакові? Я використовую password_hash()і password_verifyнезалежно від того, який пароль (правильний чи ні) я використовував, я закінчую правильний пароль
Brownman Revival

0

Зі мною це добре. Містер Етвуд писав про силу MD5 проти веселкових столів , і в основному з такою довгою сіллю, як ти сидиш симпатично (хоча деякі випадкові розділові знаки / цифри, це може покращити її).

Ви також можете подивитися на SHA-1, який, здається, стає все популярнішим в наші дні.


6
Примітка внизу допису містера Етвуда (червоним кольором) посилається на інший пост від практикуючого з безпеки, який заявляє, що використовують MD5, SHA1 та інші швидкі хеши для зберігання паролів - дуже помилково.
sipwiz

2
@Matthew Scharley: Я не погоджуюся, що додаткові зусилля, що накладаються дорогими алгоритмами хешування паролів, є хибною безпекою. Це захищати від жорстокого форсування легко вподобаних паролів. Якщо ви обмежуєте спроби входу, то ви захищаєтесь від того ж самого (хоча трохи ефективніше). Але якщо супротивник має доступ до хешів, що зберігаються в БД, він зможе досить швидко (відповідно до того, наскільки легко вподобати) змусити такі (легко зрозуміти) паролі. За замовчуванням для алгоритму криптовалюти SHA-256 - 10000, тому це зробило б це в 10000 разів складніше.
Іншалла

3
Повільний хеш насправді робиться шляхом повторної ітерації дуже великої кількості разів та переміщення даних між ними між кожною ітерацією. Мета полягає в тому, щоб навіть якщо поганий хлопець отримав копію хешей вашого пароля, йому доведеться спалити значну кількість процесорного часу для перевірки свого словника на ваші хеші.
caf

4
@caf: Я вважаю, що алгоритм bcrypt використовує параметризуемую дорогості планування ключів Eksblowfish; не зовсім впевнений, як це працює, але планування клавіш - це дуже дорога операція, яка виконується під час init шифрувального контекстного об'єкта перед тим, як проводиться будь-яке шифрування.
Іншалла

3
Іншалла: Це правда - алгоритм bcrypt - це інша конструкція, де основним крипто-примітивом є блок-шифр, а не хеш-функція. Я мав на увазі схеми, засновані на хеш-функціях, як криптовалюта MD5 MDK PHK ().
caf

0

Я хочу додати:

  • Не обмежуйте паролі користувачів за довжиною

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

  • Не надсилайте паролі користувачів електронною поштою

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

  • Оновіть хеші паролів користувачів

Хеш паролів може бути застарілим (параметри алгоритму можуть бути оновлені). За допомогою функції password_needs_rehash()ви можете перевірити її.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.