Rfc2898derivebytes что это за хеширование

Обновлено: 04.07.2024

Я делаю это не для того, чтобы навлечь позор на людей, которые написали что-то не так. Я просто хочу приложить руку к решению проблемы. За время своей работы в AppSec я очень устал от бесконечных однообразных споров. Я делаю всё, что в моих силах, чтобы люди могли без особых сложностей реализовать всё правильно, в частности, указываю на код, который можно использовать смело – например, из репозитория Secure Compatible Encryption Examples от Люка Парка. И, тем не менее, попадаются команды, которые продолжают упорствовать, даже если код еще не ушел в продакшн – а ведь это самое лучшее время для исправления ошибок. Это усложняет всем жизнь: я теряю время на объяснения, что с кодом не всё в порядке, а команде потом приходится делать значительно больше работы, потому что после того как код уходит в продакшн, для исправления криптографии приходится сначала составлять план миграции.

Я убежден: есть основания надеяться, что в будущем проблем с безопасностью, связанных с криптографией, станет меньше. У многих криптографических имплементаций появляются усовершенствованные API, во многом благодаря NaCl Дена Бернштейна. Кроме того, StackOverflow больше не закрепляет принятый ответ наверху. Это дает людям возможность выводить наверх ответы с лучшими решениями, чем то, которое было принято изначально. Можно руководствоваться этим. Давайте будем друг с другом любезны: ведь ставить плюсы хорошим ответам полезнее, чем ставить минусы плохим.

А теперь перейдем к конкретике.

Пример №1: AES-128 в режиме CBC на Java

Код находится здесь. На момент написания статьи у этого ответа 248 плюсов: он и самый популярный, и выбран автором. Чтобы вникнуть, что в нём не так, взгляните на типы ключа и вектора инициализации: и тот, и другой – строки. А должны быть массивами байтов.


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


Здесь мы видим следующие проблемы:

  • Жестко прописанный пароль (который ошибочно обозначен как ключ)
  • Жестко прописанный вектор инициализации строкового типа

Чтобы яснее показать разницу между паролем и ключом: у пароля размером в 128 бит должно быть 128 бит энтропии, соответственно, чтобы его взломать потребуется 2 в 128 степени попыток. В местной реализации пароль составляется из больших и маленьких букв, а также символов. Если пароль сформирован из этого набора абсолютно произвольным образом, то возможных комбинаций получается 66 в 16 степени, что соответствует порядку 2 в 96 степени. Это значит, что на взлом уйдет максимум 2 в 96 степени попыток, но на самом деле, всё хуже, потому что люди редко составляют пароли произвольным образом. Очень часто я вижу, что на шифрование уходят крайне предсказуемые пароли с финальным аккордом в духе ‘123!’. Печально, что людей приучили следовать древним рекомендациям по выбору надежных паролей, потому что это якобы даст больше безопасности. Не даст и не надо делать то, что там написано.

Важный момент: не используйте один и тот же вектор инициализации дважды – это убивает все защитные свойства. Я объяснял эту ошибку в другой статье, и это вызвало очень много споров. Когда кто-нибудь говорит: «Да ладно, поместим вектор инициализации в vault», раздражение у меня доходит до пика. Нет, не ладно: векторы инициализации не должны быть секретными, и даже если их спрятать, проблемы с безопасностью от этого никуда не денутся. Хватит создавать собственные криптолагоритмы! К сожалению, очень многие даже не осознают, что именно это и делают.

Гораздо лучший ответ предлагает в той же ветке пользователь Патрик Фэвр. Пожалуйста, ознакомьтесь с ним сами и, если решение покажется вам удачным, как показалось мне, поставьте плюс.

Код находится здесь. На момент написания статьи это второй по популярности ответ, у него 117 голосов. Он не отмечен автором, но всё-таки пользуется успехом у пользователей.

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


Теперь прокрутим вправо и посмотрим, что загружается в Rfc2898DeriveBytes в качестве соли хэш-функции.


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

