Криптография
Шифрование, кодирование и хеширование: что делает каждое
Разделяем кодирование, шифрование и хеширование на лабораторных с Base64, SHA-256 и OpenSSL. Почему Base64 ничего не защищает, а хеши необратимы.
Публичные и приватные ключи с нуля: что доказывает подпись, почему приватный ключ остаётся в секрете, и лабораторная на Ed25519, где изменённый файл не проходит проверку.
Криптография с открытым ключом решает задачу, с которой общие секреты не справляются. Две стороны, которые никогда не встречались, могут договориться о секрете по сети, которую может читать кто угодно. Кроме того, можно доказать, что файл написан вами, не передавая никаких секретов тем, кто это проверяет. Обе вещи работают потому, что у пары ключей две половины, связанные математически. Публичную половину можно свободно раздавать. Приватная остаётся у владельца.
Если вы пользовались TLS или SSH, вы уже полагались на эту идею. И замок в браузере, и запрос отпечатка в SSH-клиенте зависят от публичных ключей. Здесь объясняется идея, которая лежит в основе этих инструментов. Вы создадите пару ключей Ed25519 с помощью OpenSSL, подпишете файл, проверите подпись, а затем измените в файле одну строку и увидите, как проверка провалится. Подписанные релизы программ используют этот же механизм, и на нём основаны SSH-ключи из следующей статьи.
Пара ключей — это два значения, созданные вместе. Приватный ключ секретен. Публичный можно скопировать в файл, отправить письмом или вставить в конфигурацию сервера. Связывает их математика, работающая в одну сторону. Получить публичный ключ из приватного легко. Восстановить приватный ключ из публичного по замыслу практически невозможно.
Криптографы называют это асимметричной криптографией, потому что стороны используют разные ключи. Симметричное шифрование, например AES, использует один секрет в обе стороны. Асимметричная схема делит работу: один ключ выполняет операцию, а другой обращает её или проверяет.
Поможет грубая картинка. Представьте публичный ключ как прорезь почтового ящика. Опустить письмо может кто угодно, а ключ от ящика есть только у владельца. С подписями роли меняются. Владелец ставит на документе печать, которую может сделать только он, а любой, у кого есть публичный образец, может её проверить. Картинка несовершенна, ведь ключи — это числа, а не латунь. Запомнить стоит направление каждой операции.
Их всё время путают, так что не смешивайте их.
Шифрование на публичный ключ даёт любому, у кого есть ваш публичный ключ, создать сообщение, которое откроет только ваш приватный ключ. Цель — конфиденциальность. Написать вам может кто угодно, а прочитать написанное можете только вы.
Подпись — это значение, которое владелец приватного ключа вычисляет над данными. Проверить его может любой, у кого есть публичный ключ. Цель — подлинность и целостность: данные пришли от владельца ключа, и после подписания их никто не менял. Подпись ничего не скрывает. Подписанный файл остаётся полностью читаемым для всех, у кого он есть.
Ed25519, алгоритм из этой лабораторной, умеет только подписывать. Зашифровать сообщение он не может. Для шифрования с открытым ключом служат другие конструкции, например RSA-OAEP или согласование ключа на эллиптических кривых с последующим симметричным шифром. Поэтому «публичный ключ» обозначает вид ключа и не привязан к одной задаче. Сначала разберитесь с задачей, потом с алгоритмом.
TLS решает обе задачи. В рукопожатии TLS 1.3 сервер подписывает данные приватным ключом, который соответствует публичному ключу в его сертификате, и этим показывает, что контролирует ключ. Отдельное согласование ключей затем выводит симметричные ключи, которыми шифруется сессия. В SSH такое же разделение. Ключ хоста подписывает часть рукопожатия, а ключ пользователя подписывает запрос аутентификации. Когда две задачи разделены, протоколы читаются легче.
Приватный ключ и есть доказательство. Кто им владеет, тот может создавать подписи, которые проходят проверку от имени этой личности, и расшифровывать сообщения, зашифрованные на этот ключ. Проверяющий не видит вашего лица или имени. Он сверяет подпись с публичным ключом, которому доверяет. Если кто-то скопирует ваш приватный ключ, каждый проверяющий, доверяющий вашему публичному ключу, примет копию за вас.
Отсюда четыре практических правила:
Раздавать публичный ключ безопасно по замыслу. Сложнее обратная задача. Проверяющий должен знать, что публичный ключ действительно принадлежит тому человеку или проекту, чьё имя на нём указано. Для этого и существуют удостоверяющие центры, закреплённые ключи релизов и отпечатки ключей SSH-хостов. Действительная подпись доказывает, что владелец ключа подписал эти байты. Того, что владелец ключа — именно тот, кого вы ожидали, она не доказывает.
Для подписанного релиза последовательность такая:
Две детали важнее, чем кажутся. Во-первых, подпись покрывает точные байты. Один лишний пробел, другой конец строки или переупакованный архив — это уже другое сообщение, и проверка провалится. Во-вторых, подписание 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, а это меняет подписываемые байты. Запускайте лабораторную в пустой папке на своей машине. К серверам она не обращается.
openssl genpkey -algorithm ed25519 -out priv.pem
openssl pkey -in priv.pem -pubout -out pub.pemПри успехе обе команды ничего не выводят. Проверьте результат:
ls -l priv.pem pub.pemНа нашей машине priv.pem занял 122 байта, а pub.pem 116 байт. Откройте pub.pem, и вы увидите блок -----BEGIN PUBLIC KEY-----. Не выводите и не вставляйте никуда priv.pem. В этой лабораторной это секретная половина личности, а привычка никогда его не показывать понадобится и с SSH-ключами.
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 байта.
openssl pkeyutl -verify -pubin -inkey pub.pem -rawin -in msg.txt -sigfile msg.sigВывод:
Signature Verified SuccessfullyФлаг -pubin говорит, что в файле ключа только публичная половина. Проверяющему больше ничего не нужно, поэтому такую проверку можно выполнить на другой машине, где priv.pem никогда не было.
echo 'release v1.1' > msg.txt
openssl pkeyutl -verify -pubin -inkey pub.pem -rawin -in msg.txt -sigfile msg.sigВывод:
Signature Verification FailureКоманда завершается со статусом 1. Подпись в msg.sig сделана над release v1.0, поэтому за release v1.1 она поручиться не может. OpenSSL не говорит, что изменилось. Он сообщает только, что байты и подпись расходятся. Проверяющий должен считать такой результат жёсткой остановкой, а не предупреждением, которое можно пропустить кликом.
Эти проверки показывают, что именно покрывает подпись. Каждую мы выполнили на той же машине, в той же папке.
Двойное подписание одного файла даёт одинаковые байты:
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"Вывод:
identicalФайл без завершающего перевода строки не проходит проверку. echo добавляет перевод строки, поэтому этот файл на один байт короче:
printf 'release v1.0' > nonl.txt
openssl pkeyutl -verify -pubin -inkey pub.pem -rawin -in nonl.txt -sigfile a.sigВывод:
Signature Verification Failuremsg.txt занимает 13 байт, а nonl.txt 12 байт. Подпись сделана над версией в 13 байт, поэтому более короткий файл проверку не проходит.
Другая пара ключей тоже не проходит проверку, даже на исходном файле:
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Вывод:
Signature Verification FailureИ файл, и подпись правильные. Публичный ключ принадлежит другой паре, поэтому проверка проваливается.
Следующая статья, Что такое SSH и как работают SSH-ключи?, применяет ту же идею подписи ко входу в систему. Ваши файлы id_ed25519, если они есть, — это пара именно такого рода. Прочитайте и статью о хешировании паролей: пароли и ключи решают разные части аутентификации, и их полезно не смешивать.
Чтобы разобраться глубже, прочитайте RFC 8032: там видно, как устроены подписи Ed25519 и почему схема детерминирована. Прочитайте FIPS 186-5, чтобы увидеть, какое место EdDSA занимает среди одобренных алгоритмов подписи. Прочитайте man-страницы OpenSSL genpkey и pkeyutl, чтобы увидеть другие опции, например выбор дайджеста для алгоритмов, которым он нужен. Затем проверьте релиз проекта, которым уже пользуетесь, с ключом, опубликованным на его собственном сайте. Запускайте только файлы, скачанные из официального источника, и только после проверки.
Стандарты и man-страницы выше — основные источники этой статьи:
openssl-genpkeyopenssl-pkeyutlКриптография
Разделяем кодирование, шифрование и хеширование на лабораторных с Base64, SHA-256 и OpenSSL. Почему Base64 ничего не защищает, а хеши необратимы.
Криптография
Почему быстрые хеши не годятся для паролей, что делают соль и коэффициент нагрузки и как хешировать и проверять пароли с Argon2id в Python через argon2-cffi.
Криптография
Криптография с нуля: шифрование, ключи, хеши, подписи и почему шифр Цезаря учит идеям, но ничего не защищает.