Как стать автором
Обновить

Комментарии 331

теперь нас научили солить
и не сравнили алгоритмы хэширования
Причём не вдаваясь в подробности зачем это нужно и как это делать.

1. bcrypt'ованый пароль уже включает соль
2. bcrypt не участвовал и не выигрывал на конкурсах хеширования
3. Нет основания доверять bcrypt, его анализом никто не занимался, а его реализация для PHP имеет коллизии

Относящееся к bcrypt можно также утверждать в отношении scrypt.

bcrypt, а scrypt ещё в большей мере, создают дичайшую нагрузку, в первом случае по процессору, а во втором ещё и по памяти, если кто-то решил устроить перебор паролей через веб-формы.

Так что то, что автор тут нам советует, защищает от одного вектора атак, но подставляет под другие.
Проще говоря, нужно солить sha512("{$user->password}{$user->salt}{$global_salt})?

Ещё я интуитивно делаю хешеривание на стороне приложения, а не в БД — избегаю передачи пароля и глобальной соли лишний раз даже на локальной машине по файловым сокетам, не говоря о сети — мало ли кто как к каналу присосался. Или к логам БД.
«Ещё я интуитивно делаю хешеривание на стороне приложения, а не в БД — избегаю передачи пароля и глобальной соли лишний раз даже на локальной машине по файловым сокетам, не говоря о сети — мало ли кто как к каналу присосался. Или к логам БД.»

напишите как схема работает, клиент передает hash(login.salt), где hash вычисляется на JavaScript?
Речь идет о серверной части веб-приложения. То ксть, клиент тут не при чем. Коммент выше просто о том, что хэш вычисляется перед помещением в запрос, а не на стороне СУБД.

А вариант с клиентом не будет работать. Digest авторизация несовместима с хэшированем паролей, увы.
Речь о сервере — клиент передаёт в открытом (для сервера приложений) виде, тот сам хэширует и передаёт СУБД уже хэш. Перехват канала сервер — СУБД или логирование запросов СУБД не даст злоумышленнику значений паролей и глобальной соли, только индивидуальную соль сможет получить и общий хэш — пароль и глобальный хэш ему останутся недоступными.
Предположу что как нибудь так:

var hash = CryptoJS.SHA3(Password + CryptoJS.SHA3(Login));

А уже на сервере этот хеш солить случайным и глобальным хешем.

Хотя нафига, если можно просто зайти по https каналу?
А можно так: sha512($user->secret_formula($user->password,$user->salt)).
А функция secret_formula мешает особым образом байты пароля, соли и $global_salt в зависимости от например $user->id (и естественно тоже находится не в базе).

А вообще всегда интересовал вопрос: накой нужно подбирать пароль, если уже вся база слита? Из-за теоретической возможности через год залезть в систему с тем же паролем, если дыру закрыли?
Многие используют одинаковые регистрационные данные на разных сервисах. Подобрали пароль к какому-то локальному чатику, а получили к веб-мани и т. п. Ну или базу слили, а хочется в ней что-то изменить.
Существует несколько общепринятых объяснений.
Одно из них — болшинство хомячков имеет один и тот же пароль подо все сервисы => можно уводить мыло, твиттер и пр.
Или, авторизовавшись можно получить доступ на запись, а слив мог быть произведён только через чтение — и пр.
Нет. Из-за теоретической возможности слить только базу паролей, без остальных нужных злоумышленнику данных.
Кстати наиболее известный мне защищенный от подбора способ, это вместо глобального «соления» хранить в базе связку хэш и соль созданую как описано, хоть тем же blowfish, дополнительно «упакованые» aes-ом или twofish-ем, для этого обязательно использовать глобальную связку ключ + начальный вектор (оба не из базы), потому как оба эти алгоритма (blowfish к сожалению нет) вариируют шифр при упаковке каждый раз (т.е. рандомные) при одинаковых ключах. Подобрать такое (плюс еще и неизвестен хэш) практически не реально.

Пример, зашифровано 12345 (скажем такой хэш:) ключем 0123456789ABCDEF вектор IVXYZ (прим. *-wo без вектора, только ключ):
aes-wo:		MC_	hex: 4d43075f14	
aes/iv:		pÚ=Pï	hex: 70da3d50ef
twofish-wo:	íX]z!	hex: ed585d7a21	
twofish/iv:	ùÒz	hex: 0bf9d2027a
blowfish-wo:	™·Pèa	hex: 99b750e861
blowfish/iv:	íuxŠ	hex: c3ad75788a
и еще раз (как видим aes/iv и twofish/iv изменились):
aes-wo:		MC_	hex: 4d43075f14	
aes/iv:		ôåÚ2Ý	hex: f4e5da32dd	
twofish-wo:	íX]z!	hex: ed585d7a21	
twofish/iv:	õU²¶	hex: 0cf555b2b6	
blowfish-wo:	™·Pèa	hex: 99b750e861	
blowfish/iv:	íuxŠ	hex: c3ad75788a	

Не зная ключа и вектора, практически невозможно подобрать пароль, потому что даже выковырять хэш и соль из динамичного шифра — это очень большая проблема.
Эх. Зачем это, скажем, форуму, на котором 3.5 анонимуса?
ему не надо, но там и солить не сильно нужно…
А так это ж просто, практически двумя строчками, не сильно усложноив алгоритм соления (при создании зашифровать, при логине расшифровать значение из базы), настолько усложнить жизнь хакеру, что можно даже скормить ему базу (я утрирую), нехай терзает свое железо.
Солить нужно всегда. В отличие от подключения современных хэш-функций (которых может не быть в стандартной библиотеке используемого языка/фреймворка), для соления паролей вообще не надо никаких трудозатрат.
НЛО прилетело и опубликовало эту надпись здесь
Вы не путайте теплое с мягким. Это если бы я sha512 сверху md4 обернул, тогда да, а так одна мешанина заменяется на другую (не предсказуемо ни то, ни другое), а хэширующая функция одна и та же.
Спор про то куда ставить соль, в начало или в конец, вылазит в каждом топике про соль. Часто, люди которые спорят, не представляют, что хэш-функции оперируют с битами и уже после 10-й (иногда 5-й) итерации абсолютно все равно, где была соль.
НЛО прилетело и опубликовало эту надпись здесь
так то оно так, но в этом разделе к subj относится только
Compare these minor benefits to the risks of accidentally implementing a completely insecure hash function and the interoperability problems

«риск что-то накосячить», т.е. в общем FUD (хотя риск конечно есть).

весь основной текст в разделе относится к тому что не нужно так делать чтобы получить выгоду (типа «новый секретный алгоритм») т.к. исходник обычно известен злоумышленнику.
НЛО прилетело и опубликовало эту надпись здесь
ответил здесь. там хоть дискусия какая-никакая, конструктивная. У вас же не делай так потому, что низя, а почему низя — сам не знаю. Вы хоть раз, криптоанализ делали? Потому по этим ссылкам либо чушь, либо часный вырожденый случай…
Видимо надо все же развернуть ответ:

На примере SHA-512: функция работает с блоками 1024 bit при длинне слова 64 bit (длинна констант).
Чтобы соблюдать все мыслемые каноны безопасности хэширования с солью, нужно придержаться всего 2-х правил:
— сумма длинн соли и пароля желательна быть длиннее 1024 bit и в любом случае не кратна 1024;
— длинны соли и пароля по отдельности не должны быть кратны 1024 bit, и по возможности не кратны 64 bit;

Если не соблюдать эти правила, как раз в случае с последовательностью «пароль.соль» теоретически а для некоторых функций уже практически можно использовать коллизии, а в лучшем случае создать таблицы из незаконченых хэшей например для паролей с длинной кратной 4, 8, 12, 16… символам (*1). И заканчивать эти хэши перебором используя соль из базы. Ускорить таким образом перебор можно и для паролей с другой длинной, т.к. можно быстрее исключить все пароли с длинами 4, 8, и т.д (длинны зависят от конкретной реализации хэш функции).
Кроме того это вообще не касается хэш-функций, которые нельзя считать последовательно и которые считают хэш не поблочно, а всегда целым куском.

А вот как раз последовательность «соль.пароль», «соль1.пароль.соль2» или в идеальном случае произвольная для каждого пользователя смесь является наиболее стойкой, т.к. в этом случае невозможно использование никаких таблиц.
И в случае возможного нахождения в будущем коллизий для функции, как раз при использовании произвольньной смеси для каждого пользователя, необходимо перестройка всех таблиц для каждого пользователя, что практически полностью исключает их использование.

Единственный же вырожденый часный случай, в котором можно получить незначительное минимальное ускорение брута на последовательности «соль.пароль» — ТОЛЬКО когда длинна соли кратна длинне блока хэш функции (т.к. каждую итерацию брут-атаки можно начинать с подготовленого незаконченого хэш блока от соли, для sha512 это 1024 бита). Но, как говорилось уже это часный случай и противоречит 2-му правилу и это все равно остается перебором.

Если же мы солим ключи (что-то подлиннее пароля) то последовательность «ключь.соль» является наиболее опасной в случае кратности длинны ключа размеру блока хэш-функции. Для примера SHA-512 — это ключи длинной 128, 256, 512… байт. Вот тогда таблицы из незаконченых хэшей очень усоряют процесс подбора.

*1 — не путать с радужными таблицами из законченых хэшей, их можно применять только в случае когда соли нет вовсе.
ускорение брута на последовательности «соль.пароль» — ТОЛЬКО когда длинна соли кратна длинне блока хэш функции (т.к. каждую итерацию брут-атаки можно начинать с подготовленого незаконченого хэш блока от соли, для sha512 это 1024 бита)

Что-то я не понял.
Для hash(соль.пароль) можно заранее посчитать внутренее представление hash(соль).
(при этом речь не идёт о том чтобы финализировать этот хэш и выдать его в hex виде, например). Просто в нашем языке программирования скармливаем соль в хэшфункцию и делаем метод clone внутреннего состояния хэша. Это должно быть справедливо для любой хэш-функции (из привычных sha1, sha2 итд)
клонирование должно быть таким же быстрым как memcopy, всё остальное — погрешности реализации.
да нет же, блин.
это и в принципе-то возможно ТОЛЬКО если длинна соли КРАТНА длинне блока хэшируемого последовательно… см. ниже.
А про клонирование, это вообще отдельная тема.
Что-то я не понял.
К сожалению не только вы...
можно заранее посчитать внутренее представление hash(соль)
Только если длинна соли кратна размеру блока последовательно хэшируемой функции.
Пусть у нас функция хэширует поблочно 32 бита или 4 байта (ну так короче объяснить):
В случае когда соль длинной 4 или 8 байт это можно сделать:
[SsSs][PpPp]Pp…
[SsSs][SsSs][PpPp]Pp…
т.е. соли SsSs SsSsSsSs для этой хэш-функции «не стойкие», потому что мы можем их заранее подготовить и всегда начинать перебор с подготовленного пре-хэша хэшируя сверху подбираемые пароли…
В случае когда соль длинной например 3 байт этого сделать невозможно:
[SsSP][pPpP]p…
Просто потому что в первом же хэшируемом блоке есть и неизвестная составляющяя из пароля (соль злоумышленнику известна, пароль — нет).
хм. теперь понял.

ну и в случае [SsSs][SsSP][pPpP], можно «заготовить» [SsSs].

можно сказать что «заготовить» можно L — L % 4 байтов, где L — длина соли.

т.е. если «экономию» в вычислениях считать в байтах, то она максимальна когда длина соли кратна длине блока. и чуть меньше, когда не кратна.

если вырожденный случай, когда она = 0 (в случае если соль меньше блока).

совет не использовать длину кратную длине блока, позволяет избежать «экономии» злоумышленником максимум L-1 байтов (т.е. 3 байтов).

так?
да, но не совсем, просто если соль спереди то смысла в соли длиннее блока (т.е. 6 а не 3) нет никакого.
Так что мешать, мешать и еще раз мешать. (Кстати замес это операция конкатенации, что само по себе уже усложняет процесс разпаралеливания брута — fpga и всякие asic очень не любят работать с памятью).
Что-то я не понял… Какая атака становится возможной, если сумма длин пароля и соли кратна 1024 бита?
Атака это громко сказано, но представить способ упрощающий в этом случае перебор (это не совсем коллизия) я могу. Не будем окунатся в мир формул, а просто на простом тупом примере:
Злоумышленнику известен хэш (ну и соль, что пока не суть важно). Используя радужные таблицы (при наличии соответствующей мощности конечно) он может гораздо быстрее найти ВСЕ последовательности этой известной длинны.
Далее у нас есть соль. Если мы знаем где она стоит в последовательности — остальное предполагаемый пароль.
А я вам похожих сценариев (при известных коллизиях) еще штук пять нарисую.
Хм, почему-то я не нашел в этих рассуждениях числа 1024…
Потому что остаток от «вычитания» соли из найденых последовательностей — уже предполагаемый пароль, не нужно искать какой он длинны.
Даже то что длинна пароля известна заранее очень и очень плохо.
Это правило нашел давненько у какого-то гуру криптографии (не помню за давностью), исследующего коллизии первого рода md5, а в часности вырождение ее на конкретном примере в какой-то библиотеке. Так вот, там генерация соли была сделана добиванием пароля до 512 байт из случайной последовательности.
В результате формул и выкладок где-то страниц на 5-6, библиотека получила неуд.
опечатка: до 512 БИТ, но не суть важно…
А, я понял. Но ведь в таком случае случайное совпадение суммы длин соли и пароля с числом 1024 проблемой не является? Да и число 1024 тут опять-таки не при чем — плохо «добивать» солью пароль до любой длины.

Тогда это правило лучше сформулировать так: «длина соли не должна зависеть от длины пароля». Или даже так: «соль должна никак не зависеть от пароля».
Скажите, пожалуйста, именно по скорости и затрат ресурсов, сильно ли проигрывает(или выигрывает) хеширование bcrypt по сравнению, например, с SHA512?
там же снизу цифры!
220M против 8,5k, то есть где-то на 4 порядка для GPU
То есть перебор, а меня интересует именно создание хэша
Зависит от, надо тестировать конкретную систему. Если у вас скорости менее 1000 хэшей в секунду — вы ничего не заметите. Иначе — давайте смотреть ТТХ.
Перебор и создание хеша — по факту одно и то же.
Ну просто здесь GPU скорости описаны, а для CPU будут другие соотношения. bcrypt специально спроектирован для противодействия GPU атакам.
НЛО прилетело и опубликовало эту надпись здесь
Для scrypt уже выпустили ASIC, но пока скорость одного девайса на порядок ниже, чем GPU.
Кстати, если всем пользователям менять пароли, то сразу начинаются слухи о взломе, и ведь не докажешь, что это в целях безопасности, имидж часто всё.


Можно не менять пароли. Просто при следующем логине создать для пользователя более стойкий хеш.
Отличный хинт, спасибо!
Я слышал ещё про другое решение: просто изменить хэш‐функцию с old(secret) на new(old(secret)).