Вот вам реальная история: я видел этот самый кусок, когда проводил инспекцию кода для клиента. Помню, посмотрел на соль и подумал: «Что-то наводит на мысли об ASCII». Тогда я написал программу на C, чтобы вывести ее строкой и получил следующее: «Иван Медведев». Затем я загуглил вектор инициализации и вышел на тот самый ответ на StackOverflow. Поневоле задумался, кто этот бедный Иван Медведев, имя которого увековечили в ненадежном коде.

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

Если бы автор просто скопировал пример из Rfc2898DeriveBytes от Microsoft, то оказался бы намного ближе к правильному ответу. Однако остается еще проблема шифрования без аутентификации, связанная с режимом CBC, да и итераций у Microsoft тоже маловато.

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

Относительно того, что считать подходящим количеством итераций, единого мнения нет. NIST полагает, что «число итераций должно быть настолько большим, насколько позволяет производительность сервера верификации, как правило, не меньше 10 000». OWASP ставит планку в минимум 720 000 итераций. Разброс между этими двумя значениями немалый; возможно, стоит ознакомиться с тем, что говорит Томас Порнин. Так или иначе, любое число ниже 10 000 однозначно следует считать не соответствующим современным стандартам.

Пример №3: Triple DES на Java

Вопрос задавали в 2008 году, так что некоторые недочеты можно простить. На тот момент NIST рекомендовал AES, но все-таки допускал Triple DES в отдельных случаях. Сегодня Triple DES считается абсолютно устаревшим из-за слишком маленького размера блоков, однако я по-прежнему время от времени сталкиваюсь с ним в коде.

Давайте повнимательнее изучим топовый ответ, у которого на момент написания статьи 68 плюсов:


И снова жестко прописанный пароль, но этот преображен в ключ при помощи MD5, который вышел из обихода еще в 1996 году. Но есть еще и другая проблема: функция деривации ключа MD5 вообще не работает с паролями и использовать ее таким образом, как здесь, противопоказано. PBKDF2 считается стандартом с 2000 года, вот к нему-то и надо было обратиться.

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

Пожалуйста, никогда никуда не применяйте код такого вида.

Пример №4: AES на Java

Код находится здесь. На момент написания статьи у него было 15 плюсов.


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

У более достойного ответа в той же ветке всего 11 плюсов. Там режим CBC и вектор инициализации прописаны как надо. Аутентификации нет, но в остальном всё выглядит вполне разумно.

Пример №5: пожалуйста, не делайте так

Ответ находится здесь. Скриншот делать не буду, там всё слишком плохо.

Это хороший пример того, как новая система StackOverflow, выводящая ответ с наибольшим числом голосов наверх, приносит хороший результат. Ответ, у которого на текущий момент больше всего голосов (187), дает очень толковый ознакомительный обзор по шифрованию – его однозначно стоит прочитать. Придраться могу только к некоторой путанице с SecureRandom( ), которую автор и сам признает. Риск, конечно, невелик, но вот как правильно использовать SecureRandom( ) – это очень просто.

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

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

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

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

в чем разница между использованием Rfc2898DeriveBytes и просто используя Encoding.ASCII.GetBytes(string object); ?

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

основная концепция, которую я смог понять, заключается в том, что вы можете конвертировать строковые пароли в байт массивы, используемые, например, для симметричного класса шифрования, AesManaged . Через класс RFC, но вы можете использовать значения соли и пароль при создании объекта rfc. Я предполагаю, что это более безопасно, но все же это необразованная догадка в лучшем случае! Кроме того, что он позволяет возвращать байтовые массивы определенного размера, ну что-то вроде этого.

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

объект 'rfcKey' теперь можно использовать в направлении настройка интернет .Ключ или. IV свойства о классе симметричного алгоритма шифрования.

' rj ' должен быть готов к работе !

запутанная часть . поэтому вместо использования объекта "rfcKey" я могу не просто использовать мой массив' myPassInBytes 'поможет настроить мой объект 'rj'?

