Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
Мир в моей голове рухнул и я решил написать эту статью.
Caution
Using the PASSWORD_BCRYPT for the algo parameter, will result in the password parameter being truncated to a maximum length of 72 characters. This is only a concern if are using the same salt to hash strings with this algorithm that are over 72 bytes in length, as this will result in those hashes being identical.
Помножил защиту на ноль разработчик password_hash/password_verify игнорируя то, что переданный ему параметр длиннее 72 байтов и не выкидывая exception
<?php
echo password_verify($password. $pepper, $hash) ? 'Login OK' : 'Wrong password';
Завтра правят эту конкретную реализацию в пхп, которая СЕЙЧАС обрезает (и она больше не обрезает).
Но это так, чем конкретно идея перца плохаОна не плоха, и я завсегда за. Но если с головой!
Потому что вы вводили перец чтобы усилить безопасность, а такой реализацией вы ее ослабили
$hash = password_hash(hash('sha512', ($pwd . $pepper_very_long), 1) . $pepper_8_bytes, PASSWORD_BCRYPT)
Я против этой конкретной и других не обдуманых реализаций.sha2 отработает всю последовательность глобального ключа.
<?php
$pepper_very_long = 'very very long pepper - 56448s785o7856pv85c jh 78o78 8po5c8 7töl öl7öl87ö7 p78t896cüü üä69ä öpö 9ö6 ö öö ö ö 76876 876 lk lg77 7p li5xlu87xo764o7o476x4kl76kl';
$pepper_8_bytes = '01234567';
$pwds = array(
'abc',
'abcd',
'very very long password very very long password very very long password',
'very very long password very very long password very very long password2'
);
foreach ($pwds as $pwd) {
$len = strlen(hash('sha512', ($pwd . $pepper_very_long), 1) . $pepper_8_bytes);
if ($len != 72) {
throw new Exception("Invalid length for bcrypt \"$len\" ...");
};
$hash = password_hash(hash('sha512', ($pwd . $pepper_very_long), 1) . $pepper_8_bytes, PASSWORD_BCRYPT);
?>
<b><?= $pwd ?></b> : <br/>
<?= $hash ?><br/>
positive: <?= password_verify(hash('sha512', ($pwd . $pepper_very_long), 1) . $pepper_8_bytes, $hash)
? '<b style=color:green>OK</b>' : '<b style=color:red>BOOM</b>' ?>,
<?php $pwd = ''; ?>
negative: <?= (!password_verify(hash('sha512', ($pwd . $pepper_very_long), 1) . $pepper_8_bytes, $hash))
? '<b style=color:green>OK</b>' : '<b style=color:red>BOOM</b>' ?>,
<br/>
<?php } ?>Проблема инвалидации — Если Вы захотите его сменить, например, в случае утечкито Вам нужно просто перешифровать все хэши (и соли) новым ключем.
Проблема храненияэту проблему придумал автор — ее нет. Он вероятно пока из тех, кто хранит все деньги в одном банке.
Проблема «иллюзии защищенности»этой проблемы тоже не существует.
Проблема реализации — Не существует ни одной академической работы или RFC...Тут автор сам себя переплюнул: а ничего что тоже самое можно про bcrypt сказать? Но многие и автор статьи в том числе его используют…
<?php
echo password_verify($password. $password, $hash) ? 'Login OK' : 'Wrong password';
Если не понимаете ни черта ни в криптографии...
Просто потом зашифруйте хэш (и соль если есть) каким-нибудь секретным ключом (в качестве «перца») и начальным вектором, который постояннен для пользователя например id (в качестве «приправы») любым симметричным алгоритмом, хоть тем же aes.
… начальным вектором...
… любым симметричным алгоритмом...
… тем же aes.
<?php
$pepper_very_long = 'very very long pepper ...';
// -----------------
function pepper_encode( $hash, $pepper_very_long, $pepper_cor_user, $mode ) {
$td = mcrypt_module_open ('rijndael-256', '', 'ofb', '');
// pepper initial vector:
$pepper_iv = substr( hash('sha512', $pepper_cor_user, 1) , 0, mcrypt_enc_get_iv_size($td) );
// pepper key:
$pepper_key = substr( hash('sha512', ($pepper_cor_user . $pepper_very_long), 1) , 0, mcrypt_enc_get_key_size($td) );
// intialize encryption ... and encrypt :
mcrypt_generic_init($td, $pepper_key, $pepper_iv);
// encrypt or decrypt (if hash is a encrypted, cause we are symetrical) :
switch ($mode) {
case 'encrypt' :
$encrypted = base64_encode(mcrypt_generic($td, $hash));
break;
case 'decrypt' :
$encrypted = mdecrypt_generic($td, base64_decode($hash));
break;
default:
throw new Exception("Error ...", 1);
}
// free handles and close module ...
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
return $encrypted;
}
// ---------------
$userid = 555;
$pepper_vector = "vector: $userid / $userid";
$pwds = array(
'abc',
'abcd',
'very very long password very very long password very very long password',
'very very long password very very long password very very long password2'
);
foreach ($pwds as $pwd) {
// bcrypt - we have hash...
$hash = password_hash( $pwd , PASSWORD_BCRYPT );
// encrypt it for database :
$encr_hash_4_db = pepper_encode($hash, $pepper_very_long, $pepper_vector, 'encrypt');
?>
<b><?= $pwd ?></b> : <br/>
bcrypt: <?= $hash ?> <br/> with pepper 4 DB: <?= $encr_hash_4_db ?> <br/>
<?php
// decode with pepper :
$decr_hash = pepper_encode($encr_hash_4_db, $pepper_very_long, $pepper_vector, 'decrypt');
// test cases :
?>
decrypt: <?= $decr_hash ?> <br/>
positive: <?= password_verify( $pwd, $decr_hash )
? '<b style=color:green>OK</b>' : '<b style=color:red>BOOM</b>' ?>,
negative1: <?= ( !( password_verify( ($pwd.'-') , $decr_hash ) ) )
? '<b style=color:green>OK</b>' : '<b style=color:red>BOOM</b>' ?>,
negative2: <?= ( !( password_verify( ('-'.$pwd) , $decr_hash ) ) )
? '<b style=color:green>OK</b>' : '<b style=color:red>BOOM</b>' ?>,
negative3: <?= ( !( password_verify( '' , $decr_hash ) ) )
? '<b style=color:green>OK</b>' : '<b style=color:red>BOOM</b>' ?>,
<br/>-----------------<br/>
<?php } ?>
Боюсь с вашим дано-найти то не очень работает (если "перец" не есть ключ симметричного шифрования).
Выше я имел ввиду что посоленные пароли (хеши) перед записью в базу шифруются симметричным алгоритмом (например AES) единственным секретным ключом (aka "k1"), не хранящимся в базе.
Тогда процедура перешифрования на k2 при возможной компрометации k1 выглядит след. образом:
for usrid, pwdhash in get_all_usr_pwd():
pwdhash = AES.decrypt(pwdhash, k1);
pwdhash = AES.encrypt(pwdhash, k2)
write_usr_pwd(usrid, pwdhash)Если вы подмешали ваш "перец" в процесс генерации хеша (и этот процесс не симметричный), то тогда перешифрование возможно только в процессе авторизации для каждого пользования отдельно.
Не все читают документацию От и До.Вот это-то как раз и плохо, а в случае с security еще и безответственно.
For each key derivation function, we consider six different types of password:
— A random sequence of 6 lower-case letters; e.g., “sfgroy”.
— A random sequence of 8 lower-case letters; e.g., “ksuvnwyf”.
— A random sequence of 8 characters selected from the 95 printable 7-bit ASCII characters; e.g., “6,uh3y[a”.
— A random sequence of 10 characters selected from the 95 printable 7-bit ASCII characters; e.g., “H.*W8Jz&r3”.
— A 40-character string of text; e.g., “This is a 40-character string of English”.
— An 80-character string of text; e.g., “This is an 80-character phrase which you probably won’t be able to crack easily.”.
Зачем использовать bcrypt и PBKDF2 (aka PKCS#5) если есть scrypt?
Скорость работы scrypt вполне удовлетворительна — порядка сотен логинов в секунду.
Чтобы усложнить жизнь при атаке перебора следует дописать соль к паролю, а не наоборот (для людей, которые пишут слева направо, конечно).
Как раз хотел написать статью про практическую реализацию с примерами ошибок реализации хеширования.
Тем не менее, вы сделали полезное дело, что написали людям про свои ошибки. Спасибо!
Но это не имеет отношения к тем методам, которые я предлагал, так как такой код в моей статье никак не упоминался.
Также в статье, которую вы почему-то решили хаить, отдельно написано про необходимость первым передавать в подхешевое выражение пароль, а вы сделали наоборот
пароль1 на пароль2, я просто заменю соль на соль циклический_xor пароль1 циклический_xor пароль2отдельно написано про необходимость первым передавать в подхешевое выражение пароль, а вы сделали наоборот :)А если не bcrypt, а другая реализация? Которая «режет» сначала?
$hash = password_hash(hash('sha512', ($pepper_very_long . $pwd), 1) . $pepper_8_bytes, PASSWORD_BCRYPT)
$hash = password_hash(hash('sha512', ($pwd . $pepper_very_long), 1) . $pepper_8_bytes, PASSWORD_BCRYPT)
кешированияБлин, хэширование, а не кэширование. Это разные вещи, как так можно опечатываться?
Такая вероятностная характеристика для SHA2 справедлива только при условии, что длинна $pwd + $pepper_very_long больше размера хэшируемого блока (1024 для SHA2-512). Если размер этой последовательности короче размера блока (т.е. хэшируем уже не последовательно, а за один раз), то совершенно не важно где стоит (соль или перец).
Вот из-за таких грамотеев...А можно ссылку на рекомендацию IETF, где стоит что результат HMAC_SHA512 можно обернуть bcrypt'ом или хоть каким-нибудь другим алгоритмом. Ну или хотя бы про «перчение» любым MAC-ом?
To achieve this goal, it is important that inputs to KDF are selected from appropriate input distributions and also that inputs are chosen independently of each other (technically, it is necessary that each sample will have sufficient entropy, even when conditioned on other inputs to KDF).Иначе говоря, вопрос вам на засыпку, грамотей вы наш: Оцените зависимость любой MAC конструкции как псевдослучайной функции на последовательностях UNIQUE_SALT (+) GLOBAL_PEPPER. (Подсказка для UNIQUE_SALT примем энтропию как Hmax, для постоянного GLOBAL_PEPPER энтропия равна нулю).
key lengths less than the output length decrease security strength, and keys longer than the output length do not significantly increase security strengthЭто к тому, что «откройте для себя HMAC, наконец. Хватит склеивать.»
for(n = 1..18)
Pn \gets key[32(n-1)..32n-1]
В результате используются 32*18 бита из пароля = 576 / 8 = 72 байта. Перепишите этот цикл подлиннее и будет вам счастье (но это будет уже не bcrypt).hash = sha256(salt, password)
Как испортить безопасность паролей, следуя советам с Хабра