Решил я недавно разобраться в подробностях работы SSH. Использовал его для удалённого запуска команд давно, но, будучи не слишком опытным в системном администрировании, очень размыто представлял, зачем админы просят им отправить какой-то ключ, что с этим ключом происходит при подключении, зачем при запуске ssh периодически орёт на меня какими-то предупреждениями, и прочие прелести. К своему удивлению, не смог найти ресурсов с описанием протокола, после которых у меня не осталось бы только больше вопросов. Поэтому, после прочтения спецификаций и разборок с OpenSSH, хочу разложить всё по полочкам здесь.

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

Что нужно знать

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

Терминология

Далее везде

  • Пользователь — системный пользователь, то есть который создаётся на линуксе через adduser или на винде в настройках.

  • Клиент — программа, реализующая клиентскую часть протокола SSH: отправляющая запросы к SSH-серверу, пишущая вам в консоль гневные тирады из-за смены ключей, и прочее. В дистрибутивах линукса часто предустановлен клиент OpenSSH, который запускается командой ssh.

  • Сервер — соответственно программа, реализующая серверную часть SSH. Не менее часто в дистрибутивах линукса предустановлен и сервер OpenSSH, выполненный в виде демона sshd. О запуске и настройке этого демона речи не пойдёт, как это делается, читателю предлагается найти на других ресурсах.

  • Стороны — клиент и сервер.

  • Вы — вы, то есть человек, использующий клиент.

  • Админ(истратор) — человек, ответственный за сервер. Возможно, совпадает с "вами". Также это может быть некоторая автоматическая система, например, если вы используете облачные сервисы. В этом случае взаимодействие с сервером происходит через панель управления облаком.

Что такое SSH?

SSH — это сетевой протокол для защищённого управления удалёнными устройствами по незащищённому сетевому соединению. Самый примечательный сценарий использования: удалённый вход в систему и выполнение команд.

Это вольный перевод первого абзаца статьи про SSH на английской википедии. Думаю, каждый, кто открывал хоть одну ссылку про SSH в гугле, знает об этом.

Чуть менее тривиально (написано во втором абзаце) то, что на самом деле SSH состоит из трёх более или менее независимых протоколов: протокол транспортного уровня, протокол аутентификации, и протокол соединения. Возможно, называть их прямо отдельными протоколами — слишком громкие слова, и можно было бы считать, что это просто три стадии работы протокола. Но так их обзывают в спецификации, а чем я хуже непонятно. Кстати, спецификация у каждого из них своя: RFC 4253, RFC 4252 и RFC 4254 соответственно, за подробностями можно и нужно обращаться к этим ссылкам.

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

Акт 1. Протокол транспортного уровня

Сцена 1. Установка защищённого соединения

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

Итак, первое, что делают стороны после того, как между устройствами установлено сетевое соединение (а SSH обычно работает поверх TCP, так что обычно после того, как налажено соединение TCP) — отправляют друг другу версию своего ПО и самого протокола SSH, которую хотят использовать (на момент написания статьи актуальна версия 2.0). Далее они обмениваются списками поддерживаемых алгоритмов шифрования (и не только).

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

Первый алгоритм — алгоритм сжатия. Самый скучный, им просто сжимается каждое отправленное сообщение ради экономии трафика.

Уточнение 1

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

Во-вторых, на самом деле стороны договариваются одновременно обо всех алгоритмах, а "первым" я его назвал в целях красоты повествования. Вообще всё, что описано далее про протокол транспортного уровня, происходит всего за 3-4 обмена сообщениями между клиентом и сервером (то бишь всего за 6-8 отправленных сообщений). Так что по длине текста статьи не стоит делать вывод, что все проверки и обмены ключами и алгоритмами занимают большое количество шагов, это не так. В разработке протокола старались минимизировать количество необходимых сетевых взаимодействий.

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

Уточнение 2

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