Я попытался сделать это в VS2008, и немедленный ответ-нет. Но у вас, ребята, есть более образованный ответ о том, почему класс RFC используется над другим альтернатива, о которой я упоминал выше?

1 ответ:

вы действительно не хотите использовать пароль пользователя в качестве ключа шифрования, особенно С AES.

Rfc2898DeriveBytes-это реализация PBKDF2. То, что он делает, - это многократно хэшировать пароль пользователя вместе с солью. Это имеет несколько преимуществ:

во - первых, вы можете использовать пароли произвольного размера-AES поддерживает только определенные размеры ключей.

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

несколько итераций (1000 по умолчанию) замедляют атаки угадывания пароля. Рассмотрим кого-то, кто пытается угадать ваш ключ AES. Если вы просто использовали пароль, это было бы просто - просто попробуйте каждый возможный пароль в качестве ключа. На с другой стороны, с PBKDF2 злоумышленник сначала должен выполнить 1000 итераций хэша для каждого пароль угадать. Поэтому, хотя он немного замедляет пользователя, он оказывает непропорциональное влияние на злоумышленника. (На самом деле довольно часто используется гораздо более высокое количество итераций; обычно рекомендуется 10000).

Это также означает, что конечный выходной ключ равномерно распределен. Если вы использовали пароль, например, как правило, 16 из 128 бит ключа будет 0 (высокий ASCII бит). Это сразу же делает keysearch 65536 раз проще, чем должно быть, даже игнорируя угадывание пароля.

наконец, AES имеет определенные уязвимости с соответствующими ключевыми атаками. Связанные ключевые атаки возможны, когда злоумышленник знает некоторые данные, зашифрованные несколькими ключами, и существует некоторая известная (или предполагаемая) связь между ними. Например, если вы зашифровали данные как с помощью пароля-ключа "My AES key sucks" (16 байт, для AES-128), так и с помощью "MY AES Ключ сосет", может быть возможна связанная ключевая атака. В настоящее время наиболее известные атаки фактически не позволяют нарушать полный AES таким образом, но они постепенно улучшаются с течением времени - только на прошлой неделе была опубликована новая атака, которая разбивает 13 раундов (из 14 всего) AES-256 с использованием связанной ключевой атаки. Было бы крайне неразумно полагаться на то, что такие атаки со временем не станут лучше.

Я пишу последовательность шифрования для конфиденциальных данных в нашей базе данных.

В настоящее время я беру GUID на основе UserId и помещаю это через хэш. Затем я запускаю хэш через Rfc2898DeriveBytes, чтобы получить Key и IV, которые я использую для шифрования данных с помощью функции Rijndael.

Мой код выглядит так:

Затем я передаю _key и _iv для дешифрования или шифрования данных. Моя цель состоит в том, чтобы каждый пользователь всегда имел доступ к своему уникальному ключу через каждый сеанс. Говоря это, что может быть рандомизировано и по-прежнему поддерживает эту функцию? Всегда ли мне нужно использовать ту же соль и тот же IV, чтобы получить нужные данные?

спросил(а) 2020-03-21T12:00:39+03:00 1 год, 8 месяцев назад

За исключением KDF, PBKDF2 также используется в качестве функции хеширования пароля, где хеш вместо пароля сохраняется в базе данных. Таким образом, пароли могут быть проверены, в то время как пароль не может быть легко восстановлен, если данные извлекаются противником. Это описано в следующем документе RFC 8018, который содержит версию протокола 2.1.

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

Из-за ошибки проектирования, которая требует повторения всего объема работы, если требуется более одного вывода хеш-функции, не рекомендуется запрашивать у него больше данных, чем вывод хеш-функции. В этом случае лучше использовать другой метод для расширения выходного ключевого материала (байты), например, HKDF-Expand.

Замечания по коду в вопросе:

