Перейти к содержимому
New

DeepSeek V4.1 Flash free for 7 days: 10M tokens a day after a top-up of $10 or more. Until Nov 30.

How it works

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

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

Начальный8 мин чтенияАвтор: NotHoly

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

Если вы пользовались TLS или SSH, вы уже полагались на эту идею. И замок в браузере, и запрос отпечатка в SSH-клиенте зависят от публичных ключей. Здесь объясняется идея, которая лежит в основе этих инструментов. Вы создадите пару ключей Ed25519 с помощью OpenSSL, подпишете файл, проверите подпись, а затем измените в файле одну строку и увидите, как проверка провалится. Подписанные релизы программ используют этот же механизм, и на нём основаны SSH-ключи из следующей статьи.

Что такое пара ключей

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

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

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

Шифрование и подпись решают противоположные задачи

Их всё время путают, так что не смешивайте их.

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

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

Ed25519, алгоритм из этой лабораторной, умеет только подписывать. Зашифровать сообщение он не может. Для шифрования с открытым ключом служат другие конструкции, например RSA-OAEP или согласование ключа на эллиптических кривых с последующим симметричным шифром. Поэтому «публичный ключ» обозначает вид ключа и не привязан к одной задаче. Сначала разберитесь с задачей, потом с алгоритмом.

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

Почему приватный ключ никогда не должен покидать владельца

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

Отсюда четыре практических правила:

  • Создавайте приватный ключ на машине, которая будет его использовать, или на аппаратном токене, предназначенном для этого. Не скачивайте приватный ключ с сайта и не получайте его в чате.
  • Ограничьте права на файл. Защитите ключ парольной фразой, если инструмент это поддерживает, или держите его в аппаратном хранилище.
  • Не допускайте его в Git, тикеты, скриншоты и обращения в поддержку. Сканеры секретов ищут заголовки PEM. Если ключ попал в историю репозитория, считайте его скомпрометированным, даже после удаления файла.
  • Если подозреваете утечку, выведите ключ из обращения. Создайте новую пару, опубликуйте новый публичный ключ через канал, которому доверяют проверяющие, и отзовите старый. Удаление локального файла не убирает копии, которые уже покинули вашу машину.

Раздавать публичный ключ безопасно по замыслу. Сложнее обратная задача. Проверяющий должен знать, что публичный ключ действительно принадлежит тому человеку или проекту, чьё имя на нём указано. Для этого и существуют удостоверяющие центры, закреплённые ключи релизов и отпечатки ключей SSH-хостов. Действительная подпись доказывает, что владелец ключа подписал эти байты. Того, что владелец ключа — именно тот, кого вы ожидали, она не доказывает.

Как работают подписание и проверка, шаг за шагом

Для подписанного релиза последовательность такая:

  1. Издатель один раз создаёт пару ключей и хранит приватный ключ в секрете.
  2. Издатель передаёт публичный ключ через канал, которому доверяет проверяющий: страницу, которой тот уже доверяет, отпечаток, опубликованный во втором месте, или ключ, проверенный раньше.
  3. Для каждого релиза издатель подписывает приватным ключом точные байты файла. Подпись Ed25519 занимает 64 байта.
  4. Издатель распространяет файл вместе с подписью.
  5. Проверяющий выполняет проверку по байтам файла, подписи и доверенному публичному ключу. Ответ: действительна или нет.

Две детали важнее, чем кажутся. Во-первых, подпись покрывает точные байты. Один лишний пробел, другой конец строки или переупакованный архив — это уже другое сообщение, и проверка провалится. Во-вторых, подписание Ed25519 по RFC 8032 детерминировано. Один и тот же ключ и одно и то же сообщение всегда дают одну и ту же подпись, поэтому свежее случайное число каждый раз не нужно. Так исчезает один из распространённых источников сбоев. FIPS 186-5 перечисляет EdDSA наряду с ECDSA и RSA как одобренные алгоритмы цифровой подписи.

Лабораторная: подпишите файл и посмотрите, как проверка провалится

Нужен OpenSSL 3.x. Вывод в этой лабораторной получен на Windows 11 с Git Bash и OpenSSL 3.2.4. Сначала проверьте версию командой openssl version. Более старые сборки могут не принять те же опции.

Используйте Git Bash, оболочку Linux или Terminal на macOS. Windows PowerShell 5.1 при перенаправлении через > пишет UTF-16, а это меняет подписываемые байты. Запускайте лабораторную в пустой папке на своей машине. К серверам она не обращается.

Создайте пару ключей

bash
openssl genpkey -algorithm ed25519 -out priv.pem
openssl pkey -in priv.pem -pubout -out pub.pem

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

bash
ls -l priv.pem pub.pem

На нашей машине priv.pem занял 122 байта, а pub.pem 116 байт. Откройте pub.pem, и вы увидите блок -----BEGIN PUBLIC KEY-----. Не выводите и не вставляйте никуда priv.pem. В этой лабораторной это секретная половина личности, а привычка никогда его не показывать понадобится и с SSH-ключами.

Подпишите файл

bash
echo 'release v1.0' > msg.txt
openssl pkeyutl -sign -inkey priv.pem -rawin -in msg.txt -out msg.sig
ls -l msg.sig