Если вы используете вариант с изменением хэша при логине, то вы получаете сразу две проблемы: во‐первых, пользователи, которые к вам более не заходят, лишаются защиты, во‐вторых, вам нужно сильнее изменить код, занимающийся авторизацией. В моём варианте нужно просто один раз изменить базу и изменить функцию хэширования, не добавляя более никакой логики.
НЛО прилетело и опубликовало эту надпись здесь
У bcrypt можно настроить уровень сложности при генерировании соли. Можно как упростить, так и усложнить.

Вот пример из документации на bcrypt модуль для Python:
# gensalt's log_rounds parameter determines the complexity.
# The work factor is 2**log_rounds, and the default is 12
hashed = bcrypt.hashpw(password, bcrypt.gensalt(10))


На E3-1240v2 с параметром gensalt(10) хеш получается примерно за доли секунды, с gensalt(12) — где-то за секунду, а когда, например, gensalt(18), то примерно 15 секунд надо что бы сгенерировать один хеш.
Тема PBKDF2 не раскрыта :)
Серьезно, столько комментариев с рассуждениями как, что, чем солить, сколько раз хешировать и т. д.? Как из паролей сделать надежный ключ? Использовать любую современную KDF, блин. Кстати, PBKDF2 даже каким-то там стандартом является. И не нужно ничего изобретать, возьмите готовое, благо реализация почти под все известные языки есть.
Расскрываю:)
Вообще-то, из-за зацепленных вычислений псевдослучайной функции, скорость подбора ключа очень мала, но это на стандартном CPU, даже на виртексах, с появлением же железа типа ASIC и т.п. разпаралеливается на ура, что ведет к увеличению скорости перебора на несколько порядков. Но все еще не миллионы в секунду — пока нет.
Хуже, то что теоретически алгоритм позволяет использовать словарь для ускорения перебора, что уже безпокоит.
Источник: Computer Security – ESORICS 2012. Evaluation of Standardized Password-Based Key Derivation against Parallel Processing Platforms.

Тот же bcrypt в этом смысле гораздо надежнее.
А какой-же алгоритм лучше использовать? Недавно вычитал, что самый оптимальный будет blowfish, но сомневаюсь.
я же большими буквами написал, что bcrypt
Спасибо. Извиняюсь за невнимательность.
bcrypt использует blowfish
Есть еще scrypt, основанный на упомянутом выше PBKDF2. Где-то читал недавно совсем, что bcrypt не протестирован, как следует, в отличие от PBKDF2.
Использовал md5(md5(логин+дата регистрации+константа)+пароль) — вроде нормально, и без лишних полей в таблице. Единственное, это лучше перейти на SHA512.
Константа вне базы? Тогда это и есть локальный параметр.
Дата регистрации — это соль фактически, пользовательская.
Да. Локальный параметр. Но это было в 2007. Вообще, не должно быть в БД никаких полей типа salt и прочего. Много неизменяемых вещей, как логин, дата регистрации, айпи при регистрации и тп. И их стоит использовать как соль.
С точки зрения оптимизации — вы полностью правы. Но проблемы возникают на доказательстве реально величины энтропии этих случайностей.
Вообще, не должно быть в БД никаких полей типа salt и прочего.

Почему не должно? Зачем навешивать на одну сущность «вещь» несколько ответственностей, не позволяя в случае чего изменить схему данных без правок в алгоритме генерации хэша?
Ну, строго говоря, при использовании современных функций хэширования, отдельное поле действительно не нужно — соль генерируется вместе с хэшем, и функция возвращает (и принимает) их единой строкой.
Вас минусуют неоправданно. В случае с bcrypt так и есть (как минимум в php).
Главная ошибка у вас даже не MD5, а пароль в конце.
Это тоже исправить надо.
Не совсем понял. ну например создал я такой хеш, а что далее? Пользователь вводит свой пароль, а как мне получить с него хеш и как проверить правильный пароль или нет?
Ну логин-то пользователь тоже вводит. А все остальное у вас есть.
что мешает просто дописывать к каждому паролю соль подлинее?
Эта соль будет в базе и захватив хранилище можно будет проводить атаку.
Если соль не будет в базе, а будет одна на всех — то можно будет быстро найти все одинаковые пароли.
Есть промежуточные варианты — например, соль — ид пользователя, но, имхо, не стоит оно того.
Интересно, что ни одна переполненная бочка с гневом, рассказывая в очередной раз о необходимости использования соли, не находит пары слов о такой штуке, как сложность пароля.
При том что пароль вида «123456», захэшированный какой угодно медленной функцией, с какой угодно (известной атакующему) солью, раскалывается за секунды.
Отсутствие же локального параметра в базе — довольно шаткая защита. Классическая security through obscurity — как раз в том варианте, в котором на неё полагаться не стоит.
Специально начал статью с определения условий применимости, чтобы избежать таких странных комментов — но всем пофигу :)
Ну, если вы считаете, что сложность подбора не имеет отношения к защите паролей — то, видимо, так оно и есть.

С такими вводными ваша заметка действительно представляет огромную ценность. «Как поставить бронированную сейфовую дверь на сарай из гипсокартона».
Парольная политика — организационная мера. Очень важная и правильная. Также как и осведомленность сотрудников о том, что пароли не надо выкладывать на pastebin, ssh ключи пушить в github, и записывать секреты на бумажках рядом с мониторами. Но текст не об этом.
Об этом и речь. Сложность проля — никакая не организационная, а самая что ни на есть техническая мера.

Которая, если не соблюдается, то делает все эти пляски с бубном, солью и хэшем бесполезными.

Если пароль выбирается из словаря в тыщу слов, то ни соль, прицепленная с правильного конца, ни супер-пупер алгоритм хэширования не помогут.

Но про определения я понял.
Это заметка про защиту сферического пароля в вакууме при комнатной температуре и 80%-й влажности. Очень, очень познавательно, спасибо.
Все пользователи никогда не будут следовать всем техническим мерам.
Как уже сказано в посте, в толково организованных базах не хранятся пароли, берётся hash(password|salt), где | — операция конкатенации. Поэтому неважно, что пароль слабый. Хэш работает не над ним.
Да, технические меры и рекомендации по паролям нужны. Но они не сделают ваш сарай бронированным.
Всегда приятно получить развёрнутый комментарий — сразу становится ясно, что именно не понимает собеседник.

К сожалению, стойкость пароля — это важно.

Соль, по определению атакующему известна. алгоритм — тоже.
Он берет словарь, и в цикле производит ту же самую «операцию конкатенации», подставляя пароли из словаря. И получает все слабые пароли за считанные секунды.
Перебор по словарю — это первое, что делает атакующий. ДО брутфорса.

Теперь понятно?
Какие-то у вас странные претензии к автору. Автор пишет: «Поговорим о велосипеде. У велосипеда все должно быть прекрасно. И руль, и колеса, и рама, и седло. И все это важно. Но сегодня мы поговорим о руле». На что вы отвечаете: «Автор, какой руль, колеса, вот что важно!»
Глупая ситауция, не находите?
Нет, не нахожу. Пароль — это часть «руля». Стойкость хэша, о которой нам пишет автор, зависит от трёх элементов, а не от двух. Поэтому надо говорить про все три.
Но для тех, кому рассказ про соль явился небесным откровением, возможно, стойкость пароля действително кажется неважным параметром.
стойкость пароля действително кажется неважным параметром.

А это вы где вычитали? Автор вроде выше русским по белому написал, что стойкость пароля — это важно, просто статья про соль.

для тех, кому рассказ про соль явился небесным откровением

Я думаю, что найдется очень много вещей, очевидных для других, для вас будут небесным откровением. Профессионалами не рождаются, а становятся. Кто-то может этого не знать и не знает. Почитайте комменты ниже. Да что говорить, тот же ozon вон пароли в открытом виде на почту шлет и ничего, а вы тут про откровения :)
Двухэтапная аутентификация спасет от слабых паролей?
А про соление — везде, где можно, лучше использовать случайные соли. И хранить все в различных местах.

[irony]Не забывая, конечно, про требование спецслужб оставить им закладку (скажем, в ослабленном алгоритме псевдослучайных чисел).[/irony]
И какие будут предложения? Заставить пользователя ставить такой пароль, чтобы в нем были символы в верхнем регистре, символы в нижнем регистре, цифры, спецсимволы, кровь девственницы, суммарно не менее 18 символов и не более 12?

Из-за такой деятельности на редко используемом и не хранящем важной информации ресурсе зачастую проще заново зарегистрироваться, чем вспомнить/восстановить пароль. Да, можно уведомлять пользователя о ненадежности пароля, запугивать его ужасными хакерами, предлагать сделать пароль сложнее. Но если я хочу поставить пароль 123456 на данном конкретном ресурсе, дайте мне его поставить. Спасение утопающего — дело рук самого утопающего. Если это с работой связано, то должны объяснить. Если личное — предупреждение было.

Хуже этого только «Вы полгода не меняли пароль, мы его вам сбросили, поставьте новый. Ой, знаете, выбранный вами пароль был у вас три года назад, нельзя его ставить.»
Можно разрешать простые пароли.
Только надо понимать, что никакие алгоритмы с «локальными параметрами» такой пароль не защитят.

И в очередной раз закипая яростью благородной по поводу ламеров, не умеющих солить — о таком важном в звене защиты упоминать надо обязательно. Оговаривая, что при простом пароле все вышеперечисленные методы окажутся неэффективными.
Пост о том, как уменьшить вероятность получения пароля пользователя, имея доступ к БД.
Перебор по словарю — это уже совсем другая история, как и социнжиниринг, например. Тот же сканер сетчатки имеет «уязвимость», допустим, в виде слабо подготовленного физически пользователя. Это же не значит, что обучение боевым искусствам отдельных пользователей является технической мерой обеспечения безопасности системы аутентификации.
Я вас умоляю. Посты про то как «уменьшить вероятность» не нужны.
Другая история про перебор по словарю — это очень, очень смешно.
Это вообще не security through obscurity, это называется разделяемый секрет.
Security through obscurity — это если бы атакующий не знал алгоритма хэширования.
… и «разделяемый секрет» АКА «секретную соль».
то есть, мы строим свою систему, исходя из предположения, что данный параметр ему неизвестен. Классическая security through obscurity.
Защита хэшированием всегда должна строиться из предположения, что соль атакующему известна. Страусиная политика здесь не годится.

Мы сводим к невозможной офлайн атаку через доступ только к одному хранилищу.

Не знаю сколько систем вы атаковали, но могу сказать по своей статистике за 5 лет, что это очень хороший уровень защиты. Про его обходы написано в теле статьи.
Неправда ваша. Классическая security through obscurity — это «Мы складываем все буквы пароля по модулю 23145. Да, это не совсем безопасно, но так сложилось исторически. Кроме того, мы никому не скажем свой алгоритм хеширования.»

Здесь же самая правильная схема — открытый алгоритм — и закрытые входные данные. Скажите, если кто-нибудь подпишет некоторый файл своим закрытым ключом — вы ему тоже скажите: «У вас классическая security through obscurity — сначала добейтесь стойкости алгоритма при раскрытом ключе — а потом уже хвастайтесь своим алгоритмом подписи?»
Здесь абсолютно то же самое.
«Мы хэшируем пароль с солью 23145. Да, если соль будет известна злоумышленнику, это облегчит брутфорс. Кроме того, мы никому не скажем свой алгоритм хеширования.»

Судя по всему, людей, понимающих смысл защиты хэшированием, в комментах очень мало.
Вы и правда не видите разницы между «мы хешируем пароль, используя свой велосипед» и «мы хешируем пароль, используя bcrypt»?
Я не вижу разницы между подходами «мы никому не скажем секрет, который по определению хранится в открытом виде», которые используются и в том и в другом случае.

Вы думаете, что security through obscurity — это защита лажовым алгоритмом, про который никто не знает, Это не так. Про качество защиты там нет ничего. Речь не про конкретный алгоритм, а про предположение о том, что некий ключевой элемент системы неизвестен атакующему. Это предположение ложное.

Если уповать на секретность секретной соли, то можно и не хэшировать, а шифровать с тем же ключом. А что — «ведь она секретная, никто её не узнает! а алгоритм у нас стойкий!». Но так никто не делает. Потому что «секретная» соль на самом не секретная. И предполагается известной взломщику.

Гениальность принципа одностороннего хэширования как раз в том и состоит, что она, при соблюдении всех правил, безопасна даже если у атакующего есть все данные — хэш, соль, алгоритм. Увы, хабрасообщество очередной раз упало в моих глазах, взявшись рьяно обсуждать тему, в которой понимает понаслышке.
Если уповать на секретность секретной соли, то можно и не хэшировать, а шифровать с тем же ключом.
Простите, но кто уповает на секретность-то соли? Никто не уповает, соли хранятся вместе с теми же паролями.
А локальный параметр решает совсем другие задачи. Да, вместо локального параметра можно было использовать шифрование. Но локальный параметр — быстрее шифрования, а потому — лучше.
Простите, но у вас каша в голове :)
Начиная с того, что для алгоритма хэшированя скорость не достоинство, а недостаток — скорость однократного создания хэша при авторизации не критична, а вот при переборе чем медленнее алгоритм, тем сложнее брутфорс.
И, разумеется, никто не шифрует пароли совсем не потому что это «медленнее».

Автор уповает. Я там ниже вам написал, что вы изобрели себе какой-то «локальный параметр» и по какой-то неведомой причине полагаете, что он чем-то принципиально отличается от соли. Это даже забавно. Такой метод ведения дискуссии — изобрести ничего не значащее слово и ссылаться на него :)
Да, насчет скорости — соглашусь. А по поводу «неведомой причины» — нет.

Все очень просто. Локальный параметр отличается от соли тем, что его нельзя использовать как соль. Вы считаете это недостаточным отличием?

UPD: в комментарии ниже оппонент сознался, что не понимает разницы между Security through obscurity и Pre shared secret. Потому не вижу смысла продолжать спор.
Он добавляется к паролю при хэшировании — то есть, используется, как соль :)
Где она хранится — вопрос десятый. Хранение вместе с хэшем — это просто частный случай.

Предполагать, что «секретная соль» неизвестна атакающему — это ровно то же самое, что предполагать, будто у атакующего нет доступа к хэшам.
Предполагать, что «секретная соль» неизвестна атакающему — это ровно то же самое, что предполагать, будто у атакующего нет доступа к хэшам.

Не тоже самое. Это как предполагать, что атакующему неизвестен пароль пользователя и пароль рута на апп-сервере, а известен только пароль рута СУБД.
Речь не про конкретный алгоритм, а про предположение о том, что некий ключевой элемент системы неизвестен атакующему.

Если все элементы криптосистемы известны атакующему, то вообще ни о какой защите речи быть не может. Давайте обсуждать ситуацию, когда у атакующего есть пароль пользователя, его индивидуальная соль и соль глобальная. Что тут обсуждать?
В том-то и дело, что может. Именно в этом и состоит принцип хэширования. Вот уж от кого не ожидал такого ляпа. :\

И это. Нет никакой «глобальной», «локальной» или «специальной» соли. Есть просто соль. Она заведомо известна атакующему. Это азы.
Почему тогда пароль заведомо неизвестен атакующему?

