Надлежащее хэширование паролей

Надлежащее хэширование паролей

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

Авторы: bwall (@bwallHatesTwits), drone (@dronesec)
 

1 Введение

 

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

 

1.1 Текущее положение

 

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

 

1.2 Моральное обязательство

 

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

 

2 Надлежащие методы хэширования

 

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

 

2.1 Соль хороша для вас

 

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

 

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

 

2.2 Функции получения ключей

 

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

 
2.2.1 PBKDF2
 

PBKDF2 (Password-Based Key Derivation Function 2 или Функция Получения Ключа На Основе Пароля 2) – это функция получения ключа, разработанная RSA Laboratories, используемая для получения стойких ключей на основе хэша. Она работает путем применения псевдослучайной хэш-функции (вроде SHA-256, SHA-512, и т. д.) к строке, в нашем случае – к паролю, вместе с солью и повторением этого процесса большое число раз. Данный процесс может быть обобщен следующей диаграммой:

 

Мы уже обсудили вопрос соли, однако, стоит отметить, что минимальная длина рекомендуемой соли для PBKDF – 128 бит. Спецификация1 PBKDF утверждает, что SHA-12 является утвержденной PRF (псевдослучайной функцией) для алгоритма. Однако, в 2005 году стало ясно, что SHA-1 – относительно слабая функция3 и не должна использоваться как HMAC. В ваших интересах использовать достаточно сильный HMAC, чтобы выдержать полный перебор, специальные атаки и потенциальные будущие проблемы дизайна (вроде взлома алгоритма или вычислительной достижимости). Важно отметить разницу между хэшем и HMAC – хэш не связан с аутентификацией сообщения. Он связан только с целостностью данных. HMAC обычно используется для подписи сообщений при хэшировании их с солью, уникальной для ее владельца. HMAC также можно использовать для генерации очень сильных хэшей. Проблема в том, что при известной соли HMAC столь же легко взломать полным перебором. Здесь на выручку приходит PBKDF2, реализующая итеративный HMAC, который увеличивает безопасность и время взлома каждого отдельного хэша. Давайте взглянем на псевдокод этого процесса:

 1 bytes PBKDF2(HashAlgo, Plaintext, Salt, Iterations) { bytes IterativeHash = hash_hmac(HashAlgo, Salt, Plaintext);
bytes TempHash = IterativeHash; for(int iter = 0; iter < Iterations; iter++)
{ TempHash = hash_hmac(HashAlgo, TempHash, Plaintext); IterativeHash ^= TempHash } return IterativeHash; }
 

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

 function PBKDF2($plain, $salt, $iterations, $algo = 'sha256' )
{ $derivedkey = $b = hash_hmac($algo, $salt . pack('N', $block), $plain, true); for ( $i = 0; $i < $iterations; $i++ )
{ $derivedkey ^= ($b = hash_hmac($algo, $b, $plain, true)); } return $derivedkey; }
 

Хоть вышеприведенные примеры и обобщены, они дают базовый взгляд на то, как может быть реализована функция PBKDF2. Здесь всего лишь хэшируется соль и открытый текст для получения первого хэша, затем в цикле тот же алгоритм используется для вычисления хэша от открытого текста и результата предыдущей итерации, после чего возвращается результат применения операции XOR ко всем вычисленным хэшам. Выполнив данную операцию 1000 или больше раз, вы сгенерируете сильный, стойкий ко взлому хэш, который вы можете безопасно использовать для хранения паролей. За подробностями можете обратиться к NIST-SP800-132.

 
2.2.2 ARC4PBKDF2
 

Я (bwall) некоторое время обыгрывал в уме идею динамической энтропии. PBKDF2 кажется хорошей площадкой для ее внедрения. Данная идея заключается в том, чтобы сделать энтропию алгоритма шифрования изменяющейся в ходе шифрования. Она происходит из ограничений некоторых систем быстрого взлома хэшей, которые имеют тенденцию жестко оптимизировать определенные процессы вроде применения энтропии. Чтобы бороться с этим, мы можем добавить относительно быстрый процесс создания дополнительной энтропии, который потребует дополнительных вычислений в ходе взлома хэша и возможного удаления некоторых оптимизаций. В ARC4PBKDF2 поток шифра ARC4 инициализируется ключом, затем данный шифр используется для шифрования открытого текста перед его использованием в HMAC в каждой итерации, продолжая тот же поток и также шифруя результат HMAC. Этот поток ARC4 должен быть единым для всего процесса, чтобы добавить сложности и в без того трудный для взлома метод хэширования. Далее представлен пример реализации на C#.

 public byte[] Hash(byte[] input) { ARC4 rc4 = new ARC4(ARC4Key);
byte[] derived = new HMACSHA256(Salt).ComputeHash(rc4.CryptBytes(input));
byte[] temp = derived; for (int x = 0; x < Iterations; x++) { temp = rc4.CryptBytes(new
HMACSHA256(temp).ComputeHash(rc4.CryptBytes(input)));
for (int y = 0; y < derived.Length; y++) { derived[y] ^= temp[y]; } } return derived; }
 

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

 
2.2.3 bcrypt
 