Метод GenerateHash является ложным, Rfc2898DeriveBytes сделает это за вас; Вы должны использовать что-то менее предсказуемое, чем UID, для создания ключа; данные не должны быть непосредственно доступны злоумышленнику, так как это полностью нарушит цель PBKDF2; Если вы хотите использовать один и тот же набор итераций UID + соль + для нескольких шифрований, то вы должны сгенерировать случайный IV и добавить его к зашифрованному тексту, поскольку неслучайный IV полностью нарушает назначение IV; Вы можете изменить соль, чтобы получить несколько ключей, но вам придется пройти через функцию PBKDF2 для каждого шифрования.

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

В чем разница между использованием Rfc2898DeriveBytes и просто использованием Encoding.ASCII.GetBytes(string object); ?

У меня был относительный успех при любом подходе, первый - более продолжительный подход, когда последний является простым и точным. Оба, похоже, позволяют вам делать то же самое в конечном итоге, но я изо всех сил пытаюсь понять смысл использования первого над последним.

Основная концепция, которую я смог понять, заключается в том, что вы можете преобразовать строковые пароли в байтовые массивы, которые должны использоваться, например, для симметричного класса шифрования, AesManaged . По RFC-классу вы можете использовать значения солей и пароль при создании вашего объекта rfc. Я предполагаю, что он более безопасен, но все же это необразованное предположение в лучшем случае! Кроме того, он позволяет вам возвращать байт-массивы определенного размера, ну что-то в этом роде.

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

Теперь объект 'rfcKey' можно использовать для настройки свойств .Key или .IV на классе симметричного алгоритма шифрования.

'rj' должен быть готов к работе!

Запутанная часть. поэтому вместо использования объекта 'rfcKey' я могу не просто использовать мой 'myPassInBytes', чтобы помочь настроить мой объект 'rj'?

Я попытался сделать это в VS2008, и немедленный ответ НЕТ. Но вы, ребята, получили более образованный ответ о том, почему класс RFC используется по сравнению с другой альтернативой, о которой я упомянул выше?

Вы действительно не хотите использовать пароль пользователя напрямую в качестве крипто ключа, особенно с AES.

Rfc2898DeriveBytes - это реализация PBKDF2. То, что он делает, неоднократно хеширует пароль пользователя вместе с солью. Это имеет несколько преимуществ:

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

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

Множественные итерации (1000 по умолчанию) замедляют атаки на угадывание пароля. Подумайте о том, кто пытается угадать ваш ключ AES. Если вы просто использовали пароль, это было бы просто - просто попробуйте каждый возможный пароль в качестве ключа. С другой стороны, с PBKDF2, злоумышленник сначала должен выполнить 1000 итераций хэша для каждого угадывания пароля. Таким образом, хотя он немного замедляет пользователя, он оказывает непропорциональное воздействие на атакующего. (На самом деле это довольно часто, чтобы использовать гораздо более высокие значения итераций, обычно рекомендуется 10000).

Это также означает, что конечный выходной ключ равномерно распределен. Например, если вы использовали пароль, обычно 16 из 128 бит ключа были бы равны 0 (высокий бит ASCII). То, что прямо тут же делает keyearch 65536 раз проще, чем должно быть, даже игнорируя угадывание пароля.

Наконец, AES имеет специфические уязвимости со связанными ключевыми атаками. Связанные ключевые атаки возможны, когда злоумышленник знает некоторые данные, зашифрованные несколькими ключами, и между ними существует известная (или догадка) связь. Например, если вы зашифровали данные с ключом пароля "My AES key sucks" (16 байт, для AES-128) и с "MY AES KEY SUCKS", может быть возможна соответствующая ключевая атака. В настоящее время наиболее известные атаки фактически не позволяют взломать AES таким образом, но со временем они постепенно улучшались - на прошлой неделе была опубликована новая атака, которая разбивает 13 раундов (из 14 всего) AES-256, используя связанная ключевая атака. Было бы крайне неразумно полагаться на то, что такие атаки не улучшатся с течением времени.

Читайте также: