[Перевод] Шифрование ключа по умолчанию в OpenSSH хуже его отсутствия
Авторы этого материала приводят аргументы против стандартных механизмов шифрования ключа в OpenSSH.
Недавно злоумышленники использовали npm-пакет eslint-scope для кражи npm-токенов из домашних каталогов пользователей. В свете этого события мы занялись проверкой других подобных уязвимостей и задумались над тем, как снизить риски и последствия таких инцидентов.
У большинства из нас под рукой есть RSA SSH-ключ. Такой ключ наделяет владельца самыми разными привилегиями: как правило, он используется для доступа к production среде или в GitHub. В отличие от nmp-токенов SSH-ключи зашифрованы, и поэтому принято считать, что ничего страшного не произойдет, даже если они попадут не в те руки. Но так ли это на самом деле? Давайте узнаем.
user@work /tmp $ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/user/.ssh/id_rsa): mykey
...
user@work /tmp $ head -n 5 mykey
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,CB973D5520E952B8D5A6B86716C6223F+5ZVNE65kl8kwZ808e4+Y7Pr8IFstgoArpZJ/bkOs7rB9eAfYrx2CLBqLATk1RT/
Этот ключ зашифрован, о чем гласит одна из первых строк файла. Кроме того, в начале нет MII — base64-ключа кодирования, используемого в RSA. И, конечно же, в глаза бросается AES! Это ведь хорошо, так? И CBC, на первый взгляд, со случайным вектором инициализации. Кода аутентификации (MAC«а) нет. Ну и ладно, зато не будет никакой padding oracle атаки, верно?
Узнать, что на деле означает содержимое DEK-Info, не так-то просто. Поиск по ключевому слову «DEK-Info» в репозитории openssh-portable показывает только примеры ключей. Но суть здесь в том, что AES-ключ есть не что иное, как простой MD5-хеш (пароль || вектор инициализации [:8]). И это скверно, ведь передовые практики хранения паролей гласят, что пароли в чистом виде в силу своей низкой энтропии представляют собой плохой материал для шифрования. И, чтобы сделать его лучше, нужна затратная функция вроде Argon2. Но MD5, в отличие от последнего, легко поддается вычислению.
Единственный положительный момент в этой схеме в том, что соль помещается после пароля, поэтому вычислить промежуточное состояние MD5(IV[8:]) и подобрать пароли на его основании не получится. Но это слабое утешение, особенно в эпоху, когда нам доступны машины, выполняющие миллиарды вызовов MD5 в секунду, — больше, чем можно придумать паролей.
У вас может возникнуть вопрос, как OpenSSH дожил до такого. Увы, ответ прост: инструмент командной строки OpenSSL изначально использовал эту схему по умолчанию, и она попросту стала нормой.
В итоге справедливым становится замечание, что стандартные, зашифрованные паролем ключи ничем не лучше обычных незашифрованных просто потому, что механизм шифрования неэффективен. Однако мы высказались бы еще смелее — они хуже. И аргументировать это просто.
Для хранения пароля от SSH-ключа многие едва ли будут пользоваться менеджером паролей. Скорее пользователь просто запомнит его. И, поскольку это одна из выученных на память комбинаций, велика вероятность, что пользователь уже применял ее где-либо еще. Возможно, она даже совпадает с пользовательским паролем от устройства. Угадать его вполне реально (слишком ненадежная у него формирующая функция), и если пароль станет известен, можно проверить его наверняка по публичному ключу.
К самой паре RSA-ключей никаких претензий нет: весь вопрос лишь в методах симметричного шифрования приватного ключа. Провести описанную выше атаку, зная один лишь открытый ключ, нельзя.
Как можно исправить ситуацию?
В OpenSSH предусмотрен новый формат ключей, которым следует пользоваться. Под новым имеется в виду введенный в 2013 году. Этот формат использует bcrypt_pbkdf, по существу представляющий собой bcrypt с фиксированной сложностью, реализованный в рамках стандарта PBKDF2.
Удобно то, что вы автоматически получаете ключ нового формата при генерации Ed25519 ключей, поскольку старый формат SSH-ключа не поддерживает более новые типы ключей. Это довольно странно, ведь на самом деле нам вовсе не нужно, чтобы формат ключа определял, как работает Ed25519-сериализация, поскольку Ed25519 сам по себе задает работу сериализации. Но если уж нужна хорошая формирующая функция, то можно не заморачиваться с такими мелочами. В итоге один из ответов — ssh-keygen -t ed25519.
Если по соображениям совместимости необходимо придерживаться RSA, можно воспользоваться ssh-keygen -o. Таким образом, можно получить новый формат даже для старых типов ключей. Обновить старые ключи можно с помощью команды ssh-keygen -p -o -f имя ключа. Если ваши ключи живут на Yubikey или смарт-картах, то там эти поправки уже учтены.
Так или иначе, мы стремимся к более оптимальному выходу. С одной стороны, есть хороший пример aws-vault, в которой информация об учетных данных была перемещена с диска в связки ключей. Есть и иной подход: перемещение разработки в разделенные окружения. И, наконец, большинству стартапов следует рассмотреть отказ от длительного хранения SSH-ключей и переход на центр SSH-сертификации с ограниченным временем хранения ключей вкупе с системой единого входа. К сожалению, в случае с GitHub такой подход невозможен.
P. S. Трудно проверить эту информацию в авторитетном источнике, но, если нам не изменяет память, версионный параметр в приватных ключах OpenSSH формата PEM влияет только на способ шифрования. Однако это не играет никакой роли: проблема заключается в формирующей ключ функции, и, думаем, это еще один аргумент против обсуждения протоколов по частям. На эту тему будет отдельный пост в нашем блоге.
И напоследок — ссылка на полный ключ. Это на случай, если сегодня вы настроены что-нибудь взломать.