Ответ 1
Предисловие
Начиная с определения вашей таблицы:
- 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()
хэширования
Хранить обратимый пароль в вашей базе данных следует избегать как можно больше; вы просто хотите проверить пароль, а не знать его содержимое. Если пользователь теряет свой пароль, лучше разрешить им reset, а не отправлять их исходный (убедитесь, что пароль reset может быть выполнен только в течение ограниченного времени).
Применение хэш-функции - односторонняя операция; впоследствии его можно безопасно использовать для проверки без выявления исходных данных; для паролей метод грубой силы является приемлемым подходом к его раскрытию из-за относительно короткой длины и низкого выбора пароля для многих людей.
Хеширующие алгоритмы, такие как MD5 или SHA1, были сделаны для проверки содержимого файла с известным значением хэш-функции. Они сильно оптимизированы, чтобы сделать эту проверку как можно быстрее, но при этом быть точным. Учитывая их относительно ограниченное пространство на выходе, было легко создать базу данных с известными паролями и их соответствующими выходами хэшей, радужными таблицами.
Добавление соли к паролю перед хэшированием приведет к тому, что таблица радуги будет бесполезной, но недавние улучшения аппаратного обеспечения заставили грубую силу найти жизнеспособный подход. Вот почему вам нужен алгоритм хэширования, который преднамеренно медленно и просто невозможно оптимизировать. Он также должен быть способен увеличить нагрузку для более быстрого оборудования, не влияя на возможность проверки существующих хэшей паролей, чтобы сделать это будущим доказательством.
В настоящее время доступны два популярных варианта:
- PBKDF2 (функция деривации пароля на основе пароля v2)
- bcrypt (aka 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,40 с 3GHz) и соль 22 символа.
Validation
После того, как вы выберете строку, содержащую пользовательскую информацию, вы подтверждаете пароль следующим образом:
$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()