Опция -rawin говорит OpenSSL подписывать содержимое файла напрямую. Ed25519 хеширует входные данные внутри алгоритма, поэтому заранее хешировать файл не надо. Команда подписания ничего не выводит. Наш msg.sig занял 64 байта.

Проверьте публичным ключом

bash
openssl pkeyutl -verify -pubin -inkey pub.pem -rawin -in msg.txt -sigfile msg.sig

Вывод:

text
Signature Verified Successfully

Флаг -pubin говорит, что в файле ключа только публичная половина. Проверяющему больше ничего не нужно, поэтому такую проверку можно выполнить на другой машине, где priv.pem никогда не было.

Измените одну строку и проверьте снова

bash
echo 'release v1.1' > msg.txt
openssl pkeyutl -verify -pubin -inkey pub.pem -rawin -in msg.txt -sigfile msg.sig

Вывод:

text
Signature Verification Failure

Команда завершается со статусом 1. Подпись в msg.sig сделана над release v1.0, поэтому за release v1.1 она поручиться не может. OpenSSL не говорит, что изменилось. Он сообщает только, что байты и подпись расходятся. Проверяющий должен считать такой результат жёсткой остановкой, а не предупреждением, которое можно пропустить кликом.

Проверьте крайние случаи

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

Двойное подписание одного файла даёт одинаковые байты:

bash
echo 'release v1.0' > msg.txt
openssl pkeyutl -sign -inkey priv.pem -rawin -in msg.txt -out a.sig
openssl pkeyutl -sign -inkey priv.pem -rawin -in msg.txt -out b.sig
cmp a.sig b.sig && echo "identical"

Вывод:

text
identical

Файл без завершающего перевода строки не проходит проверку. echo добавляет перевод строки, поэтому этот файл на один байт короче:

bash
printf 'release v1.0' > nonl.txt
openssl pkeyutl -verify -pubin -inkey pub.pem -rawin -in nonl.txt -sigfile a.sig

Вывод:

text
Signature Verification Failure

msg.txt занимает 13 байт, а nonl.txt 12 байт. Подпись сделана над версией в 13 байт, поэтому более короткий файл проверку не проходит.

Другая пара ключей тоже не проходит проверку, даже на исходном файле:

bash
openssl genpkey -algorithm ed25519 -out other.pem
openssl pkey -in other.pem -pubout -out other.pub
openssl pkeyutl -verify -pubin -inkey other.pub -rawin -in msg.txt -sigfile a.sig

Вывод:

text
Signature Verification Failure

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

Частые ошибки

  • Принимать подпись за шифрование. Подписанный файл по-прежнему может прочитать кто угодно. Если нужна секретность, нужно шифрование, а это отдельная конструкция.
  • Доверять любому найденному публичному ключу. Действительная подпись доказывает, что владелец ключа подписал байты. Кто этот владелец, она не доказывает. Сверяйте отпечатки по второму каналу.
  • Проверять не те байты, которые подписывали. Конвейер, который перекодирует, пересжимает или меняет концы строк, ломает проверку. Подписывайте финальный артефакт и проверяйте именно его.
  • Подписывать в оболочке, которая переписывает байты. Один из примеров — перенаправление в Windows PowerShell 5.1. Другой — редакторы, которые сохраняют файл с другой кодировкой или концом строки.
  • Использовать один ключ для всего. Отдельные ключи для подписи релизов, входа по SSH и шифрования ограничивают ущерб, когда утекает один из них.
  • Считать удалённый ключ исчезнувшим. Копии в бэкапах, папках синхронизации, истории оболочки или логах чатов переживают локальное удаление.
  • Выбирать примитивы по названию. «RSA» или «ECC» не говорят, какое дополнение, какая кривая или какой режим используются. Читайте параметры в стандарте, а не только метку.

Что дальше

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

Чтобы разобраться глубже, прочитайте RFC 8032: там видно, как устроены подписи Ed25519 и почему схема детерминирована. Прочитайте FIPS 186-5, чтобы увидеть, какое место EdDSA занимает среди одобренных алгоритмов подписи. Прочитайте man-страницы OpenSSL genpkey и pkeyutl, чтобы увидеть другие опции, например выбор дайджеста для алгоритмов, которым он нужен. Затем проверьте релиз проекта, которым уже пользуетесь, с ключом, опубликованным на его собственном сайте. Запускайте только файлы, скачанные из официального источника, и только после проверки.

Источники

Стандарты и man-страницы выше — основные источники этой статьи:

  • RFC 8032, Edwards-Curve Digital Signature Algorithm (EdDSA)
  • FIPS 186-5, Digital Signature Standard (DSS)
  • OpenSSL documentation, openssl-genpkey
  • OpenSSL documentation, openssl-pkeyutl

Источники

  1. RFC 8032: Edwards-Curve Digital Signature Algorithm (EdDSA)
  2. FIPS 186-5: Digital Signature Standard (DSS)
  3. OpenSSL documentation: openssl-genpkey
  4. OpenSSL documentation: openssl-pkeyutl

Ещё по теме: Криптография

Криптография

Пароли и хеширование с Argon2id

Почему быстрые хеши не годятся для паролей, что делают соль и коэффициент нагрузки и как хешировать и проверять пароли с Argon2id в Python через argon2-cffi.

Средний9 мин чтения