Вопрос: как так сделать, чтобы клиент и сервер, изначально общаясь по незащищённому соединению, смогли договориться о ключе симметричного шифрования, и при этом никто кроме клиента и сервера не узнал этот ключ? Ответ: использовать хитроумный алгоритм обмена ключами (англ. key exchange algorithm, он же kex algorithm). Это третий алгоритм, о котором договариваются стороны.

Конкретные алгоритмы обмена ключами — это обычно вариации на тему алгоритма Диффи-Хеллмана. На английской википедии есть очень наглядная картинка (приведена ниже), где в роли ключей выступают цвета. Сначала стороны договариваются о некотором открытом ключе (обычно клиент его генерирует и отправляет серверу по незащищённому соединению, на картинке общим ключом является жёлтый цвет). Затем каждая сторона создаёт свой закрытый ключ (на картинке цвет заката и морской волны в моей интерпретации ????). Он каким-то образом объединяется с открытым ключом (в примере с цветами — смешивается, а вообще, то, как ключи объединяются, и как в принципе генерируются, зависит от конкретного выбранного алгоритма обмена ключами), и результат отправляется другой стороне.

Затем каждая сторона объединяет уже полученный чужой результат со своим закрытым ключом, и в итоге, благодаря магии математики, оказывается, что у обеих сторон вышло одно и то же — общий секретный ключ. Далее я не буду выделять его как один из используемых ключей, потому что это всего лишь промежуточное значение, которое используется для генерации ключей, делающих более осмысленные вещи. Тут вопрос терминологии: в английском этот секретный ключ даже ключом не называется, там это просто "shared secret".

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

Иллюстрация работы алгоритма Диффи-Хеллмана.
Иллюстрация работы алгоритма Диффи-Хеллмана.

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

Об имитовставке

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

Но SSH же работает поверх TCP, который гарантирует целостность данных? Он включает контрольную сумму, которая не позволит просто так изменить сообщение? Не совсем. TCP защищает от случайных изменений пакета. Злоумышленник же, поменяв содержимое сообщения, может запросто пересчитать контрольную сумму, и подменить и её: ведь шифруются только данные, на уровне протокола SSH, а данные всех протоколов, поверх которых SSH работает, не шифруются.

Поэтому для предотвращения таких вмешательств в ход истории существует имитовставка (англ. MAC — message authentication code). Это набор байтов, который генерируется из содержимого сообщения и ключа имитовставки (integrity key), и дописывается к концу сообщения. Используя этот ключ и содержимое сообщения, каждая сторона может проверить, что оно дошло без изменений. Ключ хранится в секрете и (обычно) одинаковый у сервера и клиента, всё как с симметричным шифрованием. Таким образом, если злоумышленник подменит какие-то байты, сервер заметим подвих и не станет обрабатывать изменённое сообщение.

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

Алгоритмов имитовставки (а, следовательно, и ключей имитовставки) также может быть два на соединение: один от клиента к серверу, другой от сервера к клиенту.

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

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

Чтобы бороться с недобросовестностью сервера, используется очередной алгоритм, о котором стороны также договариваются перед обменом ключами — алгоритм цифровой подписи сервера. Это не официальное название, это я так обозвал в попытках отразить суть, в спецификации зовётся "server host key algorithm".

Сцена 2. Идентификация сервера

Пока что все описанные действия — базовые меры безопасности, которые происходят за кулисами, и с которыми редко приходится иметь дело, если вообще приходится. Теперь же речь пойдёт о вещах более приземлённых, и знание о которых важно при работе с протоколом.

У сервера изначально есть пара ключей: закрытый и открытый, они называются ключами сервера (host keys). Только на самом деле такая пара не одна: у сервера есть по паре ключей на каждый алгоритм цифровой подписи, который он поддерживает (ведь разные алгоритмы требуют разного вида ключей, вообще говоря). У сервера OpenSSH на линуксе по умолчанию ключи сервера хранятся в папке /etc/ssh в файлах с названиями ssh_host_*_key (закрытый ключ) и ssh_host_*_key.pub (соответствующий открытый ключ), вместо звёздочки пишется название алгоритма, например rsa или ecdsa. Эти ключи перед запуском SSH-сервера кладёт туда администратор: либо генерирует с нуля (например, с помощью утилиты ssh-keygen -A), либо устанавливает из каких-то других соображений, мало ли в кармане завалялись.

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

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