В данной ситуации хэш у нас функция трёх параметров: пароль (хранится у пользователя), индивидуальный хэш (хранится в базе рядом с хэшем) и глобальный хэш (хранится на апп-сервере). Подбор только пароля требует взлома и апп-сервера, и СУБД. Если мы взломали только СУБД, то нам для определения пароля нужно подбирать кроме него и глобальную часть соли.
Криптография не строится на допущениях. Рассуждения вида «что требуется взломать, чтобы добраться туда-то» относятся к безопасности приложения в целом. Но если говорить о безопасности конкретно хэшей, то только стойкий пароль (в сочетании с остальными правилами) может действительно её гарантировать.
Рассуждения типа «а вот мы придумаем тайное слово и никому его не скажем» — это детская игра в секретики.

Что есть пароль как не такое тайное слово?
Пароль — это субъект защиты. В контексте данного обсуждения.
В контексте данного обсуждения субъект защиты не пароль вообще, а пароль в базе данных. Глобальная часть соли, не хранящаяся в БД, как раз вводится для того, чтобы усложнить переборы (и прямой, и по словарю). Особенно усложняется как раз подбор коротких и/или словарных паролей — вместо «123456» атакующему нужно будет подобрать «GJLGJD*(^&*T^#@&TKFVDMNVDVHG#I&#T@K@HGVDK@HFVD123456».

В общем, как не крути, вынесение части соли из БД усиливает криптозащиту при атаках на базу.

Думаю, недопонимание возникло из-за рассмотрения разных видов атак.
В контексте данного обсуждения субъект защиты не пароль вообще, а пароль в базе

— В контексте моей модели задача решена не для скачек вообще, а для случая сферического коня в вакууме.
=)
Криптография не строится на допущениях.

Строго говоря, именно на них она и строится. Например, асимметричная криптография строится на предположении, что задачу, которая вообще-то имеет единственное решение, всё-таки решить в приемлемое время не смогут (хотя бы случайно) и предположении, что хотя сейчас эффективных алгоритмов решения этой задачи нет, они не будут найдены в ближайшее время.
Я тогда предлагаю размяться и узнать пароль на конкретном примере.
Хэширование — md5
Первые 5 паролей 123456, кодировка cp1251
С секретными солями будет так:
123456Erk`
123456pEMt
123456Yf1H
123456K#N@
123456tx|$

Локальный параметр неизвестен. Соли просто дописываются сзади пароля, то есть алгоритм такой md5(user_passwd+secret_salt+local_param)
Хэши:
29c82a4a118a69b6764943072995770b
d20d533e7fb9d512e0e3e1f2928542df
8007b02939821a40ab57afe95da11199
ab3020a1e8c9c728810fb3acc0e91fae
a342fbb6d236a9c69aa99d24351fd371
Узнайте какой пароль под этим хэшем — d1d4fa8330f7eeb77241699797b258d8
Да, локальный параметр един для всей базы данных.
К сожалению, вы не поняли обсуждаемого вопроса.
Точно так же я могу задать встречный вопрос — «Есть хэши, но соль неизвестна. Предлагаю размяться».
Или — «есть база, пароли лежат в открытом виде». Узнайте пароли.
При анализе уязвимости надо всегда предполагать худший сценарий. Тот факт, что я не откадаю пароль в вашей задачке, никак не поможет человеку, у которого скомпрометированным окажется «секретный» параметр.

А игры «отгадай число, которое я задмуал» стоит оставить для детского сада.
Для компрометации локального параметра требуется как минимум рутовый доступ с серверу, на котором крутится приложение. И подчеркну, что рутовый доступ на сервер приложения и доступ к базе данных — абсолютно разные и не взаимосвязанные вещи. Никто не говорит, что данная схема полностью неуязвима. Но данная схема более устойчивая к взлому чем прочие. Не верите — предложите лучшую и объясните почему.
Я предлагаю на протяжении всего этого топика.
Для хэша, который не имеет заведомо слабых звеньев, весь этот детский лепет с «локальными параметрами», «повышающими» базопасность становится ненужным.
А «чтобы поломать нашу систему надо найти иголку, которая в яйце, яйцо в утке, утка в зайце, заяц в а… фиге» — это классическая security through obscurity.

Само наличие условий «чтобы… надо» — уже говорит о заведомой скомпрометированности подхода. В действительно защищенной системе нету никаких «если». И «повышать» её безопасность не требуется.
Слабые звенья есть всегда. Абсолютно защищенная система — система которой не существует.

Скажем, с тру-паролями, тоже есть условия «чтобы… надо». Скажем, «чтобы никто посторонний не мог зайти, надо чтобы пароль хранился в секрете».
Речь в топике идет о взломе хэша. Не надо уводить разговор в сторону.
По-моему, очевидно, что точно взломать хэш трех параметров, хранящихся в разных местах (голова пользователя, апп-сервер, БД) сложнее чем двух (голова пользователя, апп-сервер или БД).

При одинаковых требованиях к паролям какая система будет защищена больше — с одной солью или с двумя?
В случае с электронной подписью не требуется никаких «секретных локальных параметров». Есть условная «голова пользователя» и открытый ключ. Всё. Даже «доступ к БД» не требуется, не говоря уже ни о каких «апп-серверах» — всё и так открыто.

Давайте вы сначала поучите всех операторов электронных подписей, как правильно организовывать безопасность — хранить открытый ключ в закрытой БД, а еще один дополнительный открытый ключ в ещё одной дополнительной БД. И если они согласятся, что да — так надёжнее — то мы вернёмся к этому вопросу.
Тут скорее речь о том как хранить закрытый ключ. Скажем, хранится он локально на машине пользователя длиной 256 бит. Какой-нибудь специалист однозначно скажет, что если удлинить ключ до 512 бит, первую половину хранить так же локально, а вторую пускай на флэшке, то безопасность системы снизится?
Ещё раз. Мы десь не говорим об атаке на пароль. Оставьте голову пользователя в покое.
В данном топике обсуждается стойкость по определению доступных хакеру данных.
Речь о защите хэша, а не пароля.
Хороший хэш в костылях не нуждается. Он защищен сам по себе.
Локальный параметр — детский лепет «а вот мы спрячемся и никто нас не найдет». Вся защита строится на допущениях. Утверждение «чтобы получить локальный параметр, нужно иметь локальный доступ» ничем принципиально не отличается от утверждения «чтобы получить дамп базы, надо получить доступ к базе». И то и другое — допущения.

При анализе защиты всегда предполагается худший сценарий. Защита «локальным параметром» такую проверку не проходит. Защита стойким паролем — проходит. Это — принципиальная разница.
Если вы не в состоянии этого понять, то продолжать бесполезно.
Товарищ говорит о модели угроз. Вы почему-то оперируете каким-то термином «допущение».

Есть модель, в которой локальный параметр недоступен атакующему, в это случае атака становится на порядки сложнее.

Эта модель очень применима для веб-приложений на основе 5 лет аудитов.

Если Вы говорите, что модель не применима — аргументируйте.
При анализе защиты от чего, от какого вида атак?

Топик о защите хэша, да, о сложности восстановления захэшированных данных из дампа БД. Атакующему нужно восстановить либо пароль, либо пароль и «локальный параметр». Что сложнее при одинаковой сложности пароля?
Если пароль сильный, то необходимая сложность будет уже досигнута. И костыль не нужен.

Если пароль слабый — тогда костыль в виде «локального параметра» может с некоторой долей вероятности предотвратить утечку слабых паролей. Однако всякий, кто полагается на авось («авось не доломают до локального параметра») должен ясно представлять себе риски такого подхода. И оговаривать их в статье, если уж взялся поучать других.

Какая необходимая? И это не костыль, а усложнение подбора любого пароля при определенных атаках весьма низкой ценой. Слабый пароль — хорошо, он превращается в сильный. Сильный — ещё лучше, он превращается в очень сильный.
И оговаривать их в статье, если уж взялся поучать других.


То есть получив доступ к СУБД, злоумышленник получает и значения хешей и соли.

Вектор атаки явно обозначен — получение доступа к СУБД. Об атаках на само приложение речи нет.
Слабый не превращается в сильный.
Сильному превращаться в «очень сильный» не нужно. Да и не превращается он. Это самообман.

Про сферический вектор атаки я уже комментировал. Осталось злоумышленнику рассказать, что он обязан атаковать только БД, а остальное — ни-ни!

(Вы это — вообще понимаете, какую чушь сейчас несете, с этим своим «вектор атаки обозначен»? Пишем статью про защту от SQL инъекций и обозначаем вектор — атакуем только строки в запросе. польза от такой защиты и статьи — просто небывалая).
Пост не полная инструкция по безопасности, а раскрывает защиту по конкретному виду атак. Хотите узнать как защищаться от других атак — ищите другие статьи.

И не самообман это. А то самообманом можно назвать установку замков на двери в квартиру, но не установку их на окна. Просто защитились от одного из вида угроз — проникновения через дверь путем простого захода. Никто не утверждает, что обеспечена полная безопасность, но один из векторов атак перекрыт, что увеличивает защищенность.
Дело в том, что «обозначение вектора» — это шулерство.

ЕСЛИ БЫ в статье действительно рассматривалась атака только система «хакер — БД», то это была бы просто неполная и бесполезная статья.
Но, увы, стаья не ограничивает себя на самом деле рамками атаки на базу. Поскольку САМА ЖЕ вводит такую сущность, «атака на приложение». И это уже превращается в шулерство.
Автор говорит — «а вот предположим, что у нас есть приложение, которое никто не поломает. и тогда мы в него положим секретный ключ». Это уже чушь и обман читателей.

Это, конечно, замечательный способ писать статьи — выделить в защищаемом объекте условно «ломаемую» часть и условно «защищенную», возложить обязанности по защите на вторую — и вуаля! — проблема решена. Но объяснить хакеру, в отличие от незадачливых читателей, что «здесь я играю, а здесь — в домике» будет затруднительно.

Окна — неверная аналогия. Если есть возможность положить вещь в сейфовую ячейку, бронированную со всех сторон, то окон там не будет. И самообмана не будет.

А вот если делать, как автор статьи — ставить дверь, а про окна сказать «этаж у нас 22-й, никто не залезет» — это-то и будет самообман. Так понятнее?

Учитывая же отношение автора к сейфовой ячейке — сугубо негативное — статья получается и откровенно вредная.
Дело в том, что «обозначение вектора» — это шулерство.

Это не шулерство, это обычная практика защиты. Причем не в данном контексте, а вообще защиты чего угодно от чего угодно. Прежде чем защищаться нужно определиться от чего защищаться, от каких атак. Невозможно защититься от всего, сейфовая ячейка, например, защищена только от некоторых видов атак, а от атаки типа революции с национализацией и автогеном не защищена.
Увы, опять забываем о контексте обсуждения, и отвечаем на комментарий «в лоб», без учета не то что всей дискуссии но даже и предыдущей пары комментариев.
Судя по всему, даже флуд в 40 тысяч сообщений не может научить этому простому навыку.
Вы вводите несуществующий контекст «защиты от всего».
Неправда.
Как раз я объясняю этим горе-специалистам, как правильно обеспечить безопасность пароля в контексте доступа к БД, без применения костылей и введения дополнительных сущностей.

А вот вы как раз всей компанией лёгким движением руки вводите дополнительную сущность «получение доступа к приложению» помимо сущности «получение доступа к СУБД». Причем требуется это вам из-за заведомно несекурного способа хранения пароля.

То есть, это вы расширяете контекст, а не я. Причём делаете это буквально анекдотическими методами: защита у вас работает только для «сферической базы данных в вакууме», а про реальную защиту, стало быть, надо читать «в других статьях».

Говорите про хранение в БД? Отлично, ограничивайтесь БД. А вводить дополнительную сущность, которая никак критически не оценивается, и принимается априори секурной — это шулерство автора и потенциальная дыра в приложении.

Если бы автор понимал важность сложных паролей, он бы об этом упомянул в статье — и вопросов бы к нему не было. Но он, если даже и понял постфактум, то самолюбие не даёт ему в этом признаться — и теперь ему только и остаётся, что цепляться за свою идею раздельного хранения, которая по определению менее секурна. Но оскорбленное самолюбие я еще могу понять. А вот добровольных адвокатов, на голубом глазу рассказывающих про векторы «мы лечим только голову. То, что после лечения голова отделяется от тела — не наша проблема. Не вводите несуществующий контекст лечение всего!» — я понять не могу.
Какая разница хранится в БД хэш от (сложный пароль)+(индвидуальная соль) хэш от ((простой пароль)+(глобальная соль))+(индивидуальная соль). В базе у нас в любом случае есть только хэш и индивидуальная соль и для восстановления пароля нужно или перебрать сложные пароли, или перебрать объединение простого пароля и соли. Для базы эти сущности внешние.
Вот! Наконец-то мы пришли к общему знаменателю.

Дело в том, что задача «перебрать сложные пароли», при соблюдении остальных двух правил, совершенно корректно описанных в этом топике — медленный хэш и уникальная соль — задача не-ре-ша-е-ма-я. Об этом я и все время говорю на протяжении этой чрезмерно растянувшейся дискуссии. Именно в этом и состоит разница. У подхода «сикретный тайничок» есть слабое звено. У подхода «сильный пароль при соблюдении других двух условий» — нет. Потому что работает именно так, как эта система и была задумана.

Теперь понятнее?

Собственно, при соблюдении всех трех правил система становится самодостаточной. Ей вообще становятся не нужны никакие «сущности», «векторы», «ограниченные задачи» и любые другие искусственные контексты для того, чтобы быть неуязвимой. Здорово же?
Вычислительно задачи одного порядка (вернее при требовании к глобальной соли таким же как к сильному паролю, подбор одновременно и слабого пароля, и глобальной соли на порядки сложнее, а требования к соли на практике могут быть куда сильнее). Отличия лишь в том, что в вашей ситуации секрет, не хранящийся непосредственно в БД, известен только одному лицу, а в нашей он распределён — никто «пароля» целиком не знает. Опасность лишь в том, что если всё же злоумышленник подберёт глобальную соль, то остальные слабые пароли подбирать ему будет легче, но это компенсируется возможностью задавать сколь угодно сильную глобальную соль, сильнее любого практически применимого пароля (сохраненные пароли таковыми уже считаться не могут, имхо).
Не компенсируется.
Не надо проводить знак равенства между неравнозначными величинами. И не надо подменять понятия. Речь идёт не о «подборе» «секретной соли», а о таком же взломе.

Если одна отдельно взятая компания по проведению аудита умеет ломать базу, но не умеет ломать приложение — это совсем не значит, что приложение секурнее базы, увы.

Сам принцип хэширования предполагает, что соль хранится открыто. И при соблюдении всех правил, это совершенно безопасно.

Про разделение соли для защиты слабых паролей можно говорить только в качестве компромисса. Подробно информируя читателя:
— во-первых, о правильной практике
— во-вторых, о недостатках предложенного трейд-оффа
Если бы это было сделано, то у меня к статье не было бы ни одной претензии.
Опять возвращаемся к различным видам атак. В статье расписана атака, в результате которой злоумышленник получает доступ исключительно к БД. Что там с приложением — за рамками статьи, как, например, за рамками вместо атаки на приложение произвести атаку на сервис хранения паролей, которым пользуется большинство пользователей, или прийти к каждому пользователю с утюгом.
Об этом и речь.
Чтобы оправдать слабость своего подхода, авторам статьи и их добровольным адвокатам приходится юлить и изворачиваться, изобретая «векторы атак», «рамки подходов» и прочую чепуху, не имеющую ни малейшего отношения к криптографии :)

В самой статье, кстати, тема «атаки на БД» не педалируется столь бешено, как в попытках оправданий в комментариях :)
В веб-приложениях, в числе прочего, хеш-функции используются для безопасного хранения секретов (паролей) в базе данных.

— скорее наоборот, речь идёт именно о приложении.

Кстати, ещё один фейл в статье (спасибо за повод перечитать её почнимательнее):
Ниже перечислены требования, которым ваш хеш в базе должен удовлетворять:
1. стойкость к атакам перебора (прямой перебор и перебор по словарю)
Для выполнения первого требования нужно использовать стойкие хеш-функции.

На самом деле стойкость хеш-функции не защищает от перебора по словарю.
— это ещё один довод за то, что все эти векторы и прочие отмазки придумываются по ходу дела, а не «так было задумано, отвяжитесь» :)
Дождался, пока вы закончите перепалку.

"Все, что может быть украдено, будет украдено. Если вы уверены в защищенности кода, то свой алгоритм хеширования поможет лучше соли. "


Это вообще перл! Его надо запомнить и передавать из поколения в поколение.

Следуя такой логике, будут сразу украдены пользовательские (например) данные, которые мы защищаем паролями. Ну или сами пароли, если так проще воспринимать метод индукции.

Советовать собственный алгоритм хеширования программистам — это диверсия :)

Веб-программисты! Никогда не пишите своей криптухи, никогда!

Поставлю точку. Локальный параметр — это секрет. Такой же секрет, как ваш закрытый SSH ключ, как закрытый ключ от SSL сертификата вашего веб-сервера. И как все секреты его надо защищать.

Вы можете, например, использовать защищенный носитель (смарт-карты, USB токены, и другие). В защищенную область памяти записывается локальный параметр, затем при вызове функции хеширования (cryptoapi), реализованной в самом устройстве, указать, что требуется посчитать ее (хеш) от данных из защищенной памяти (локального параметра), с прибавленными справа входными данными (солью с паролем).

В таком случае, доступ к локальному параметру сводится к физическому доступу к серверу. А получив удаленный доступ на сервер возможно перебирать пароли со скоростью защищенного носителя через тот же cryptoapi. Но в реальной жизни, получив доступ на сервер приложения аутентификации, проще оказывается компрометировать саму систему аутентификации, чем заморачиваться с хешами. О чем тут тоже неоднократно писали в комментариях.

Такие расходы и уровень защищенности для большинства веб-проектов не обоснованы, поэтому хранить локальный параметр предлагается в отдельном месте от хранилища хешей и солей.
Вы сейчас расписались в том, что не понимаете смысла защиты хэшированием.

По-вашему, это обычный подход «все спрятать и никому ничего не показывать», «ограничить доступ» и пр. Но на самом деле смысл хэширования — это «спрятать у всех на виду». Возьмём для примера электронную подпись: её вообще никто не прячет. Она всегда доступна, равно как и подписываемый текст, и используемый алгоритм. «Ломайте, кому не лень». Сам принцип хэширования — в заведомой открытости хэша.

Собственно, хэширование — это «средство последней надежды», возможность защитить пароли, когда приложение скомпрометировано. Поэтому защищать хэш средствами приложения — оксюморон.
Чтобы оправдать слабость своего подхода, авторам статьи и их добровольным адвокатам приходится юлить и изворачиваться, изобретая «векторы атак», «рамки подходов» и прочую чепуху, не имеющую ни малейшего отношения к криптографии :)

Да, это не имеет отношения к криптографии. Но к реальной защите реальных данных на реальных серверах имеет! И с чего начинается анализ защищённости информационной системы?
Хэширование имеет.
При этом хэширование паролей, как раз, к защите «информационной системы» имеет отношение весьма опосредованное.
Поскольку основное его предназначение — как я уже писал выше — спасти хотя бы пароли, если систему поломали.

Своим «локальным параметром» вы вносите в систему заведомо слабое звено. При этом по какой-то (очевидной, разумеется, но не будем на ней заострять) причине насмерть отрицаете вариант с защитой без оного. Довольно забавно это выглядит.
вы вносите в систему заведомо слабое звено.
Да нет же. Необходимость соления хэшей вы же не отрицаете? Что есть соль? Дополнительная информация, наличие которой в последовательности для хэширования пароля затрудняет или делает невозможным брут. От того что, соль известна или нет, сам алгоритм не становится слабее. Эта неизвестная составляющая соли делает брут или криптоанализ практически невозможным.
Только в случае если «слили» секретный ключ вместе с базой, все сводится как всегда к стандартной задаче — и хэш и соль известны — подбираем пароль.
Посему, «вносить в систему заведомо слабое звено» это, извините, чушь.
Вы тоже не понимаете основных принципов и оперируете ложными посылками.
Соль у нас служит не против брута, а против радуги.
И она считается по определению известной. Это не секретный ключ, а наоборот — открытый.
Это жонглирование понятиями (улицу не перебегают, а переходят), не отменяет всего вышесказаного.
Офигеть «жонглирование понятиями» :)
Почитайте сначала, что есть брут, а что радуга. Когда поймёте, что это не одно и то же — продолжим.
Не отменяет всего вышесказаного
что тут непонятного?
1) Радугу нельзя применять для соленых хэшей, только брут или тяжелый криптоанализ, основы которого вам боюсь недоступны.
2) Что тяжелее по вашему брут с известной солью (например 80бит Х = пароль + 220бит соль) или с неизвесной солью (300бит Х, хотя даже 300-ли нам неизвестно, т.к. соли нет).
3) Украсть секретный ключ на порядок сложнее, если он в железе или на другом компе (web-service в локальной сети) то вообще невозможно.
4) Если он все-таки украден, все сводится как всегда к задаче брута или тяжелого криптоанализа с извесной солью.
ВСЕ.
Вот этот комментарий уже лучше, чем предыдущая бессмыслица про
соль… затрудняет или делает невозможным брут
но опять на мотив старой песни «локальный параметр».

Брут с неизвестной солью не нужен.
Точнее, это костыль для слабых паролей. В таком контексте о нём и надо говорить.

Разговоры про «X тяжелее чем Y» при том, что X достаточно для выполнения задачи, напоминают бородатые каламбуры про «расстрелять через повешение с последующим утоплением» :)

Точнее, это костыль для слабых паролей.
Да что вы привязались к слабым паролям. У вас, помоему, проблема с пониманием причинно-следственной связи.
Это, если хотите, есть решение для любых паролей (и то, что слыбые попадают в группу тоже, не есть предпосылка, а есть следствие). Если пользователь-дурак хочет пароль ABC, и кроме всего прочего, самым «второстепеннейшим» образом мы затрудняем узнать злоумышленнику, что пользователь-дурак пароль не äA54mFà53DEg6450õ. Это что, плохо?

То, что ему разрешили слабый пароль:
1) как минимум выходит за рамки этой статьи.
2) имеет множество других причин (например, клиент или заказчик всегда прав, или например он уйдет на другой сайт, если уму предписать, как выбирать его же личный пароль).
3) абсолютно не важно, по следующей причине: есть два пароля, X и Y. Чем X хуже Y? И теперь, не зная ничего про эти величины, находясь в одинаковых условиях для обеих — «угадайте», которая из этих двух неизвестных — слабая. X, а может все-таки Y?
4) То, что X и Y уже находятся в одинаковых условиях, гарантирует соль, и для обеспечения этой гарантии, известность соли имеет второстепенное значение, т.к. вы только предполагаете, что пароль слаб, и начинаете ваш перебор со словаря из, известных вам, слабых паролей или начинаете перебор с коротких паролей.
Как-то так…

А какой, кстати по вашему мнению, слабый пароль, 6 символов или может 8 символов? Или 10?
А если у меня есть десяток virtex-ов?
Это не решение, а костыль.
Ваше «решение» оставляет потенциальную дыру — параметр, хранящийся в открытом виде.
Когда вы, наконец, это признаете, мы сможем двигаться дальше.

Вы придумали себе очень удобную сказку про «отдельный параметр», назначили его неуязвимым, и от этого строите дальше свою защиту. Это не защита, а фуфло.
Я могу принять такой вариант, как трейдофф между юзабилити и безопасностью, но вот не надо преподносить его, как серебряную пулю.

При этом систему, которая в костылях не нуждается, вы ну наотрез отказываетесь даже рассматривать. Ну что за детский сад? :)

А какой, кстати по вашему мнению, слабый пароль
Хороший вопрос. Давайте считать. Насчет виртексов не знаю, возьмём цифры из статьи:
8,5 k/s полученных на карточке AMD Radeon 7990 стоимостью менее $1000 (даже по старому курсу):
соответственно, за месяц у нас получится (8,500 × 3600 × 24 × 30) ~ 22М вариантов.
Это очень приятная цифра, с ней и пароль из 8 букв, с его миллиардами комбинаций уже становится неплохим вариантом.
Если у вас «десяток virtex-ов» — приводите их цифры, будем на них смотреть.
При этом систему, которая в костылях не нуждается

Ограничения снизу на пароль такой же костыль, имхо.
Вы придумали себе очень удобную сказку про «отдельный параметр», назначили его неуязвимым, и от этого строите дальше свою защиту.

Да, для атак на базу данных (взлом ли сервера, получение ли доступа к базе через sql-injection, слив ли бэкпа) он неуязвим.
Вы придумали себе очень удобную сказку про «отдельный параметр», назначили его неуязвимым, и от этого строите дальше свою защиту. Это не защита, а фуфло.
Вы не слышите просто никаких аргументов, итог опонент в дискусии бессилен (перечитайте еще раз ветку, может тогда дайдет). Вам же говорят, что если он не «неуязвим», то ничего не изменилось: имеем те-же условия, как если бы его небыло вовсе, а именно: вся соль известна. Вы же все время твердите, что мы тем самым «ослабляем» систему, а это совершенно не так, даже если этот кусок соли в десять раз длиннее, чем соль из базы для каждого юзера.

Про виртексы я вам написал, чтобы вы поняли, то что вы считаете «сильным» паролем это миф. И даже если вы повысите сложность на порядок завтра он станет «слабым», и если ничего не делать, то мы с вами в 2020-м году в качестве «хорошего» пароля будем килобайты копипастить.

По поводу AMD Radeon 7990 vs. virtex, гуглите сами. Просто виртексы это FPGA, т.е. «чисто» железное решение и хоть и сильно зависит от алгоритма хэширования, но при прочих равных порвет при переборе любую граку, хоть на CUDA, хоть нативно…
Нигде я не писал, что введением своего параметра вы ослабляете систему. Я говорю, что она остаётся изначально дырявой, даже при наличии костыля.

Про сложность пароля не стоило спрашивать, если у вас нет своих цифр.
Пока у нас есть только те, что приведены в статье. Если они вас не устраивают — предъявляйте претензии автору, а не мне.
Коллега, скажите, при какой температуре утюга вы согласитесь по секрету рассказать мне свой пароль? И если такая температура существует (а она существует) — то какой смысл вообще что-то защищать?
С чего вы взяли, что система изначально была дырявой?
Нигде я не писал, что введением своего параметра вы ослабляете систему
Да ну?
Своим «локальным параметром» вы вносите в систему заведомо слабое звено.
Здесь да — виноват. Формулировка подкачала.
Имелось в виду то же, что и выше — «Проектируя систему так, чтобы её безопасность зависела от „локального параметра“, вы вносите в неё заведомо слабое звено».

В рамках парадигмы «пароли находятся в безопасности для случая сферической базы в вакууме» система получается безопасной. Проблема в том, что взломщик не будет связывать себя такими смехотворными рамками — «ломать только базу, и больше ничего» :)

Взломщик конечно не будет. Но мы-то можем его связать, правда? Вот он получил базу, а там фиг. Чтобы что-то достать, ему теперь мало сломать базу, придётся ещё ломать сервер приложений. Работы больше. (А там не факт, что всё просто: базу он через инъекцию, например, получил, а в сервер приложений влезть посложней будет.)

Итак: работы взломщику больше в любом случае. Значит, взлом становится дороже. Для чего любая защита и затевается.
Защита хэшированием устроена по другому принципу.
Хэширование имеет.

Я имею ввиду: «векторы атак» не имеет непосредственного отношения к криптографии. Точнее, отношение может быть такое: одним из векторов атаки может быть атака на криптоалгоритм, например, использование его слабости. Такое, как всем известная брутфорсоподверженность MD5.

Хеширование паролей — это как раз защита от определённого типа атак (с вектором — украсть базу и получить все пароли разом).

Но на вопрос вы всё-таки не ответили.

Как нужно начинать анализировать безопасность системы?
Ошибаетесь. В ЭЦП есть и третья сторона — регистратор, который собственно и генерирует ключи. Причем ему еще необходимо доверие всех участников.
А пример этого мифического хэша без слабых звеньев можно?
Необязательно рутовый. Вполне достаточно зайти под пользователем имеющем права на чтение, скажем веб-сервер или бэкап.
В общем да, достаточно. Я как-то сразу подумал о локальном параметре зашитом в недра исполняемого файла веб-сервера)
Рутовый пароль может не потребоваться. Ведь приложение работает под своим пользователем и имеет доступ к локальному параметру, верно?..
Если этот секрет компрометирован, то это навсегда, без плясок с бубном вокруг базы Вы не сможете изменить его. А скомпрометировать секрет довольно просто — уволился сотрудник, имеющий к нему доступ, например. Я, честно говоря, не вижу в нем смысла при условии надежности прочих компонент.

Кстати, а scrypt не тестили на скорость?
Ну, взломщику потребуется ещё и найти такого уволенного сотрудника. А когда секрет станет ему известен, задача сведётся к «пароль с солью», которая тоже не то, чтобы проста.
Часто взломщик и уволенный сотрудник — это одно лицо. А если попадет в публичный доступ этот секрет — все, можно считать, что его уже и нет. Так зачем усложнять алгоритм?
Если этот секрет компрометирован, то это навсегда, без плясок с бубном вокруг базы Вы не сможете изменить его.

Изменяем в коде и пользователи не смогут войти и начнут восстанавливать пароли. :)
При том что пароль вида «123456», захэшированный какой угодно медленной функцией, с какой угодно (известной атакующему) солью, раскалывается за секунды.

А системы где такие пароли запрещены, многие пользователи избегают. Вечное противостояние безопасности и удобства.
Дело не в том, что где запрещено.
А в том, что пароль — ключевое звено. Не менее важное, чем алгоритм или соль. Это критический элемент, который сводит последние два на нет.
Стойкость хэша зависит от трёх элементов, а не двух.

Но по какой-то причине про соль и алгоритм кричат на каждом углу, а про пароль начинают рассказывать сказки о вечном противостоянии. Нелогичненько получается.
Вот пример из жизни: у вас есть SQL-инъекция в SELECT с правами на чтение таблицы users. Это mysql, FILE_PRIV нету, никаких других полезных прав кроме этой таблицы на чтение у вас (у того пользователя базы, от которого выполняется SQL запрос) нету. Пусть даже вы можете читать файлы через инъекцию, но СУБД расположена на отдельном сервере, а не на сервер приложений (все так делают, правда).

В базе лежат хэши, соли и логины. Но вы ничего не сможете с ними сделать, если алгоритм хеширования надежный, а локальный параметр достаточной длины.

Думаете редкий пример? Отнюдь :)
И да, представьте что у всех этих пользователей пароль 1234.
Вы все-равно не сможете это доказать или опровергнуть :)
Проверить через форму логина :)
Во-первых, этот комментарий иррелевантен моему.
Я писал про стойкость хэша в зависимости от пароля. А комментарий — про «секретную соль».

Во-вторых, про надежды на секретность секретной соли я уже писал. Не буду повторяться. Уповая на неё, вы расписываетесь в непонимании принципа защиты хэшированием. Который предполагает, что соль известна атакающему. Что при правильном применении никак не сказывается на стойкости.
Что означает слово «уповая»? Никто ни на что не уповает. К паролю при хешировании дописываются две строки — соль и локальный параметр. Они решают свои задачи. Соль решает свою — а локальный параметр свою.
Не надо на ходу изобретать терминологию :)
С точки зрения алгоритма хэширования никаких «локальных параметров» не существует. Это та же соль.

Автор статьи уповает на то, что «секретная» соль так и останется неизвеcтной взломщику. Это security through obscurity.
Не я придумал терминологию локального параметра. Написал откуда она. И не думаю, что можно оспаривать эти материалы.
МНогие уповают на то, что пароли пользователи атакующему недоступны. Это тоже security through obscurity?
Разумеется.
Есть пример реально существующей системы криптозащиты, в которой все компоненты (алгоритм и все его параметры) известны атакующему?
Любая система электронной подписи :)
Как алгоритмы, так и «соль» — открытый ключ — заведомо известны и даже по возможности широко распространяются.
Я слыхал что там еще один ключ есть…
А как быть с закрытым ключом? Он разве считается заведомо известным атакующему?
Эм. Что мы имеем в виду под «всеми» компонентами?
Грубо, закрытый ключ — это аналог пароля. Если он известен атакующему, то все остальное становится ненужным. Мы говорим о взломе или о чём?
Если не устраивает ответ, то тогда стоит конкретизировать вопрос.
Грубо, глобальная часть соли — это аналог второго пароля.
Это ерунда, а не аналог второго пароля.
«второй пароль» — это та же соль. Которая просто хранится отдельно. И её утеря, при соблюдении всех правил, никак не скажется на стойкости хэша.
Утеря же закрытого ключа гарантированно компрометирует все подписанные документы. Неужели даже в запале желания во что бы то ни стало уесть оппонента не видно разницы? Ну, жаль. Всё-таки, ололо побеждает разум.

И её утеря, при соблюдении всех правил, никак не скажется на стойкости хэша.

Хранение частей соли отдельно означает, что вероятность компрометации двух частей одновременно менее вероятна чем компрометация одной. Храня (индивидуальную) соль в БД мы решаем задачу невозможности получения паролей всех пользователей в одном цикле подбора. Храня (глобальную) соль на апп-сервере мы делаем недостаточным получения доступа только к БД, чтобы ограничиться подбором исключительно паролей. Храня одну (индивидуальную) часть в БД, а другую (глобальную) на апп-сервере, мы делаем невозможным подбора исключительно паролей всех пользователей в одном цикле при получении доступа исключительно к БД. Общая стойкость системы повышается.
Хранение частей соли отдельно означает, что мы сознательно вносим в систему слабое звено — нестойкий пароль, а дальше занимаемся классической security through obscurity — начинаем выдумывать всякие способы для повышения стойкости нашей заведомо нестойкой системы.

В то время как для случая элекстронной подписи никто не занимается такими детскими играми в секретики, внося в систему некий третий «локальный параметр», помимо собственно субъектов защиты — польователя и его данных.

Для системы хэширования паролей, у который все три составляющие не скомпрометированы —
1. медленный хэш против перебора
2. уникальная соль против радуги
3. сложный пароль против словаря и перебора
— эти детские игры, опять же, становятся не нужны.

Я, разумеется, согласен, что всякие «локальные параметры» — это tradeoff в пользу юзабилити. Но об этом надо прямо и честно заявлять, а не стыдливо прятать голову в песок, прикрываясь кучей ничего не значащих терминов, выдавая заведомо нестойкую систему за эквивалент заведомо стойкой.

И в этом контексте реакция местных завсеггдатаев меня поражает. Ну, то есть, понятно, что она-то как раз неудивительна. Но всё равно, всякий раз сталкиваясь со столь массовым невежеством и неумением отличить заведомо нестойкую систему, которой требуется «повышение» стойкости, от заведомо стойкой, которой такие костыли не нужны — как-то становится не по себе.
Хранение частей соли отдельно означает, что мы сознательно вносим в систему слабое звено — нестойкий пароль

Хранить части соли отдельно или нет никак не зависит от того стойкий пароль или нет. Храня их отдельно мы повышаем стойкость системы, независимо от того стойкий пароль или нет. Да, система с раздельной солью и стойким паролем более защищена чем система с раздельной солью и нестойким паролем. Но вот что система с раздельной солью и нестойким паролем более защищена чем система и единой солью и стойким паролем далеко не факт в общем случае.
Зависит. При стойком пароле локальный параметр не нужен. Его выдумали какие-то доморощенные «эксперты» и носятся с ним, как с писаной торбой. Нормально защищённая система в таких костылях не нуждается.
Что значит «не нужен»? Безопасность системы не увеличивается от того, что в некоторых сценариях угроз взломщику нужно получить доступ к двум серверам, а не к одному? Или, может, она ещё и понижается от этого?
Локальный параметр по сути включен в подпункт
2. уникальная соль против радуги
А нужен он для того, чтобы при компрометации
2. уникальная соль против радуги
у взломщика была дополнительная головная боль.
Для тех, кто решил поучаствовать в дискуссии, не понимая, о чем она: соль заранее считается скомпрометированной. Сам принцип использования соли предполагает это, он на этом основан. Тот же рекомендуемый в статье bccrypt кладёт её в одну строку с возвращаемым хэшем.
Частые вопросы о соли для новичков
— Где хранить соль? Не опасно ли хранить ее в открытом виде? Можно ли поместить соль в код и ее использовать для всех паролей?
— Все, что может быть украдено, будет украдено. Если вы уверены в защищенности кода, то свой алгоритм хеширования поможет лучше соли. Помните: соль не защищает один конкретный хеш от перебора, поэтому нет цели прятать соль — она хранится в открытом виде рядом с хешем. Задача соли — спасти набор украденных хешей, «удлиняя» пароль, а сделать она это может только в том случае, если у каждого хеша будет своя соль. Поэтому мы храним соль рядом с хешем и для каждого хеша генерируем свою уникальную последовательность символов соли.
Считайте «локальный параметр» принудительным усложнением каждого пароля, а не соли.
Хотя FanatPHP на протяжении всей перепалки получает минусы, он — прав.
1. медленный хэш против перебора
2. уникальная соль против радуги
3. сложный пароль против словаря и перебора

Именно эти пункты обеспечивают безопасность. Если он выполнены, то остальное просто не нужно. Более того, можно все испортить.
Считайте «локальный параметр» принудительным усложнением каждого пароля, а не соли.

Именно. Локальный параметр — усиление слабого пароля.
Теперь давайте поговорим о проблемах такого усиления:

1. Не существует ни единой академической работы или RFC или подробного исследования, где бы рекомендовалось использовать такой локальный параметр.
В основном, источники подобных идей — разного рода блоги рядовых разработчиков (ну и 2 предложения на слайде из этого поста).

2. Не существует ни единой реализации известных проверенных алгоритмов, которые бы принимали локальный параметр, как аргумент.
Соль — есть, локального параметра — нет.
Т.е. нужно самому придумывать способ внедрения локального параметра.
Можно удлинять соль, но это не всегда возможно (например, bcrypt имеет фиксированную длину соли).
Можно удлинять пароль. Опять же, с какого краю дописывать?
Насколько вообще это эффективно дописывать везде одну и ту же строку?

3. Идея локального параметра строится на предположении, что злоумышленник украдет только базу через инъекцию, а к остальному у него доступа не будет.
Это очень оптимистично.

Итого, мы имеем довольно серьезные проблемы с реализацией такого решения и неслабую вероятность сделать только хуже. И все это ради очень призрачной выгоды.
Криптография — это сложно. А ложное ощущение безопасности — это главный враг.
Давайте так. С чего начинается анализ безопасности системы? Не «криптографической устойчивости алгоритма», а именно безопасности системы в целом — блога, электронной почты, системы платежей и так далее. (Я в итоге хочу подвести к тому, что ваше высказывание по поводу п.3 — бессмысленно.)
… смысл хранить ее в очень другом месте?
Правда, если хакер увел не только базу, но получил root доступ к серверу, то, разумеется, любой способ бессилен и здесь нужно действовать на более низком уровне — менять все пароли на сервере, искать возможные оставленные им лазейки типа дропперов, спамить пользователей советом изменить их пароль — желательно, менее угадываемым…
То есть, цитируя, безопасность системы оценивается наиболее слабым ее узлом.
Уповая на неё, вы расписываетесь в непонимании принципа защиты хэшированием. Который предполагает, что соль известна атакающему. Что при правильном применении никак не сказывается на стойкости.

Ё-мое…

Задача. Есть две базы на сто миллионов пользователей каждая. В одной пароли хешираются как hash(passwd), в другой hash(password+salt). У вас есть терабайты радужных таблиц. В случае одной из баз вы можете достать винт с терабайтами радужных таблиц, а если нет этих терабайтов, запустить один брутфорс на ВСЕ пароли разом. В случае другой радужные таблицы выкидываются в мусорку, и надо запускать по брутфорсу на каждого пользователя (сто миллионов пользователей = сто миллионов брутфорсов по одному и тому же словарю).

Вопрос. Сказывается ли наличие соли на стойкости паролей вида 12345, и станет ли вообще кулхацкер пытаться достать пароли из правильно соленой базы?
Ответ: Не сказывается. Станет. Чтобы понять — почему, надо понять разницу между брутфорсом и перебором по словарю. Это не так сложно.

Особенно если внезапно! не две базы, а одна. И с солью.

Но сам неожиданный ход мысли мне понравился. Воображаем себе хакера в ресторане, где ему предлагают на выбор две базы — с солью и без. И на том основании, что он выберет ту из них, которая никак не относится к обсуждаемой теме, с блеском докажем несостоятельность доводов оппонента. Люблю когда у автора хорошо с фантазией.
Ответ: Не сказывается.

Вы уверены?
Формулирую вопрос по-другому. Дайте примерную сравнительную оценку сложности нахождения всех пользователей с паролем «12345» (может, хоть так будет понятно).
Чтобы понять — почему, надо понять разницу между брутфорсом и перебором по словарю.

А ничего, что одно из них является подмножеством другого? :)
И на том основании, что он выберет ту из них, которая никак не относится к обсуждаемой теме, с блеском докажем несостоятельность доводов оппонента.

Конечно. Вы ведь доказываете, что соль не увеличивает криптостойкость. Так почему кулхацкер выберет несоленую базу И там, и там вряд ли достанут 16-значный пароль, состоящий из букв, цифр и символов. И там, и там могут достать пароль «12345». Но в чем же разница?
Я не знаю, КАК надо читать, чтобы "известная атакующему соль" прочесть как "отсутствующая".
Но если все ваши претензии сводятся к этому, то вы меня очень обяжете, если избавите от необходимости копаться в ваших фантазиях.
Нет, известная атакующему соль не равна отсутствующей:
1) хорошая соль исключает применение радужных таблиц
2) хорошая соль уменьшает скорость подбора (считать хэш например 16 символов и 272 несколько разные по сложности задачи обычно)
3) индивидуальная соль делает невозможным подбор паролей всех пользователей в одном цикле перебора.
Количество фантазёров, беседующих сами с собой, уже начинает напрягать.
Нет — я всё понимаю. Беда больших сайтов, эффект толпы и всё такое. Но топик как бы тоже не про цену морковки на рынке. Предполагает некоторое умение концентрироваться на проблеме и логически мыслить. А не выхватывать из контекста одно-два слова и изливать в ответ набор банальностей.

Нигде я не писал, что известная атакующему соль равна отсутствующей в общем случае. Если дать себе труд прочесть переписку, которую взялся комментировать, там будет написано:
Сказывается ли наличие соли на стойкости паролей вида 12345,

Ответ, рождённый работой мысли, а не бездумным повторением заученных мантр — нет. В базе John the Ripper пароль 12345 стоит на втором месте. Что означает практически полное отсутствие задержки при взломе. Перебор по всей базе в 3,5К также не будет медленнее перебора про радуге. Из чего можно сделать тот самый вывод, о котором я говорю с самого начала этой занимательной дискуссии — для паролей вида 12345 наличие соли не сказывается на стойкости.
Если зайти на фейсбук и подобрать пароль к какому-нибудь аккаунту это будет взломом? Черт, как это оказывается просто — взломать фейсбук!
При таком подходе хэширование это тоже security through obscurity. Если скомпрометирована вся сеть, то атакующий может снять пароли в открытом тексте в точке авторизации.

Но я скажу страшную вещь: о плохости security through obscurity говорилось тогда, было идеализированное представление о безопасности. Сейчас понимание безопасности другое. Невозможно сделать что-то безопасное. Возможно сделать что-то безопасное до определенной степени. Например, то, что требет 0-day эксплоита и что невозможно взломать за $1000 — уже неплохая степень защиты. В этом плане все, что усложняет взлом, делает его затратней по времени и ресурсам или закрывает один из возможных векторов можно считать полезным.
злоумышленник может предвычислить заранее хеш(соль) и далее считать хеш(соль)+хеш(пароль) уже куда быстрее (практически с той же скоростью, что и просто хеш(пароль))

А можно этот момент более подробно? Я не понял как поможет вычисление хаша соли при поиске хэша соли+пароля
Например, md5 действует последовательно. То есть, когда вы считаете хэш от каких-то данных, это можно представить в виде:
hasher = new MD5Hash()
for piece in data:
hasher.feed(piece)
return hasher.getvalue()

Соответственно, при каждой «кормёжке» объекта хэшера данными, он вычисляет следующее состояние хэша. Если данные всегда начинаются с известной соли, то можно в качестве начального состояния хэшера брать всегда одинаковый хэшер (с заданным состоянием), куда уже скормили соль, и просто перебирать пароли, как будто соли нет вообще.
НЛО прилетело и опубликовало эту надпись здесь
В тексте ссылки на тексты Solar Designer, какому еще эксперту в криптографии вы доверяете?
И еще что-то мне подсказывает, что текст статьи совпадает с мнением Владимира Воронцова)
Очень тонко :)
Я старался. Судя по никам, сюда набежало довольно много людей с конкурса PHD)
Подписан на канал Youtube Computerphile — доходчивые рассказы на различные темы из области IT. Поясняют самые основы, но интересно. Не так давно было видео о хэшировании и хранении паролей.
На английском языке.
Hashing Algorithms and Security


How NOT to Store Passwords!

Чтобы усложнить жизнь при атаке перебора следует дописать соль к паролю, а не наоборот (для людей, которые пишут слева направо, конечно).
Так как хеш-функция, как правило, вычисляется последовательно по строке (требования поточности алгоритма), то злоумышленнику при переборе «соленых» хешей, будет проще, когда подхешовое выражение начинается с соли.
Проще потому, что он (злоумышленник) может предвычислить заранее хеш(соль) и далее считать хеш(соль)+хеш(пароль) уже куда быстрее (практически с той же скоростью, что и просто хеш(пароль)). Для всех паролей, что он будет перебирать.

Не согласен. Хеш пароля тоже можно вычислить заранее, даже проще, ибо соль — случайна, а пароли пользователи используют, в основном, плохие. Т.е. есть возможность использовать словарь.

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

Аргументы против «локального параметра» можно посмотреть в статье "Properly Salting Passwords, The Case Against Pepper".
«Я могу предположить, что злоумышленник, написав модифицированный алгоритм нужной хеш-функции, может заранее посчитать известные начала строк, но при условии, что длина этих строк кратна размеру блока хеш-функции.»

Очень странно. Нет, не может. «Известные начала строк» — откуда они известные?
«Известные начала строк» — откуда они известные?

В статье Вы пишете:

злоумышленнику при переборе «соленых» хешей, будет проще, когда подхешовое выражение начинается с соли.
Проще потому, что он (злоумышленник) может предвычислить заранее хеш(соль) и далее считать хеш(соль)+хеш(пароль) уже куда быстрее

Т.е. злоумышленнику известна соль.

Очень странно. Нет, не может.

Аргументируйте.
Соль хранится рядом с хэшем и она никак не защищена. Пароль же в открытом виде хранится только к голове пользователя. Так что как только будет скомпрометирован хэш, будет скомпрометирована и соль.
Это для случая hash(salt.password), но для случая hash(password.salt) такое уже не работает, о чем и речь идет.
Возможно, я Вас не правильно понял. Давайте по порядку:

1. Я — злоумышленник.
2. Я получил базу.
3. Я использую перебор по словарю.
4. Беру первого пользователя:

$passwords = get_passwords_from_dictionary();
$user_salt = get_user_salt_from_stolen_db();
$user_hash = get_user_hash_from_stolen_db();
foreach ($passwords as $passwd) {
    $hash = hash($user_salt . $passwd);
    if ($hash == $user_hash) {
        echo "Cracked! User password is $passwd";
        break;
    }
}

Что изментися, если я заменю hash($user_salt . $passwd) на hash($passwd . $user_salt)
Автор пишет о том, что hash($user_salt) можно посчитать заранее, а не делать этого в каждой итерации цикла. Скорость перебора при этом возрастет.
Соль случайная. У каждого пользователя своя соль. Как ее посчитать заранее?
Но мы уже знаем соль конкретного пользователя. Считаем хэш соли, а дальше перебираем пароли, подгоняя только хэш пароля.
Вы стырили базу. Вам нужно узнать пароль конкретного пользователя. Соль вам известна из стыреной базы. У вас есть другая база на офигиард паролей, по которой вы хотите сделать перебор, в надежде, что один из паролей подойдет.

В случае $user_salt . $passwd вы один раз вычисляете hash($user_salt) для данного пользователя и запускаете перебор, считая на каждой итерации только hash($passwd) на основе, заренее посчитаного hash($user_salt). А в случае $passwd . $user_salt вам придется считать полный hash($passwd . $user_salt) офигиард раз, что медленнее.
а то что соль нельзя выбирать с длинной кратной блоку это как? и тогда абсолютно все равно где соль. И кстати, есть функции хэширования, которые нельзя считать последовательно…
Скорость перебора.
Вместо
$hash = hash($user_salt. $passwd);
Можно использовать
$hash = hash($hash_const,$passwd);
где $hash_const=hash($user_salt) будет статичной для каждой соли юзера.
Дело в том, что структура Меркла-Дамгарда не позволит обработать данные произвольной длины. Вы сможете обработать только часть, кратную размеру блока. Таким образом, Вы, конечно, пару итераций сэкономите, но большого прироста, мне кажется, не будет.

Можете ли Вы привести какие-либо бенчмарки?

Вот мой бенчмарк. Он на php (не самый подходящий в этой ситуации язык, но все же):

<?php

$salt = 'truly random :)';
$passwords = range(1, 100000);

foreach (hash_algos() as $algo) {
	$t = microtime(true);
	foreach ($passwords as $pass) {
		hash('md5', $salt . $pass);
	}
	$t = microtime(true) - $t;
	echo "$algo (test 1): $t seconds\n";

	$t = microtime(true);
	$hash = hash_init('md5');
	hash_update($hash, $salt);
	foreach ($passwords as $pass) {
		$tmp = hash_copy($hash);
		hash_update($tmp, $pass);
		hash_final($tmp);
	}
	$t = microtime(true) - $t;
	echo "$algo (test 2): $t seconds\n\n";
}


Результат (кликните для просмотра)
md2 (test 1): 0.41930294036865 seconds
md2 (test 2): 0.65669298171997 seconds

md4 (test 1): 0.33836603164673 seconds
md4 (test 2): 0.66673898696899 seconds

md5 (test 1): 0.32980585098267 seconds
md5 (test 2): 0.68694305419922 seconds

sha1 (test 1): 0.33657503128052 seconds
sha1 (test 2): 0.67078709602356 seconds

sha224 (test 1): 0.35949611663818 seconds
sha224 (test 2): 0.69086313247681 seconds

sha256 (test 1): 0.32764387130737 seconds
sha256 (test 2): 0.66027688980103 seconds

sha384 (test 1): 0.33851194381714 seconds
sha384 (test 2): 0.69340515136719 seconds

sha512 (test 1): 0.33541798591614 seconds
sha512 (test 2): 0.6745240688324 seconds

ripemd128 (test 1): 0.33752107620239 seconds
ripemd128 (test 2): 0.66947293281555 seconds

ripemd160 (test 1): 0.32880902290344 seconds
ripemd160 (test 2): 0.67787885665894 seconds

ripemd256 (test 1): 0.35289907455444 seconds
ripemd256 (test 2): 0.67755198478699 seconds

ripemd320 (test 1): 0.34517288208008 seconds
ripemd320 (test 2): 0.67947888374329 seconds

whirlpool (test 1): 0.3348240852356 seconds
whirlpool (test 2): 0.67619395256042 seconds

tiger128,3 (test 1): 0.32882118225098 seconds
tiger128,3 (test 2): 0.69343686103821 seconds

tiger160,3 (test 1): 0.32880210876465 seconds
tiger160,3 (test 2): 0.67336106300354 seconds

tiger192,3 (test 1): 0.33459401130676 seconds
tiger192,3 (test 2): 0.6954870223999 seconds

tiger128,4 (test 1): 0.34620690345764 seconds
tiger128,4 (test 2): 0.69746994972229 seconds

tiger160,4 (test 1): 0.35791516304016 seconds
tiger160,4 (test 2): 0.69432497024536 seconds

tiger192,4 (test 1): 0.35421299934387 seconds
tiger192,4 (test 2): 0.70820212364197 seconds

snefru (test 1): 0.37591290473938 seconds
snefru (test 2): 0.74338006973267 seconds

snefru256 (test 1): 0.38219094276428 seconds
snefru256 (test 2): 0.61988306045532 seconds

gost (test 1): 0.3627450466156 seconds
gost (test 2): 0.69210290908813 seconds

adler32 (test 1): 0.336021900177 seconds
adler32 (test 2): 0.68976402282715 seconds

crc32 (test 1): 0.33328604698181 seconds
crc32 (test 2): 0.70354294776917 seconds

crc32b (test 1): 0.37003493309021 seconds
crc32b (test 2): 0.68103981018066 seconds

fnv132 (test 1): 0.34683513641357 seconds
fnv132 (test 2): 0.68489599227905 seconds

fnv164 (test 1): 0.36317086219788 seconds
fnv164 (test 2): 0.68603587150574 seconds

joaat (test 1): 0.35232901573181 seconds
joaat (test 2): 0.69567322731018 seconds

haval128,3 (test 1): 0.33465504646301 seconds
haval128,3 (test 2): 0.69241309165955 seconds

haval160,3 (test 1): 0.33933401107788 seconds
haval160,3 (test 2): 0.67721199989319 seconds

haval192,3 (test 1): 0.32969999313354 seconds
haval192,3 (test 2): 0.69647789001465 seconds

haval224,3 (test 1): 0.36358118057251 seconds
haval224,3 (test 2): 0.68584513664246 seconds

haval256,3 (test 1): 0.33112812042236 seconds
haval256,3 (test 2): 0.65925908088684 seconds

haval128,4 (test 1): 0.34978795051575 seconds
haval128,4 (test 2): 0.70798516273499 seconds

haval160,4 (test 1): 0.35087609291077 seconds
haval160,4 (test 2): 0.66062998771667 seconds

haval192,4 (test 1): 0.39467096328735 seconds
haval192,4 (test 2): 0.68772506713867 seconds

haval224,4 (test 1): 0.37129783630371 seconds
haval224,4 (test 2): 0.72314190864563 seconds

haval256,4 (test 1): 0.37009191513062 seconds
haval256,4 (test 2): 0.68840098381042 seconds

haval128,5 (test 1): 0.3457510471344 seconds
haval128,5 (test 2): 0.68384885787964 seconds

haval160,5 (test 1): 0.34253597259521 seconds
haval160,5 (test 2): 0.71652007102966 seconds

haval192,5 (test 1): 0.32328200340271 seconds
haval192,5 (test 2): 0.65743803977966 seconds

haval224,5 (test 1): 0.33696818351746 seconds
haval224,5 (test 2): 0.68897008895874 seconds

haval256,5 (test 1): 0.34430909156799 seconds
haval256,5 (test 2): 0.69782590866089 seconds



В случае с php, заранее просчитывать соль вдвойне не выгодно.
oclHashcat HD7990 (one core):

10 = md5($pass.$salt)
Speed.GPU.#8...: 3341.1M/s
20 = md5($salt.$pass)
Speed.GPU.#8...: 4386.9M/s

698458060175b453b3c59834fdf3f8c5:1q2w3e4r5t
Уважайте Ваших собеседников и читателей — напишите скрипт, который можно воспроизвести.
Странно: двукратное увеличение скорости перебора понятно, оно как раз и ожидается. Но откуда берется повышение скорости перебора более чем в два раза? Вылезли за пределы блока:?
oclHashcat очень здесь не репрезентативен для тестов — он закрытый.
И вот, кстати, в тему, как мучался с ним при падении скорости от длины соли: hashcat.net/forum/thread-1437.html
Более чем в два раза никак не должно получаться.
Изменится примерно так:
$passwords = get_passwords_from_dictionary();
$user_salt = get_user_salt_from_stolen_db();
$user_hash = get_user_hash_from_stolen_db();
$preHashedSalt=preHash($user_salt);  // вычисляется 1 раз
foreach ($passwords as $passwd) {
     // считается много раз, каждая итерация быстрее в силу вычисления хэша по меньшей строке
     $hash = hashFromKnownState($preHashedSalt , $passwd);
     if ($hash == $user_hash) {
        echo "Cracked! User password is $passwd";
        break;
    }
}
В качестве компромисса использовать две соли — одну в гриву, другую — под хвост. :)
По мне так гораздо легче не БД ломать и перебирать, а поставить закладку в код авторизации, которая будет отсылать все введённые пароли «куда надо».
Ровно про это же я писал в тексте :) Но это не значит, что не надо защищать этот аспект.
БД может быть куда проще взломать.
Получить доступ на чтение (инъекция в SELECT) — одно, на запись (инъекция в UPDATE/INSERT) — другое. Залить шелл и взять логопасс из конфига, а затем UPDATE себе прав — третье.

Но это не значит, что можно забить на хеширование…
Я думаю, что БД ограничено доступом еще и адресно, и влезть туда помимо сервера приложения, скорее всего, будет невозможно.
Может будет, а может нет. А может взлом апп-сервера даст доступ только на чтение к ФС, а соединиться с него с базой будет возможно.
Если вдруг кому-то было не понятно(как и мне), в чем заключается различие между коллизиями первого и второго родов, то рекомендую глянуть вот сюда.
Лично мне это напомнило разницу между сходимостью функции в точке и равномерной сходимостью функции.
Пока использую sha512(sha512(password)+global_salt) и статья не убедила меня это менять, так как при современных мощностях подобрать что-то кроме стандартных паролей будет нереально (вы же используете случайно сгенерированные или просто пароли со спецсимволами?), а менять систему авторизации, при которой пароль и так не пересылается в процессе аутентификации не вижу рациональным.
Единственное что можно сделать — обернуть хеш ещё и в bcrypt, чтобы можно было существующие установки обновить без проблем, но пока вроде не горит.
Такая база, в случае утечки, позволит сгруппировать людей с одинаковыми паролями — и найти среди них тех, чьи пароли самые популярные. Именно их-то и можно будет сделать основной целью атаки подбором.
Нет, по вашей базе можно сразу отобрать одинаковые пароли, это очень не хорошо.
Одинаковые — значит самые слабые.
То есть если с атакующий и могу регаться в вашей системе — то смогу нарегать 100500 аккаунтов с типичными паролями и сразу узнать другие логины, которые также их используют.

По теме перебора — используются специальные правила, распределающие по вероятности пароли при переборе.
Почитать:
www.openwall.com/john/doc/MODES.shtml
hashcat.net/trac/ticket/60
contest-2010.korelogic.com/rules.html
Согласен, но тут есть нюанс. Я не передаю пароль на сервер. Даже его хеш не передаю, передаю хеш логина, по которому получаю случайно сгенерированный хеш, и отправляю обратно снова хеш логина + ещё один хеш от хеша логина, хеша пароля, user agent, и случайного полученного ранее. Это значит, что в случае уникальной для каждого пользователя соли её нужно будет сделать публичной. Не уверен, на сколько это хорошо. Возможно вы и правы, что скажете?
Нет никаких проблем в том, чтобы сделать соль публичной. Идея соли и заключается в том, чтобы затруднить раскрытие паролей даже в случае наличия у злоумышленника полного дампа базы, со всеми солями.
Возможно я пропустил, а какое количество циклов хеширования Вы рекомендуете?
Один, но bcrypt
Вы шутите? У Bcrypt есть такой параметр как cost. И его ни в коем случае нельзя ставить «1».
Это вообще не про то, что спрашивали, cost параметр. А так есть, да. И константы в алгоритмах тоже менять не следует :)
Большое спасибо за статью.
Крик души, но в отличие от большинства криков — весьма информативный.
Поясните поподробнее, пожалуйста, зачем для хеширования паролей
Стойкость к коллизиям второго рода: должно быть вычислительно неосуществимо подобрать пару сообщений ~(M, M'), имеющих одинаковый хеш
Это из определения криптографических хешей приведено.
Для хеширования паролей это не играет.
Да, похоже, я просто придрался. Всё верно.
Потому что иначе (грубо говоря) можно будет вместо сообщения M, являющегося паролем, подобрать такое M', что hash(M) == hash(M'). Это позволит вам аутентифицироваться (если аутентификация проходит только по хешу) даже не зная пароль.
Нет, вы перепутали с коллизией первого рода.

Коллизия второго рода, применительно к паролям, означает, что пользователь сможет заранее придумать себе пару паролей, а потом троллить админа тем фактом, что оба пароля подходят.
Да, действительно, вы правы :(
Это также не статья про защиту канала передачи данных, так что комментарии по challenge-response и SSL неуместны!


Я бы сказал что это совершенно некорректная формулировка. Здесь следует вставить замечание, что использовать хорошие хэши для хранения пароля можно только тогда, когда канал передачи защищен и есть SSL, иначе вы можете дать вредный совет для небольшого проекта, который живет на шаред-хостинге и не имеет возможности использовать SSL.
Согласен!
И чем этот совет будет вреден? Перекрыть один из векторов атаки вредно?
Тем, что если на проекте работала, например, CRAM-MD5 аутентификация (стандартная для браузера) — она работать перестанет, пароли будут улетать, например, при доступе из публичных вайфайных сетей. Такой вектор атаки гораздо более вероятен.
И кто мешает адаптировать схему для CRAM-MD5 аутентификации?..
Никто не мешает. CRAM-MD5 предполагает, что сервер знает пароль или его эквивалент (в смысле, данные, достаточные для успешной аутентификации) в открытом виде. В лучшем случае — «недохешированный» пароль, что конечно не криптостойко.

Не понимаю, как эта схема вообще всплыла в этом топике. Она совершенно перпендикулярна хешированию пароля, она спасает от другого и обладает своими недостатками.
Есть база реальных пользователей, в базе сохранены хеш паролей по md5. У кого есть опыт плавного и правильного перехода на другой алгоритм шифрования? Например, решено так sha512("{$user->password}{$user->salt}{$global_salt}). Но заставлять всех пользователей менять пароль не хочется, вводить какой то признак у кого какая версия хеш-функции — то же не то.
Можно посолить уже имеющиеся хэши: sha512(md5(password),salt,global_salt), тогда пользователей трогать не придется.
т.е. придется md5 все же оставить. Но это выход.
Вы считаете, что в этом месте md5(password) хуже, чем password?
Я считаю предложенный вариант самым правильным из предложенных в этой ветке. Спасибо за ответ.
Выше уже предложили еще один очень удобный способ — менять хеш при следующем успешном логине пользователя. Как переходный вариант — очень круто.
вводить какой то признак у кого какая версия хеш-функции — то же не то.

Почему? Именно плавный переход: признак в хеше (формат хеша как в юниксе: алгоритм$соль$хеш), а при входе пользователя проверяем, не устаревший ли у него алгоритм, и если да, то заново хешируем пароль уже актуальным алгоритмом (т.к. в момент входа мы пароль знаем).

Такое решение видел в Django; подобная схема возможна в Cyrus IMAPd (с вебом не связано, но какая разница).
Хэш sha512 в 4 раза длиннее, чем md5. Чем не признак?
Проще некуда. Если вам передают пароли в плейнтексте.

Тогда при следующем удачном логине вы производите автоматическое сохранения нового пароля в нужном вам виде. Далее второй уже неактуальный пароль становится просто архивным и в будущем не используется. Для новых пользователей в поле md5 просто null.
2 поля с паролями?
Пока вы продолжаете хранить старый хэш для существенной части эккаунтов, фактически вы не повысили защищенность хранимых паролей. Т.е. как минимум, старый хэш надо удалять немедленно после генерации нового, и это все равно не устранит проблему хэша у юзеров, которые редко заходят.
Кстати, хорошее дополнение. Действительно не продумал, но не мудрено ошибиться когда не до конца погружен.
Вот, где я могу наконец спросить свой вопрос :) Я уже измучился, пытаясь разобраться и расспрашивая всех знакомых.
Я пытаюсь разложить системно, по известной архитектуре, все уязвимости и способы их обнаружения и использования и никак не могу понять все в целом.

Вот в чем моя проблема.

Есть три процесса. Регистрация, авторизация и проверка авторизованности запросов.
Есть элементы системы, доступные обычному пользователю. То, как он ее видит.
Есть элементы системы, доступные злоумышленнику. Соответственно, нужно сделать так, чтобы обычный пользователь не имел возможности стать злоумышленником.

Есть несколько видов архитектуры систем (монолит, толстый клиент-сервер БД, тонкий клиент — сервер логики — сервер БД, веб-клиент — веб-сервер — сервер БД, веб-клиент — веб-сервер — сервер логики — сервер БД).

Теоретически, чем больше соединений, тем больше точек, в которых могут быть уязвимости.

Но, на практике, обычный злоумышленник имеет полный доступ к клиентскому приложению и черный ящик на стороне сервера, к которому известны запросы и ответы неавторизованного и авторизованного пользователя. То, что можно исследовать.

Вопрос защиты от взлома серверной инфраструктуры я все-таки считаю отдельным от вопроса архитектуры конкретного приложения.

Все упирается в три исходные процесса. Какой бы сложный ни был клиент, с обфускацией или без, очевидно, что он полностью доступен злоумышленнику для исследования алгоритма. Можно лишь затруднить исследование, но не предотвратить его.

Возьмем задачу защиты системы от нелегальной авторизации злоумышленника под другим пользователем (и исполнения каких-либо действий в системе под правами другого пользователя) в некой социальной сети (т.е. злоумышленник может регистрироваться и авторизоваться и исследовать все, что происходит на клиенте а так же все запросы к серверу и ответы от него)

Далее, пользователь вводит только логин и пароль. В момент авторизации больше ни на что, неизвестное злоумышленнику, опираться нельзя.

Какая бы ни была композиция преобразований логина и пароля, злоумышленник может ее воспроизвести.
Отбрасываем так же вариант перехвата обращения клиента легального пользователя к серверу в момент авторизации

И вот, объясните мне, какая разница, в этот момент, что отправлять — открытые логин и пароль, хэш логина и пароля любой степени стойкости, хэш логина и пароля и любой комбинации данных, которые злоумышленник легко обнаруживает, исследуя алгоритм работы доступной ему клиентской части?

На мой взгляд, все вопросы хэширования имеют смысл только на стороне сервера, причем даже не для защиты от злоумышленника, взломавшего инфраструктуру сервера (возможно, включая даже алгоритмы защищаемого приложения), а только для того, чтобы сформировать уникальный ключ сессии, который можно использовать для контроля авторизации запросов авторизованного клиента (и его идентификации). То есть, для получения того самого токена.

Все остальное я до сих пор воспринимаю как не вполне уместное шаманство. То есть, вместо защиты инфраструктуры сервер-сайд, строятся бубны из предположения, что злоумышленник преодолел эту защиту, но настолько туп, что не сможет исследовать алгоритмы и данные в базе данных

Я чувствую, что сильно ошибаюсь, но не могу понять — где именно. Кто может мне объяснить, где моя ошибка? Или, возможно, есть что-то, что можно почитать, чтобы системно понять всю структуру уязвимостей и весь комплекс методов защиты от них.

Практически с проблемой я столкнулся лишь когда стал разрабатывать публичный сервис, когда возник вопрос, как передавать данные из клиентского приложения на сервер и обратно. Я так и не смог понять преимущества использования какой бы то ни было соли и хеширования в этот момент.
Верно ли я понимаю, что речь идет о способах даже не защиты сервер-сайд от злоумышленника, получившего полный доступ к серверу и базе данных, а только лишь к максимальному усложнению и замедлению анализа того, что злоумышленник с таким уровнем доступа может исследовать?
но настолько туп, что не сможет исследовать алгоритмы и данные в базе данных

Злоумышленник может взломать только базу данных (скажем, SQL-инъекция), но не знать алгоритма и/или статичного ключа в приложении.
Да, верно, я довольно немного знаю про реальные атаки SQL-инъекций (ну, помимо основного правила зашиты — никогда не подставлять в запросы непосредственно ничего введенного пользователем, только парсить и проверять на соответствие). Поэтому упустил, что такая атака тоже возможна
про «введённое пользователем» — это тоже лишнее, и ведёт к инъекциям.
Так что просто «никогда не подставлять в запросы непосредственно ничего». Точка.
А только через плейсхолдер или белый список.

И плюс ещё про Digest авторизацию (которая challenge-response) — она, увы, несовместима с хэшированием паролей. Поэтому — только SSL. Тем более, что это совсем несложно.

Про SRP6 слышали?
Спасибо, читаю. Интересно.
Вместо такой простыни, вы могли просто спросить зачем нужна соль…
Без соли, уникальной для каждого процесса хэширования (хоть пароль пользователя, хоть кусок данных) возможно найти одинаковые хэши и во первых сосредоточится на них брутфорсом, во вторых зная пароль одного, знаешь и пароль другого.
А вообще мысли здравые, но краткость, все-таки, сестра таланта…
Я бы даже уточнил свой вопрос, не зачем нужна соль, а где именно она нужна :) Просто уникальную соль все равно нужно где-то хранить, по сути там же, где полученный хэш, и тогда уникальность только замедляет исследование, верно? Ну и вопрос, по сути, в компрометации алгоритмов хэширования (т.е. исследовании особенностей получаемых хэшей, зная которые, количество вычислений резко сокращается).
Длинные объяснения — мой бич :( Надо пройти какой-нибудь тренинг, поправить что-то в привычке длинно мыслить.
и тогда уникальность только замедляет исследование, верно?

Так цель любой криптозащиты замедлить исследование.
Всё гораздо проще, без соли можно значительно легче подобрать пароль используя Rainbow table. То есть соль нужна для защиты от радужных таблиц и всё. Остальное (одинаковые пароли) это вторично и бесплатный бонус.
Совершенно, верно, я как-то не подумал, что можно жеж совсем без соли (думал только про уникальную для юзера), а про отсутствие глобальной как-то забыл.
Во-первых, Вы путаете аутентификацию и авторизацию.
Во-вторых, вы путаете хэширование пароля на стороне сервера и challenge-response аутентификацию. При challenge-response аутентификации хэш пароля не передается, передается хэш от пароля известного обоим сторонам и случайного challenge, который передается открыто + некоторой соли.
В-третьих, Вы предполагаете полный доступ злоумышленника к компьютеру пользователя и возможность манипулирования клиентским приложением. При таком условии действительно нет разницы в способе аутентификации, т.к. именно клиентское приложение представляет пользователя. В любой момент времени компьютеры, скажем, 1% пользователей контролируются злоумышленниками. Это нехорошо, но не смертельно для сервиса. Но бывают другие ситуации:
1. злоумышленник получил доступ к базе эккаунтов сервера. Если не предусмотрено защиты, то вместо 1% пользователей страдают 100% пользователей, это может быть смертельно для сервиса. Тут спасает хэширование перолей на стороне сервера, чтобы угнанную базу нельзя было использовать, можно было бы восстановить только небольшой процент слабых паролей.
2. злоумышленник имеет доступ к каналу связи. Например контролирует точку доступа Wi-Fi. В таких случаях спасает шифрование с публичным ключом, если оно возможно или challenge-response аутентификация если возможности шифрования до аутентификации нет, иначе злоумышленник «угонит» пароли всех пользователей, которые через эту точку авторизуются.
Ага, спасибо! Про challenge-response не знал, надо изучить! Насчет разницы между аутентификации и авторизации — действительно, не задумывался, Вы правы.

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

Насчет доступа к базе аккаунтов — да, я собственно про это и написал, что хэширование предполагает затруднение ее анализа

Насчет доступа к каналу связи — действительно, упустил.

По итогу Вашего комментария — есть что изучить и освоить, спасибо Вам! Надеюсь, я не один такой запутанный в этом вопросе и Ваш комментарий будет полезен многим.
challenge-response несовместима с хэшированием паролей.
поэтому, чтобы не выбирать из двух зол — только шифрование канала.
Не совсем так. CRAM-MD5 позволяет хранить хэши, но, разумеется, без соли.
Почему же, вполне реализуемо и реализовано…
Например типичный для challenge сценарий:
1) Server: generates challenge = unique(), passes it and the requested user's salt on to the client.
2) Client: computes response = H(H(Hs(password, salt)), challenge) xor Hs(password, salt), passes it on to the server.
3) Server: checks if (H(H(hash, challenge) xor response) == hash), which means successful authentication.
И hash и salt известны серверу…

При challenge-response гарантируется что бы не передавать пароль в открытом виде при логине. А так все тоже.
В таком случае клиенту достаточно знать hash = Hs(password, salt) для аутентификации, и это хранится на сервере в открытом виде. Сервер проверяет знание этого hash, а не password.
Никто не говорил, что это хэш хранится на сервере в открытом виде, см. мой коммент выше: достаточно его и соль зашифровать динамичным шифром и все.
Да, это ограничение, но имхо вполне себе компромис и уж гораздо лучше чем отправлять пароль открытым текстом или заявленое выше «чтобы не выбирать из двух зол — только шифрование канала».
Потому что забраться на сервер (не просто слить базу паролей через дыру), а именно найти глобальный ключ шифрования — задача посложнее, чем организовать тот же MIM и «слушать» шифрованый канал.

Блин, хочу уже полгода написать статью как раз про такую реализацию, но все как-то недосуг.
И hash и salt известны серверу…

Никто не говорил, что это хэш хранится на сервере в открытом виде



В любом случае, зачем извращаться? Проще и нисколько не менее безопасно
зашифровать динамичным шифром

сам пароль и всё.

Проблем у нас в сущности две:
— Подслушивание канала (решается CRAM или DIGEST)
— Компроментация базы паролей (решается хешированием)
Есть схема аутентификации, внешне похожая на обычную парольную, но решающая сразу обе эти проблемы — причём действительно решающая, а не как в вашем варианте — SRP6 (на Хабре про неё писали). Про другие с такими же свойствами я не слышал.
«известны серверу» и «хэш хранится на сервере в открытом виде» как говорится две большие разницы.
Мой комент был, как бы, ответом на «challenge-response несовместима с хэшированием паролей.»
Это не так и все тут.
Проблем у нас в сущности две
Ну как бы три, если утечет не база паролей, а весь сервер в том числе и с глобальным ключем/вектором (скажем был получен рут), то пароли раскрыть как два пальца. В случае хранения только хэша и соли, можно их расшифровать, но потом еще долго-долго брутфорсить…
Хотя утечька глобального ключа решается, например хардварным способом — dongle, или выносом функционала шифрации/дешифрации в отдельный сервис на отдельно взятой машине, с ограничением доступа по ip. Способов на самом деле море.

А вообще да SRP интересная вещица, а вот эту статью как-то пропустил, спасибо огромнейшее что ткнули носом, увидел для себя интересный момент про схему, которая «позволит аутентифицироваться даже с тупых клиентов» (даже параноика без яваскрипта).
На самом деле все это уже реализовано, именно так выглядит хранение паролей в Windows. И есть именно та самая проблема о которой вам рассказали — угнав базу хэшей, можно войти с любой учетной записью, PoC (модификация к smbclient позволяющая логиниться с используемым в Windows NT-хэшем пароля вместо самого пароля есть в этой статье, там же описаны способы доступа к хэшам, база которых хранится в шифрованном виде. Статья писалась 10 лет назад, некоторые аспекты устарели, но это место до сих пор более-менее актуально, насколько я знаю.
Это хождение по кругу.
Просто вместо password становится Hs(password, salt).
Если для входа на сервер нам надо знать не пароль, а хэш — то хэш теперь становится тем «паролем», зная который, можно войти на сервер. И в итоге этот «пароль» хранится на сервере в открытом виде.
промахнулся, см. #comment_7259424
Для оценки эффективности (сложности) системы хеширования рекомендуется замерить скорость вычисления хэша на сервере. Это должно занимать не менее 0,1 сек и до 0,5 сек. Именно исходя из этого я подбирал параметр «сложность» для bcrypt.
А можно дурацкий вопрос?
При переборе человек будет предполагать по виду ключа, какой алгоритм использован, так?
т.е. bcrypt / md5 — это стандартные алгоритмы для генерации хэша, и т.е. на них первых и падет подозрение (ну и на другие стандартные).

1) если у взломщика есть только БД, но нет кода, лучше использовать любой нестандартный алгоритм, ну не знаю, md5( Rijndael( пароль + md5( времени регистрации на ресурсе ) + статическая соль, статический вектор, зашитый в код ) ) — мне кажется так просто «в лоб» подобрать такие комбинации весьма проблематично.

2) если у взломщика есть доступ до файлов / движка, защититься в принципе невозможно, и будет скомпромитированы все пользователи с простыми, словарными паролями, вычислить же чистым пароль, которого нет в словарях при таком алгоритме практически невозможно.

3) если операций авторизации у вас на ресурсе не огромное количество, то в качестве соли, для некомплементарных алгоритмов можно выбрать соль килобайт на 200, так что на быстродействии авторизации это практически не скажется, а вот подбор затруднит существенно.

4) ну и естественно попытки брута надо определять и дополнять счастье капчей + ограничением попыток в единицу времени.

Нет?
По внешнему виду ключа не зная алгоритма почти невозможно предсказать как оно было захэшировано (только если по размеру иногда можно некоторые исключить), но если стандартный фреймворк, и вдруг…
1) да
2) словарей для соленых хэшей нет, но пароль можно подобрать брутом например на граках или асиках;
3) да, но нет большого смысла
4) не имеет смысла если угнали базу (ну и/или сорцы / глобальную соль).
1 — достаточно просто, с учетом того, что взломщик знает пароль от своего эккаунта и время регистрации. Можно составить всех возможных преобразований и функций типа time(), их не так много, и устроить перебор по возможным сочетаниям.
2 — зависит от того, какой доступ у взломщика. Если он разово «слил» код и базу, но не сумел закрепиться в системе — то узнать алгоритм он может, а узнать пароли — нет.
3 — а что это даст? соль хранится вместе паролем. Усложнить время вычисления хэша проще добавив еще несколько прогонов, зачем лишние данные хранить?
4 — удаленный брут не имеет никакого отношения к хэшированию паролей. Хэширование спасает от ситуаций, когда слита база эккаунтов и брут можно выполнять локально. Соответственно никакие ограничения здесь не помогут.
по п.1 — теоритически «случайную соль» можно высчитать, как узнать что она является вектором например в AESе или еще где-то? или еще чем-то, и не она сама, а md5( нее + что-то )?
Т.е. как узнать что используется за алгоритм вычисления хэша по хэшу?
Конечно, очень сложный протокол, состоящий из большой последовательности разных вызовов разных алгоритмов и скрытых констант в таком случае будет фактически выполнять роль локального параметра и перебор всех возможных вариантов становится затруднителен. В простом случае (например Вашего примера) он достаточно легко обнаруживается. Точно так же легко восстановить слишком короткий локальный параметр. Но дополнительно к достаточно длинному локальному параметру такое усложнение алгоритма ничего не дает.
Я не представляю, как путем реверс-инжениринга можно это высчитать, ну вот честно. Если что-то простое, то да. Но у нас алгоритмов криптографии даже не 5 или 10, а гораздо больше, причем у каждого из них есть еще внутри куча вариаций и подтипов, плюс надо перебирать поля из базы, чтобы понять комбинация из каких полей составляет случайную соль.

Реально ли это можно подобрать? Ибо, если честно, не особо верится.
Все возможные преобразования, константы и поля базы перенумеровываются и дальше последовательно генерируется постфиксное выражение увеличивающейся глубины по количеству констант и количеству операций, пока результат вычисления сгенерированного выражения для известного пароля не совпадет с известным хэшем. Т.е. фактически обычный брютфорс, в котором роль алфавита играют преобразования и переменные. Естественно, как и в случае любого брютфорса, чем сложнее алгоритм, тем сложнее его подобрать.
При переборе человек будет предполагать по виду ключа, какой алгоритм использован, так?
Нет, неверно. Данная статья рассматривает защиту от взлома, когда злоумышленник знает все алгоритмы. Ситуация, когда злоумышленник не знает алгоритмов, называется security through obscurity, и среди специалистов этот термин является ругательством.

Соответственно, по пунктам:
1. Как я уже сказал, такая ситуация попросту не рассматривается. Любой криптографический алгоритм должен быть открытым. Точка.
2. Да, пользователи с простыми и словарными паролями при условии полного доступа злоумышленника к серверу «улетают» в любом случае. Но в таком случае, как правило, подбирать пароли уже и не нужно — злоумышленник уже получил все, что хотел.
А случае же частичного взлома сервера, некоторые «трюки», вроде описанного в статье локального параметра, помогут спасти всех пользователей.
На 3 и 4 — вам уже ответили.
Т.е. получается что мы бьемся за интервал времени разгадывания паролей — так или иначе, рано или поздно, злоумышленник получит их, но мы дадим больше времени, чтобы пользователь мог успеть сменить свой пароль?
По сути тогда надо использовать максимально медленный алгоритм, и при этом чтобы медленность его росла с увеличением числа знаков.

Какой из существующих алгоритмов для этого лучше?
Совершенно верно. Лучший алгоритм для этих целей сейчас — bcrypt. Хотя бы потому что он позволяет задавать «сложность» вычисления хеша как параметр — что позволяет подбирать этот параметр в зависимости от возможностей сервера.

Но смысл соли и локального параметра — не в «лобовом» замедлении времени. Смысл соли — не дать злоумышленнику подбирать пароль ко всем пользователям базы одновременно, вынуждая использовать индивидуальный подход. Фактически, соль увеличивает время полного подбора паролей ко всем пользователям во столько раз, сколько пользователей в базе.

Смысл же локального параметра — вообще не дать возможность начать подбор паролей в случае частичного взлома.
Смысл же локального параметра — вообще не дать возможность начать подбор паролей в случае частичного взлома.

Не не дать, а усложнить. Ничто не запрещает ему подбирать пароль и соль одновременно, причем возможны коллизии.
По поводу security through obscurity или пункта 1:
Мне очень нравятся слова Бри Хатча (точнее summary) из статьи Security with Obscurity is Great / Hacking Linux Exposed:
Схема «security through obscurity» — не схема для построения вашего плана безопасности, но она имеет местно быть в качестве средства повышения общей безопасности.

Короче, лучше все же с obscurity, чем без.
Security with obscurity очень плохо при полном непонимании, что действия по скрытию алгоритма могут ослабить ту самую безопасность, например увеличив вероятность коллизии и т.д. Но следуя всем канонам безопасности и при этом скрыв что-то от посторонних глаз мы ее никак не уменьшим.

Самое простое «доказательство» или скажем «постулат» security with obscurity: стойкость алгоритма как такового не зависит от того скрыт он или нет. Например тот же blowfish или соление ключей, не станет менее стойким если о нем не дать знать злоумышленнику.

Если вместо поля salt в базе или дополнительно к нему вы используете что-то другое, то имхо это как раз security with obscurity: Соль есть (security), но злоумышленник не знает что есть соль (obscurity).
Буду реалистом — естественно, никто не станет переписывать свои проекты ради «каких-то» хешей. Но новые проекты можно писать на scrypt/bcrypt.
Мы поменяли. Перешли на bcrypt как раз. Пароль перехешировался, когда приходило время пользователям менять свой пароль (раз в полгода).
Можно перейти более «мягко». Добавив поле «password_strategy» в котором будет old для всех старых хешей. При успешной авторизации менять тип стратегии на «новый» (bcrypt) и сохранять новый хеш. Через месяц другой вся активная аудитория будет иметь нормальные хеши. Ну а для тех же кто не заходил больше месяца сбросить пароли, если захотят в будущем войти на сайт пусть пользуются восстановлением пароля.
В некоторых популярных решениях есть поле alhoritm со значениями типа md5, sha1 и т. п.
у нас не сайт, у нас коммерческий облачный продукт. там нельзя так с пользователями.
В статье утверждается, что использовать MD5 — уже нельзя. Можно подробней об этом?
Смысл претезний к нему, что он слишком быстрый, что облегчает brute force-атаку (на неё тратится очень мало ресурсов).
Смысл в том, что существует возможность найти коллизию, то есть пароль, при примешивании к которому соли после хеширования получается тот же хеш.
Нет такой возможности. Не уязвим MD5 к коллизиям первого рода. Второго рода — да, есть работа, где с нуля сгенерировали два сообщения с одинаковым хешем. Это эквивалентно придумыванию двух новых паролей (вместе с их солью), у которых одинаковые хеши… А по известному хешу коллизию пока ещё никто не научился находить. Тем более такую, в которой часть (хвост) конец заранее известна, так как хранится рядом с хешем в открытом виде (соль).
" В сравнении с PBKDF2, алгоритм расширения ключа в bcrypt практически не исследовался криптографами[4]"

— и вы предлагаете это использовать??
На самом деле, а почему не PBKDF2?
Вроде бы как сейчас самый верный вариант, из того что смог посмотреть.

В PHP есть имплементация простая:
код
/*
 * Password hashing with PBKDF2.
 * Author: havoc AT defuse.ca
 * www: https://defuse.ca/php-pbkdf2.htm
 */

// These constants may be changed without breaking existing hashes.
define("PBKDF2_HASH_ALGORITHM", "sha256");
define("PBKDF2_ITERATIONS", 1000);
define("PBKDF2_SALT_BYTES", 24);
define("PBKDF2_HASH_BYTES", 24);

define("HASH_SECTIONS", 4);
define("HASH_ALGORITHM_INDEX", 0);
define("HASH_ITERATION_INDEX", 1);
define("HASH_SALT_INDEX", 2);
define("HASH_PBKDF2_INDEX", 3);

function create_hash($password)
{
    // format: algorithm:iterations:salt:hash
    $salt = base64_encode(mcrypt_create_iv(PBKDF2_SALT_BYTES, MCRYPT_DEV_URANDOM));
    return PBKDF2_HASH_ALGORITHM . ":" . PBKDF2_ITERATIONS . ":" .  $salt . ":" . 
        base64_encode(pbkdf2(
            PBKDF2_HASH_ALGORITHM,
            $password,
            $salt,
            PBKDF2_ITERATIONS,
            PBKDF2_HASH_BYTES,
            true
        ));
}

function validate_password($password, $good_hash)
{
    $params = explode(":", $good_hash);
    if(count($params) < HASH_SECTIONS)
       return false; 
    $pbkdf2 = base64_decode($params[HASH_PBKDF2_INDEX]);
    return slow_equals(
        $pbkdf2,
        pbkdf2(
            $params[HASH_ALGORITHM_INDEX],
            $password,
            $params[HASH_SALT_INDEX],
            (int)$params[HASH_ITERATION_INDEX],
            strlen($pbkdf2),
            true
        )
    );
}

// Compares two strings $a and $b in length-constant time.
function slow_equals($a, $b)
{
    $diff = strlen($a) ^ strlen($b);
    for($i = 0; $i < strlen($a) && $i < strlen($b); $i++)
    {
        $diff |= ord($a[$i]) ^ ord($b[$i]);
    }
    return $diff === 0; 
}

/*
 * PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt
 * $algorithm - The hash algorithm to use. Recommended: SHA256
 * $password - The password.
 * $salt - A salt that is unique to the password.
 * $count - Iteration count. Higher is better, but slower. Recommended: At least 1000.
 * $key_length - The length of the derived key in bytes.
 * $raw_output - If true, the key is returned in raw binary format. Hex encoded otherwise.
 * Returns: A $key_length-byte key derived from the password and salt.
 *
 * Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt
 *
 * This implementation of PBKDF2 was originally created by https://defuse.ca
 * With improvements by http://www.variations-of-shadow.com
 */
function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false)
{
    $algorithm = strtolower($algorithm);
    if(!in_array($algorithm, hash_algos(), true))
        die('PBKDF2 ERROR: Invalid hash algorithm.');
    if($count <= 0 || $key_length <= 0)
        die('PBKDF2 ERROR: Invalid parameters.');

    $hash_length = strlen(hash($algorithm, "", true));
    $block_count = ceil($key_length / $hash_length);

    $output = "";
    for($i = 1; $i <= $block_count; $i++) {
        // $i encoded as 4 bytes, big endian.
        $last = $salt . pack("N", $i);
        // first iteration
        $last = $xorsum = hash_hmac($algorithm, $last, $password, true);
        // perform the other $count - 1 iterations
        for ($j = 1; $j < $count; $j++) {
            $xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
        }
        $output .= $xorsum;
    }

    if($raw_output)
        return substr($output, 0, $key_length);
    else
        return bin2hex(substr($output, 0, $key_length));
}

Вот меня наверное заминусуют, но я одно время делал так:
<?php strrev(md5($password . $salt)); ?>

:D
зачем там md5, помоему и с strrev вполне секюрно.
Крутые советы. Для еще большей безопасности параноики могут хранить соль в localStorage браузера и присылать каждый раз вместе с запросом. Сменил браузер — делай Reset password.
Это уже совершенно другая механика (привязка к браузеру), и для нее есть лучшие решения.
например?
user-agent, например
Ну и? Может обоснуете минусы подхода, когда пользовательские куки проверяются по совпадению юзер-агента? А?
Юзер-агент а) легко подделывается (даже не браузерами, а каким-нибудь курлом — вот только вчера хромом представлялся) б) часто совпадает у разных пользователей
Т.е. не самый лучший вариант? А что можете посоветовать тогда в ситуациях, когда нужно определить, что печенье принадлежит именно этому пользователю?

PS. Спасибо за ответ, вместо минуса
Не понял постановки задачи.
Ну, например, авторизационные куки. При правильном применении их можно защитить от угона через javascript — что нельзя сделать в случае с localStorage.
Да, тоже вариант, но это уже мелочи имплементации. Можно хоть в etag кеш запрятать.
Правда все они угоняются если заменить серверный код

Публикации

Изменить настройки темы

Истории