Передмова
Починаючи з визначення таблиці:
- UserID
- Fname
- Lname
- Email
- Password
- IV
Ось такі зміни:
- Поля
Fname
, Lname
і Email
будуть зашифровані з допомогою симетричного шифру, що надається OpenSSL ,
IV
Поле буде зберігати вектор ініціалізації , використовуваний для шифрування. Вимоги до зберігання залежать від шифру та використовуваного режиму; докладніше про це пізніше.
Password
Поле буде хешіруются з використанням односторонню хеш пароля,
Шифрування
Шифр і режим
Вибір найкращого шифрувального шифру та режиму виходить за межі цієї відповіді, але остаточний вибір впливає на розмір як ключа шифрування, так і вектора ініціалізації; для цієї посади ми будемо використовувати AES-256-CBC, який має фіксований розмір блоку 16 байт і розмір ключа або 16, 24 або 32 байти.
Ключ шифрування
Хороший ключ шифрування - це двійковий крап, який генерується з надійного генератора випадкових чисел. Наступний приклад рекомендується (> = 5.3):
$key_size = 32; // 256 bits
$encryption_key = openssl_random_pseudo_bytes($key_size, $strong);
// $strong will be true if the key is crypto safe
Це можна зробити один або кілька разів (якщо ви хочете створити ланцюжок ключів шифрування). Зберігайте їх як можна більш приватними.
IV
Вектор ініціалізації додає випадковість шифруванню і необхідний для режиму CBC. Ці значення слід в ідеалі використовувати лише один раз (технічно один раз за ключ шифрування), тому оновлення будь-якої частини рядка має відновити її.
Надається функція, яка допоможе вам генерувати IV:
$iv_size = 16; // 128 bits
$iv = openssl_random_pseudo_bytes($iv_size, $strong);
Приклад
Давайте зашифруємо поле імен, використовуючи раніше $encryption_key
і $iv
; для цього нам доведеться перекласти наші дані до розміру блоку:
function pkcs7_pad($data, $size)
{
$length = $size - strlen($data) % $size;
return $data . str_repeat(chr($length), $length);
}
$name = 'Jack';
$enc_name = openssl_encrypt(
pkcs7_pad($name, 16), // padded data
'AES-256-CBC', // cipher and mode
$encryption_key, // secret key
0, // options (not used)
$iv // initialisation vector
);
Вимоги до зберігання
Зашифрований вихід, як і IV, є двійковим; зберігання цих значень у базі даних може бути здійснено за допомогою визначених типів стовпців, таких як BINARY
або VARBINARY
.
Вихідне значення, як і IV, є двійковим; щоб зберегти ці значення в MySQL, розглянемо використання BINARY
абоVARBINARY
стовпці. Якщо це не варіант, ви також можете перетворити бінарні дані в текстове подання, використовуючи base64_encode()
або bin2hex()
, для цього потрібно від 33% до 100% більше місця для зберігання.
Розшифрування
Розшифровка збережених значень схожа:
function pkcs7_unpad($data)
{
return substr($data, 0, -ord($data[strlen($data) - 1]));
}
$row = $result->fetch(PDO::FETCH_ASSOC); // read from database result
// $enc_name = base64_decode($row['Name']);
// $enc_name = hex2bin($row['Name']);
$enc_name = $row['Name'];
// $iv = base64_decode($row['IV']);
// $iv = hex2bin($row['IV']);
$iv = $row['IV'];
$name = pkcs7_unpad(openssl_decrypt(
$enc_name,
'AES-256-CBC',
$encryption_key,
0,
$iv
));
Підтверджене шифрування
Ви можете додатково поліпшити цілісність згенерованого тексту шифру, додавши підпис, створений секретним ключем (відмінним від ключа шифрування) та текстом шифру. Перш ніж розшифрувати текст шифру, підпис спочатку перевіряється (бажано методом порівняння з постійним часом).
Приклад
// generate once, keep safe
$auth_key = openssl_random_pseudo_bytes(32, $strong);
// authentication
$auth = hash_hmac('sha256', $enc_name, $auth_key, true);
$auth_enc_name = $auth . $enc_name;
// verification
$auth = substr($auth_enc_name, 0, 32);
$enc_name = substr($auth_enc_name, 32);
$actual_auth = hash_hmac('sha256', $enc_name, $auth_key, true);
if (hash_equals($auth, $actual_auth)) {
// perform decryption
}
Дивитися також: hash_equals()
Хешинг
Зберігання реверсивного пароля у вашій базі даних слід уникати якомога більше; ви хочете лише перевірити пароль, а не знати його вміст. Якщо користувач втрачає свій пароль, краще дозволити його скинути, а не надсилати їм оригінальний (переконайтеся, що скидання пароля може бути здійснено лише протягом обмеженого часу).
Застосування хеш-функції - це одностороння операція; після цього його можна безпечно використовувати для перевірки, не розкриваючи вихідні дані; що стосується паролів, метод грубої сили - це здійсненний підхід до їх розкриття через його порівняно невелику довжину та неправильний вибір пароля багатьох людей.
Алгоритми хешування, такі як MD5 або SHA1, були створені для перевірки вмісту файлу на рівні відомого хеш-значення. Вони значно оптимізовані для того, щоб зробити цю перевірку якомога швидшою, але при цьому залишаючись точною. Зважаючи на їх відносно обмежений вихідний простір, було легко створити базу даних з відомими паролями та їх відповідними хеш-виводами, таблицями веселок.
Додавання солі до пароля до хешування, це зробить райдужну таблицю марною, але останні вдосконалення обладнання зробили грубу силу пошуку життєздатним підходом. Ось чому вам потрібен алгоритм хешування, який навмисно повільний і просто неможливий для оптимізації. Він також повинен мати можливість збільшити навантаження на більш швидке обладнання, не впливаючи на можливість перевірки існуючих хешей паролів, щоб зробити його подальшим доказом.
Наразі доступні два популярні варіанти:
- PBKDF2 (Функція виведення ключа на основі пароля v2)
- bcrypt (він же Blowfish)
У цій відповіді буде використаний приклад з bcrypt.
Покоління
Хеш паролів може генеруватися так:
$password = 'my password';
$random = openssl_random_pseudo_bytes(18);
$salt = sprintf('$2y$%02d$%s',
13, // 2^n cost factor
substr(strtr(base64_encode($random), '+', '.'), 0, 22)
);
$hash = crypt($password, $salt);
Сіль генерується з, openssl_random_pseudo_bytes()
щоб утворити випадкову крапку даних, яка потім проходить через base64_encode()
і strtr()
відповідає необхідному алфавіту [A-Za-z0-9/.]
.
У crypt()
функції виконує хешування на основі алгоритму ( $2y$
для Blowfish), фактор вартості (коефіцієнт 13 займає приблизно 0.40s на машині 3GHz) і солі 22 символів.
Перевірка
Після вибору рядка, що містить інформацію про користувача, ви перевіряєте пароль таким чином:
$given_password = $_POST['password']; // the submitted password
$db_hash = $row['Password']; // field with the password hash
$given_hash = crypt($given_password, $db_hash);
if (isEqual($given_hash, $db_hash)) {
// user password verified
}
// constant time string compare
function isEqual($str1, $str2)
{
$n1 = strlen($str1);
if (strlen($str2) != $n1) {
return false;
}
for ($i = 0, $diff = 0; $i != $n1; ++$i) {
$diff |= ord($str1[$i]) ^ ord($str2[$i]);
}
return !$diff;
}
Щоб підтвердити пароль, ви дзвоните crypt()
ще раз, але передаєте раніше обчислений хеш як значення солі. Повернене значення дає той самий хеш, якщо даний пароль відповідає хешу. Щоб перевірити хеш, часто рекомендується використовувати функцію порівняння в постійному часі, щоб уникнути атак часу.
Хешування паролів із PHP 5.5
PHP 5.5 представив функції хешування паролів, які можна використовувати для спрощення описаного вище способу хешування:
$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 13]);
І перевірка:
if (password_verify($given_password, $db_hash)) {
// password valid
}
Дивіться також: password_hash()
,password_verify()