Как убедиться, что сервер настоящий — это вопрос менее тривиальный, чем применить цифровую подпись. Есть разные варианты, одни более безопасные, другие менее. Чаще и проще всего это делается так: клиент хранит у себя небольшую базу данных (обычно просто в виде файла в определённом формате), которые сопоставляют каждому серверу его открытый ключ. Например, клиент OpenSSH на линуксе смотрит в файл ~/.ssh/known_hosts.

Теоретическая ситуация: мне нужно ssh-нуться к машинке по адресу example.com. Для этого (будучи ответственным и волнующимся о безопасности человеком) я пойду к администратору, и узнаю у него публичный ключ сервера, запущенного на этой машинке (соответствующий алгоритму, который буду использовать для проверки сервера). Затем добавлю в known_hosts запись о том, что публичный ключ сервера example.com такой-то (формат файла подскажет гугл). Затем при каждом подключении к example.com клиент будет проверять, что отправленный сервером открытый ключ совпадает с тем, что лежит в known_hosts. Если совпадает, клиент проверит, что сервер правда владеет соответствующим закрытым ключом как описано выше. Если владеет — значит сервер точно не подставной. Небольшой нюанс: так как в known_hosts ключ сопоставляется домену, то если я хочу подключаться к этой машинке не только по домену, но и по IP адресу, этому IP адресу тоже нужно отдельной строчкой сопоставить тот же ключ.

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

The authenticity of host '...' can't be established.
ED25519 key fingerprint is SHA256:noP2S4gaQmKTIO8cHHg4ju3QptLSo6MEgCw2AiLwOJM.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])?

Таким образом клиент говорит, что не знает, можно ли доверять этому серверу, и оставляет это на ваше усмотрение. Как можно действовать:

  • Набрать no и отменить подключение.

  • Набрать yes: тогда клиент автоматически добавит полученный открытый ключ сервера в known_hosts, сопоставив его домену, к которому пытались подключиться (обычно добавляется и сопоставление к IP адресу, соответствующему на данный момент этому домену). Перед тем, как бездумно набирать yes, можно обратить внимание, что выше выведен хеш открытого ключа. Его можно сравнить с ожидаемым хешем ключа (например, опять же, спросить у админа, какой должен быть хеш, или, если подключаетесь к машине в облаке, в панели управления облака он может быть указан).

  • Также можно ввести отпечаток (fingerprint) — это, грубо говоря, тоже хеш публичного ключа, который можно спросить у админа или скопировать из авторитетного источника, тогда клиент сам проверит, что отпечаток ключа, отправленного сервером, совпадает со введённым вами, и, если это так, добавит его в known_hosts.

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

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the RSA key sent by the remote host is
6e:45:f9:a8:af:38:3d:a1:a5:c7:76:1d:02:f8:77:00.
Please contact your system administrator.
Add correct host key in /home/hostname /.ssh/known_hosts to get rid of this message.

Собственно русским по белому написано: возможно вас пытаются злоумыслить, а возможно просто ключ поменялся. Узнать это можно только обратившись к администратору. Есть способы, как организовать регулярную смену ключей, но я недостаточно квалифицирован, чтобы их описывать. Любопытный читатель загуглит "ssh key rotation".

Хранить публичный ключ вкупе с доменом — не единственный способ идентификации сервера, ещё можно, например, использовать сертификаты: тогда сервер перед обменом ключами отправит свой сертификат, который клиент сможет проверить на достоверность. Но о них, возможно, в другой раз.

Итого

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

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

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

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

Акт 2. Протокол аутентификации

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

Простейший способ аутентифицировать клиента: не аутентифицировать клиента. Да, можно настроить сервер для некоторых пользователей так, что как только соединение установлено, считается, что клиент молодец, и сразу может от имени этих пользователей выполнять команды/делать что там SSH ещё умеет делать. Под каким пользователем нужно подключиться, вы выбираете сами при подключении (пишете ssh myuser@example.com), если для него авторизация выключена, то и ни паролей, ни ключей не понадобится. Естественно, это дико небезопасно, но в некоторых случаях можно оправдать такую схему.

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

А вот обязательна возможность входа ещё менее простейшим, но более безопасным способом — по ключу. Здесь уже вы сами создаёте пару ключей клиента (например, с помощью утилиты ssh-keygen, по умолчанию закрытый ключ она записывает в файл ~/.ssh/id_rsa, а открытый — в ~/.ssh/id_rsa.pub), сообщаете администратору открытый ключ (при использовании ssh-keygen это id_rsa.pub), тот добавляет его на сервер. Если запущен сервер OpenSSH на линуксе, то у каждого пользователя на сервере в домашней папке есть файл ~/.ssh/auhorized_keys: администратор добавляет ключ клиента в authorized_keys того пользователя, под которым клиент сможет входить. Можно добавить один ключ в authorized_keys нескольких пользователей, тогда вы сможете входить под всеми ними. Под каким конкретно вы пытаетесь войти — опять же, указываете при подключении, например ssh myuser@example.com.

Дальше всё как было при идентификации сервера: клиент составляет строку (из данных, которые сервер тоже знает, конкретная строка описана в спецификации) подписывает закрытым ключом, и отправляет подпись серверу вместе с открытым ключом, а также именем пользователя, под которым хочет войти (допустим, myuser). Сервер проверяет, что открытый ключ ему известен (OpenSSH на линуксе смотрит, что он указан в файле /home/myuser/.ssh/authorized_keys), составляет такую же строку, и проверяет достоверность подписи. Если всё совпало — клиент идентифицирован, и можно принимать от него команды/файлы/что угодно.

Важно! Ни в коем случае нельзя никому сообщать свой закрытый ключ (по умолчанию ~/.ssh/id_rsa без расширения .pub). Это всё равно, что рассказать свой пароль: сервер не может отличить вас от злоумышленника, имеющего ваш закрытый ключ.

Акт 3. Протокол соединения

На этом этапе, защищённое соединение установлено, клиент подтвердил личность сервера, а сервер — клиента. Протокол соединения работает уже на прикладном уровне: если SSH используется для запуска команд в терминале он описывает, как передавать сами команды и настройки терминала, если для передачи файлов — процесс передачи файлов. Это наименее интересная для меня часть SSH, так что в подробности вдаваться не буду. Просто определяется какой-то набор сообщений: как запустить команду на удалённом устройстве, как сообщить серверу, что на клиенте изменились размеры окна терминала, и т.д. и т.п.

Резюме

При установке соединения в SSH используются следующие ключи:

  • Ключ сервера — концептуально это ключ цифровой подписи, нужен для проверки личности сервера клиентом. Не используется для шифрования.

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

  • Ключ синхронного шифрования и ключ имитовставки — меняются при каждом подключении, могут меняться в течение сессии, используются для защиты соединения.

Если вы используете OpenSSH на линуксе, то вот список основных файлов, которые используются. На клиенте:

  • ~/.ssh/id_rsa — закрытый ключ клиента, который никому нельзя сообщать.

  • ~/.ssh/id_rsa.pub — открытый ключ клиента, который администратор добавляет на сервер, чтобы вы получили доступ.

  • ~/.ssh/known_hosts — список, сопоставляющий доменам/IP адресам серверов их открытые ключи. Используется чтобы удостовериться, что сервер не подставной.

  • ~/.ssh/config, /etc/ssh/ssh_config — в статье не упоминался, но это настройки клиента SSH, подробнее, что можно настроить, расскажет man ssh_config.

На сервере:

  • /etc/ssh/ssh_host_*_key — закрытые ключи сервера, по ключу на каждый поддерживаемый алгоритм цифровой подписи.

  • /etc/ssh/ssh_host_*_key.pub — открытые ключи сервера.

  • /home/[user]/.ssh/authorized_keys — список открытых ключей клиентов, которым разрешено входить под пользователем [user].

  • /etc/ssh/sshd_config — настройки сервера, подробнее в man sshd_config.

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

Комментарии (37)


  1. sshemol
    10.07.2023 12:58
    +1

    Чем ключ безопаснее 20-символьного случайного пароля?


    1. agalakhov
      10.07.2023 12:58
      +12

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


      1. mpa4b
        10.07.2023 12:58
        +2

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


        1. pae174
          10.07.2023 12:58

          На сервере ещё можно настроить требование логиниться по комбинации "пароль от учетки + ключ".


          1. AVX
            10.07.2023 12:58

            Как это сделать? Чтобы И пароль с логином И ключ был нужен? Но чтобы без чего-то одного нельзя было войти.


            1. klirichek
              10.07.2023 12:58

              PAM. Там можно много интересного накрутить. Но однако практичнее комбинация, когда либо по ключу, либо по двойному паролю (пароль от учётки + одноразовый код). По 'PAM 2fa' можно нагуглить решения.


  1. funca
    10.07.2023 12:58
    +1

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

    Ключи в SSH не имеют срока жизни и их практически нереально отозвать в случае компрометации, ухода сотрудника или же банальной ротации. Такое решение ущербно от природы и в этом смысле они ни чуть не лучше паролей. Хорошо если вы гуру secrets management, точно знаете исчерпывающий список серверов, где их использовали и имеете возможность оперативно отовсюду удалить. А если нет?

    И тут вы обнаруживаете, что допустим ssh.com не просто так использует коммерческий домен. Здесь они прямым текстом топят за прямо противоположный passwordless/keyless approach с короткоживущими токенами и предлагают не маяться дурью, а закупиться PrivX. Все популярные cloud предлагают какие-то подобные аналоги, но по сути это тоже решение за которое надо платить.


    1. pae174
      10.07.2023 12:58
      +6

      Ключи в SSH не имеют срока жизни и их практически нереально отозвать в случае компрометации, ухода сотрудника или же банальной ротации.

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

      Ключи, кстати, могут быть ограничены по времени жизни - это правило пишется в ~/.ssh/authorized_keys прямо вместе с самим ключем.


    1. mpa4b
      10.07.2023 12:58
      +5

      Как будто openssh не умеет использовать PAM.


    1. websnow
      10.07.2023 12:58

      Использовать один ключ для всех серверов не следует. Одна машина, даже "на пару дней" - один ключ (чтобы не возникало ситуации, что ключ для "временных VPS" стал вдруг использоваться повсюду). Дополнительно можно ограничение по ip поставить,банить новые подключения (запускать скрипт при первом подключении с нового устройства), время жизни ключей настроить, 2fa прикрутить - однако в большинстве случаев последнее излишне


    1. lrrr11
      10.07.2023 12:58
      +3

      Ключи в SSH не имеют срока жизни и их практически нереально отозвать в случае компрометации, ухода сотрудника или же банальной ротации

      почему нереально? Есть всякие KRL и так далее. Добавляем строку RevokedKeys в sshd_config, и можно увольнять.


      1. randomsimplenumber
        10.07.2023 12:58
        +2

        Можно просто удалить из authorized_keys.

        Если серверов >1, все равно нужны централизованные решения


    1. truthseeker
      10.07.2023 12:58

      А что мешает пароли от учёток юзеров раздавать через LDAP? SSH умеет в LDAP. А что мешает публичные ключи юзеров раздавать через LDAP на тачки? Уволился сотрудник — централизованно удаляем записи о пользователе, и его пароле и ключе, и всё. Кроме LDAP есть ещё варианты использования связки SSH + Keycloack + Vault. Принудительная ротация ключей и смена паролей в таком случае тоже вполне себе реализуемый челлендж. Некоторые компании для подобных действий по блокированию/удалению учёток даже удобный WebUI себе пилят. В принципе, реализовать такое на каком-то Django, или даже flask + flask-bootstrap — вполне себе посильная задача даже для админа-одиночки. В случае с Keycloack даже пилить свой отдельный WebUI не нужно. И вот у вас уже возможность оперативного удаления ключей/хэшей паролей со всех ваших тачек, и даже с WebUI, так чтобы безопасник мог их выпилить даже зайдя куда надо со своего смартфона...


  1. mpa4b
    10.07.2023 12:58
    +1

    Важно! Ни в коем случае нельзя никому сообщать свой закрытый ключ (по умолчанию ~/.ssh/id_rsa без расширения .pub). Это всё равно, что рассказать свой пароль, только пароль можно сменить самому, а чтобы сменить ключ, надо дёргать администратора.

    Вообще-то нифига подобного. Достаточно на машине, откуда запускается клиент ssh, сделать ssh-keygen и вместо старой пары ключей сгенерить новый. Залогинившись по старому, прописать в .ssh/authorized_keys новый. Ситуация ровно точно такая же как с утёкшим паролём: можно или просить админа прибить вообще любые пароль/ключи юзернейма на целевой машине, или же понадеявшись на авось, зайти старым паролём/ключём и сменить на новый.

    расшифровывает строку открытым ключом

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

    Кроме того, проверка подписей (как сервера так и клиента) позволяет защититься от атаки типа man-in-the-middle, когда злоумышленник вклинивается в обмен между клиентом и сервером, клиент думает, что он выработал общий симметричный ключ с сервером, но на самом деле с злоумышленником, то же верно и для сервера, а злоумышленник расшифровывает и зашифровывает данные между ними. Подписывается информация, известная и клиенту и серверу, но лоумышленник не имеет закрытых ключей чтобы сформировать корректную подпись, соответственно и атака man-in-the-middle будет разоблачена.


    1. jabuj Автор
      10.07.2023 12:58
      +1

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


    1. s7eepz
      10.07.2023 12:58

      Достаточно на машине, откуда запускается клиент ssh, сделать ssh-keygen и вместо старой пары ключей сгенерить новый. Залогинившись по старому

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


      1. randomsimplenumber
        10.07.2023 12:58
        -1

        Если старый ключ скомпроментирован, его нет смысла сохранять.


        1. mayorovp
          10.07.2023 12:58

          Как вы зайдёте на сервер чтобы записать туда новый ключ если потеряете старый?


  1. pae174
    10.07.2023 12:58
    +1

    Тема Message Authentication Code (MAC) не раскрыта. Если бы этого не было то MitM мог бы заменить передаваемые данные на всякий мусор. То есть шифрование само по себе защищает только от прослушки и от замены передаваемых данных на осмысленные данные злоумышленника. Шифрование не защищает от замены передаваемых данных на мусор, это делает MAC.


    1. jabuj Автор
      10.07.2023 12:58

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


      1. jabuj Автор
        10.07.2023 12:58
        +2

        UPD: я обдумал эту тему, и понял, что действительно как-то не гоже утверждать, что соединение защищено без упоминания MAC. Написал чуть более подробно о нём.


  1. domix32
    10.07.2023 12:58
    +1

    ключа имитовставки (integrity key),

    А откуда лексика взята? integrity key вполне себе по-русски "ключ целостности", насколько мне известно.


    1. jabuj Автор
      10.07.2023 12:58

      https://ru.wikipedia.org/wiki/Имитовставка

      А вообще, мне название "ключ целостности" кажется немного размытым. Всё-таки "имитовставка" подразумевает, что это дополнительная "вставка" в сообщение, а "ключ имитовставки" соответственно нужен чтобы эту "вставку" сгенерировать и проверить. А "ключ целостности" по-моему никак не описывает, что он из себя представляет и как именно используется. Но кому как, я знаком с MAC только по английским ресурсам, не возьмусь утверждать, какая терминология закрепилась в русском.


      1. domix32
        10.07.2023 12:58

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


        1. jabuj Автор
          10.07.2023 12:58
          +2

          Во-первых, то, что её ввели в старом отменённом ГОСТе, не значит, что она неверная. Возьмите, например, ГОСТ Р 34.13-2015. Издан в 2016 году, сейчас действует, и использует эту лексику.

          Во-вторых статья, которую вы привели говорит о целостности в совершенно другом контексте, собственно в этом и размытость. Имитовставка, она же MAC — вполне конкретное понятие, набор байт, благодаря которому можно убедиться в целостности сообщения. Целостность — очень общее понятие, которое применимо к самым разным контекстам. Если хотите, говорите "ключ целостности". Я же считаю, что "имитовставка", как более узкий термин, здесь уместна, а раз алгоритм имитовставки, то и ключ для него предпочитаю называть ключом имитовставки.


    1. mpa4b
      10.07.2023 12:58

      Прочитал про "имитовставку". Написано так, что не зная заранее -- даже не поймёшь о чём речь. На самом деле всё гораздо проще. Это может быть обычный криптохеш типа SHA256 от конкретного пакета SSH (не путать с пакетами IP, TCP и т.д.), по алгоритму HMAC замешанный с секретным ключом. Или же равносильные по взломостойкости (при условии применения секретного ключа) алгоритмы типа UMAC или GCM (последний ещё и способ применения шифра устанавливает). Ещё пример -- бандл шифра и MACа типа ChaCha20-Poly1305. Но суть всегда одна -- хеш, который не зная секретного ключа, невозможно подделать.


      1. domix32
        10.07.2023 12:58

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


        1. mpa4b
          10.07.2023 12:58

          Вообще "имитовставка" -- это терминология из ссср-российской криптологии, которая видимо шла своим путём до 90х. Лично мне конечно непривычно, но кому-то наверное ОК.


    1. klirichek
      10.07.2023 12:58

      Ну, например "OpenSSL 3. Ключ к тайнам криптографии". Свежая книжка (2023) от ДМК.

      Там именно "имитовставка". Возможно, переводчик посмотрел в википедии, но термин вполне на слуху. Просто обычно с криптографией говорят в терминах оригинальных нерусскоязычных аббревиатур. А там MAC - требует контекста. (mac-адрес устройства ethernet?). Имитовставка сразу понятна.


  1. s7eepz
    10.07.2023 12:58
    +1

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

    ssh-keygen [-t dsa | ecdsa | ecdsa-sk | ed25519 | ed25519-sk | rsa]

    Рекомендуется использовать ed25519, а не rsa.


    1. klirichek
      10.07.2023 12:58

      Не все его поддерживают, к сожалению.

      На старый роутер с dropbear на борту порой только rsa. Хотя чуть посвежее уже да, могут в ed25519 (а вот в ecdsa не могут).


  1. s7eepz
    10.07.2023 12:58
    +1

    при смене ключей на сервере, на клиенте можно воспользоваться

    ssh-keygen -R hostname

    удалит старый отпечаток и добавит новый.


  1. sirri
    10.07.2023 12:58

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

    Тапками прошу не кидать, ибо я слабовать в таких технологиях. Не айтишнег йа.


    1. jabuj Автор
      10.07.2023 12:58

      Да, именно так. Если потеряете — не конец света, только придется создать новую пару ключей, и попросить администратора перенастроить авторизацию, отправив новый открытый ключ. Главное, чтобы потерянный закрытый никто не узнал до того момента, как старый открытый не удалят с сервера.


      1. sirri
        10.07.2023 12:58

        Спасибо! А если это мой сервер, т.е. я еще и администратор?


        1. mayorovp
          10.07.2023 12:58

          Тогда у вас есть локально-удалённая консоль этого сервера на крайний случай.


  1. s7eepz
    10.07.2023 12:58

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

    KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha256
    
    Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
    
    MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,umac-128@openssh.com