Bcrypt – адаптивная хэш-функция, появившаяся в 1999 году. Она немного походит на PBKDF2, но действует более сложным образом. Первую работу, связанную с данной функцией, опубликованную Наелсом Провосом и Дэвидом Мазьересом, можно найти здесь. Она очень подробно объясняет тонкости алгоритма и его реализацию. Для наших целей будет достаточно краткого обзора с примером.

 

По сути, bcrypt – это блочный шифр Blowfish4, используемый в режиме ECB, с более сложным алгоримом подготовки ключей (особенно, что касается S-блоков). Это позволяет алгоритму быть стойким к возможным будущим атакам и существенно более адаптивным. Реализация алгоритма использует 128-битную соль и усовершенствованный алгоритм, известный, как eksblowfish или expensive key schedule blowfish. Заголовок функции bcrypt выглядит так:

  bcrypt(cost, salt, pwd)  

Здесь cost – контроллер подготовки ключей (задает ресурсоемкость фазы подготовки ключей), salt – 128-битное значение, а pwd – текстовый (до 56 байтов) ключ, используемый для шифрования по алгоритму Blowfish.

 

Существует пара действительно удивительных, полезных вещей, касающихся bcrypt и заслуживающих обсуждения. Одна из них – то, что алгоритму НЕОБХОДИМА соль. Хотя соль сама по себе не сохранит ваш украденный SQL-дамп, она увеличит его стойкость. Вторая – то, что алгоритм является (я использовал этот термин уже несколько раз) адаптивным. Время подготовки ключей может быть всего порядка одной миллисекунды или таким большим, как вы захотите. Замечательный факт состоит в том, что для сервера почти безразлично, что проверка пароля занимает 3 секунды вместо 0.3, но для атакующего такое увеличение сложности – абсолютный хаос. Это также позволяет напрямую бороться с экспоненциальным наращиванием вычислительных мощностей (известным, как закон Мура), поскольку со временем вы можете постепенно изменять значение переменной cost, чтобы увеличить сложность паролей.

 

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

 public string bcrypt(int cost, byte[] salt, byte[] password)
{ byte[] state = EksBlowFishSetup(cost, salt, password);
string ciphertext = "OrpheanBeholderScryDoubt"; for (int i = 0; i < 64; ++i)
{ ciphertext = EncryptECB(state, ciphertext); }
return cost.ToString() + BitConverter.ToString(salt) + ciphertext; }
 

Вышеприведенный код довольно прост. Сначала значение переменной state устанавливается алгоритмом EksBlowFish – данная стадия является наиболее емкой по времени. Далее идет цикл получения шифртекста, изначально имеющего значение 192-битной магической строки (почти всегда равной "OrpheanBeholderScryDoubt"), путем многократного шифрования шифртекста из предыдущей итерации в режиме ECB вместе со значением переменной state. Итоговый результат – статическая 192-битная конкатенация стоимости, соли и результирующего шифртекста.

 

3 Заключение

 

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

 

Ссылки и материалы для дальнейшего чтения

 

[0] Bruce Schneier’s description of the BlowFish cipher https://www.schneier.com/paper-blowfish-fse.html   
 [1] Niels Provos’ and David Mazieres original paper on Bcrypt http://static.usenix.org/events/usenix99/provos.html   
 [2] NIST SP800-132: Recommendation for Password-Based Key Derivation http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf   
 [3] FIPS 180-1 on Secure Hashing http://www.itl.nist.gov/fipspubs/fip180-1.htm

 
1 http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf 2 http://www.itl.nist.gov/fipspubs/fip180-1.htm 3 https://www.schneier.com/blog/archives/2005/02/cryptanalysis_o.html 4 https://www.schneier.com/paper-blowfish-fse.html   

Где кванты и ИИ становятся искусством?

На перекрестке науки и фантазии — наш канал

Подписаться