Криптография в целом — это большая область знаний. И хотя блокчейн всегда идет с ней рука об руку, в реальных проектах на базе распределенных реестров используется лишь некоторые из достижений криптографии. В этом посте я постараюсь рассказать простым языком, что они собой представляют и как работают в рамках нашей блокчейн-платформы «Конфидент». Начнем с базовых понятий, связанных с обменом зашифрованными данными. Как подсказывает название, при работе через симметричный ключ для шифрования и дешифровки используют один и тот же ключ. Предположим, у нас есть исходный текст (plaintext). Неким образом мы объединяем его с ключом и получаем зашифрованный текст (cyphertext). Отправляем зашифрованный текст получателю, и с помощью того же ключа он может расшифровать его и снова получить исходный текст. Для сравнения зашифрованного и оригинального текста используется операцию xor (исключающее «или»).  На основе симметричного шифра построена единственная в мире криптографическая система с математически доказанной криптостойкостью — одноразовый блокнот (шифр Вернама). Для работы через этот шифр у вас должен быть ключ, длина которого должна быть не меньше длины передаваемого сообщения. Проблема одноразового блокнота в том, что ключ такой большой длины вам всё равно нужно передать получателю по защищенному каналу. И если у вас есть защищенный канал с достаточной для этого пропускной способностью, то зачем тогда вообще что-то шифровать? Так что хоть симметричный ключ и используются, с реализацией есть принципиальные сложности. С симметричным ключом можно работать в двух режимах. Базовый режим, поточный, я описал выше. В нем при шифровании мы солим (соединяем) байты plaintext и ключа, в результате чего получается ciphertext. Но если ключ имеет недостаточную длину, то рано или поздно разные куски plaintext мы будем солить с одним и тем же куском ключа. Если это делать самым простым образом, то можно создать уязвимость, например, для частотных атак. Частотные атаки опираются на то, что в натуральном языке некоторые буквы и слова встречаются чаще, чем другие. Если plaintext — наше сообщение — не случайно, то в ciphertext возникнут определенные частотные паттерны, которые можно сопоставить, чтобы восстановить шифр. Поэтому большее распространение получил блочный режим. В нем мы не просто шифруем данные, а добиваем их до некоей фиксированной длины — длины блока — как прописано в спецификации шифра. Сегодня самым популярным блочным шифром является AES (Advanced Encryption Standard) с длиной блока в 256 символов. Блочный режим можно применять в нескольких вариантах. Самый простой — это ECB, Electronic Code Book. В нем мы разбиваем plaintext на блоки, при необходимости доводим их отступами до нужной длины и независимо шифруем при помощи симметричного ключа. Теоретически алгоритм надежен, но на самом деле нет. И вот почему: Это результат шифрования некоей картинки с помощью блочного шифра. Как думаете, что на ней было изображено? Явно угадывается всем известный пингвин — как раз из-за наличия частотных паттернов. По этой причине в AES по умолчанию установлен режим CBC, Cipher Block Chaining Mode. К привычному блокчейну он никакого отношения не имеет. От ECB здесь есть два отличия. В начале шифрования в режиме CBC задается случайный инициализирующий вектор. А каждый последующий блок при шифровании «солят» с результатом предыдущего. Таким образом, паттерны пропадают и шифр становится гораздо более криптостойким. В результате такого шифрования изображение пингвина превратится в более-менее равномерный шум. Главное преимущество симметричных шифров в том, что ими можно очень быстро кодировать произвольные объемы информации — хоть видео шифровать. Поэтому в работе с данными внутри защищенных систем применяются в основном симметричные ключи. А в кейсах, предъявляющих больше требований к безопасности, используются уже асимметричные ключи. Вся публичная криптография основана на асимметричных ключах — ключевой паре из приватного и публичного ключа. Разница в том, что из приватного ключа получить публичный можно, а из публичного приватный — нельзя. Достигается это разными способами: например, в криптографии на эллиптических кривых по определенному алгоритму происходит умножение на эллиптическую кривую. Второй принцип: то, что может быть зашифровано публичным ключом, может быть расшифровано приватным и наоборот. Это позволяет нам использовать ключевую пару в разных режимах. Когда мы защищаем данные с помощью открытого ключа, то лишь ограниченный круг лиц с закрытым ключом может их расшифровать. В электронной же подписи наоборот: данные шифруются закрытым ключом и любой обладатель открытого ключа может убедиться, что для шифрования был использован именно требуемый закрытый ключ. Хеш-функция — это однонаправленная функция, которая принимает набор данных произвольного размера, а после обработки выдает набор данных фиксированного размера. Новый набор в идеале должен быть неотличим от случайного числа, и восстановление первичных данных (прообраза) по нему должно быть крайне затруднительно, практически невозможно. Также хорошую хеш-функцию отличает то, что подобрать для нее два разных сообщения с одним и тем же итоговым хешем практически невозможно. Среди хеш-функций выделяют слабые, по которым можно подобрать прообраз, и сильные, где прообраз подобрать нельзя. К первым относятся, например, md5, sha1, ко вторым — sha2 (sha256, sha384, sha512) и sha3. Также выделяют медленные хеш-функции, такие как, например, scrypt. Они используются для шифрования паролей пользователей и других секретов. Медленные хеш-функции реализованы так, что их исполнение невозможно распараллелить или оптимизировать. Они должны выполняться в одном потоке, и даже на мощных конфигурациях расчет хеша пароля с scrypt занимает 500–1000 мс. Такая медлительность сильно усложняет атаку перебором, и для нее требуется большой объем памяти со случайным доступом. Поэтому для паролей живых пользователей, где пространство перебора сравнительно невелико, имеет смысл использовать максимально медленные сильные функции, чтобы усложнить взломщикам работу — тот же scrypt. А для баз данных более оптимальным выбором будет, например, sha3. Общий принцип работы электронных подписей довольно прост. У нас есть данные. Мы считаем их хеш и добавляем к хешу дополнительную информацию — например, время подписания. Затем шифруем всю полученную информацию с помощью приватного ключа. Для унификации электронных подписей введены определенные стандарты, которые определяют порядок упаковки подписываемых сообщений. Но при этом не определяют требуемые криптографические алгоритмы.  Первым стандартом электронной подписи стал CMS (Cryptographic Message Syntax). Он основан на синтаксисе PKCS 7, который помимо электронных подписей описывает много чего еще: контейнеры для сертификатов, подписанных данных, отозванных сертификатов (CRL) и не только. Согласно стандарту PKCS 7, подписи могут храниться в форматах DER (бинарный) или PEM (бинарный, закодированный в Base64). Зачем вообще здесь нужны стандарты? Представим, что для криптографии мы берем алгоритм sha-256. И какой-нибудь другой алгоритм для подписи — скажем, 512-, а не 256-битный. После шифрования мы отправляем подписанный документ получателю на проверку и подпись. Для него наше сообщение будет выглядеть как хаотичный набор байтов. Чтобы проверить нашу подпись, ему нужно произвести ряд операций: самостоятельно посчитать хеш документа, расшифровать нашу подпись открытым ключом и сравнить полученные данные. Для этого нужно знать, какие алгоритмы использовали мы. Но информацию об этом нельзя добавить в саму подпись; эти данные нужно добавить рядом. Для описания, как их добавлять, и нужны стандарты. Упомянутый выше CMS обновили последний раз в 1998 году, и в 2000-х ему на смену пришел стандарт CAdES, именуемый в России «усовершенствованной электронной подписью». CAdES предусматривает целое семейство форматов с разным объемом информации, которые входят друг в друга подобно матрешке. Стандартный формат, используемый для подписи 80% документов — это базовый CAdES-BES (Basic Electronic Signature). Стандарты CAdES-BES определяет обязательные и необязательные атрибуты подписи. Среди обязательных — content-type (тип содержимого), message-digest (хеш сообщения) и ESS signing certificate (указание, каким сертификатом его подписали). По умолчанию для вычисления хеш-суммы подписываемых данных используется sha-1, но в дополнительном атрибуте можно указать и другой алгоритм. Вообще, знать о дополнительных атрибутах полезно — это позволяет сильно расширить возможности электронных подписей. Например, с помощью signer-attributes вы можете приложить прямо в подпись ID машиночитаемой доверенности. Тогда получатель сможет получить нужную подпись из реестра МЧД и проверить полномочия. Мы используем это в проекте единого реестра машиночитаемых доверенностей, который запустили на базе ЦПРР ФНС России. Также в необязательных атрибутах можно указать точное время подписи. Часто электронную подпись пытаются использовать для проверки именно того, когда был подписан документ. Но в CAdES-BES эта метка времени не является доверенной, поскольку ее может изменить отправитель. Здесь матрешка продолжает обрастать новыми слоями. Следующий, более сложный стандарт — CAdES-EPES. Он не особо популярен, в нем лишь вводится понятие регламента подписи — набор правил трансформации документа, используемый для проверки подписи. А на стандарте CAdES-T мы остановимся подробней; он уже достаточно распространен. В CAdES-T появляется метка доверенного времени. Представим, что у нас есть сертификат, который некогда был валиден, а сейчас отозван. Чтобы понять, был ли сертификат валиден на момент подписания, используется TSA — служба штампов времени. Как она работает? Сначала мы соединяем документ, подписываемые атрибуты, добавляем подпись и получаем готовый контейнер CAdES-BES или CAdES-EPES. Зачем считаем хеш и отправляем его в TSA. В ответ получаем подписанный хеш со штампом времени и расширяем им наш контейнер. Но возникает вопрос: как убедиться, что на момент подписания документа у подписанта был валидный сертификат TSA?  Переходим к следующему слою — CAdES-C. Он включает набор проверочных данных — неподписываемые атрибуты complete-certificate-references и complete-revocation-references. В них содержатся ссылки на все сертификаты, участвовавшие в подписи, и на все сертификаты, актуальные на момент подписания. Так мы получаем контекст на момент подписания и можем провести релевантную проверку актуальности. А что, если ссылки протухнут, в сервер попадет метеорит или случится что-нибудь еще? Переходим к CAdES-X — самому сложному формату, к долгосрочной расширенной подписи. Существует аж четыре вида CAdES-X. В CAdES-X Long вместо ссылок просто загружают все списки отозванных сертификатов (CRL) и другие необходимые сертификаты. CAdES-X Long не зависит от доступности Сети. Не нужно ни соединения с удостоверяющим центром, ни проверки OCSP (о ней чуть ниже), ни списков отозванных сертификатов, ни штампов времени. Все списки включаются в саму подпись, и поэтому она занимает несколько мегабайт. Доверять такой подписи можно даже «в вакууме», поскольку все списки отзывов (CRL) всегда включают в себя подпись УЦ. Следующий вид CAdES-X — CAdES-X Type 1. Здесь на исходный контейнер CAdES-C добавляется штамп времени сборки сертификата. Есть и Type 2 с другим принципом подписи. Самая большая матрешка — это формат CAdES-A, архивный. Его идея в том, что со временем в криптографических алгоритмах могут появиться дефекты, которые позволят подменить подпись в CAdES-X Type 1 или Type 2, вследствие чего подпись станет недоверенной. Поэтому CAdES-A добавляет регулярные (например, ежегодные) штампы времени, подтверждающие, что контент не изменился. Да, CAdES-A надо регулярно переподписывать. Этот формат используется в самых ответственных случаях — например, в федеральном казначействе для бюджетов регионов. Теперь объединим все описанные понятия в одной концепции. Недавно наша платформа получила поддержку PKI (public key infrastructure, инфраструктура открытых ключей). С точки зрения законодательства это очень важная функция, необходимая для реализации проектов на государственном уровне. Если по-простому, то PKI позволяет участникам сети понять, доверяют они друг другу или нет. PKI состоит из следующих компонентов: Корневой УЦ (удостоверяющий центр) — он выдает сертификаты другим УЦ, и через него также декларируется доверие другим УЦ. У вас в локальном хранилище обязательно имеется сертификат какого-нибудь корневого УЦ. По умолчанию большинство браузеров распространяется с несколькими корневыми УЦ. Подчиненные УЦ Защищенное хранилище закрытого ключа (HSM, hardware security module) — это неизвлекаемое очень защищенное аппаратное хранилище, в котором находится ключ корневого УЦ. Утечка таких ключей уже случалась несколько раз в истории; это серьезно компрометирует безопасность всего интернета. Набор политик и регламентов Набор выданных сертификатов Набор отозванных сертификатов Что такое сертификат в данном контексте? Это электронный документ, содержащий метаданные по публичному ключу. Он используется, чтобы что-то подписать, зашифровать или аутентифицировать себя перед кем-то. Сертификаты создаются по стандарту X.509 и имеют следующие параметры: серийный номер, кто и когда выдан сертификат, срок действия, кому выдан сертификат, публичный ключ, для которого выдан сертификат, с указанием алгоритма шифрования, набор расширений (дополнительная метаинформация), указание алгоритма для подписи УЦ, подпись УЦ. Всю дополнительную информацию о сертификате легко посмотреть: Каждый сертификат промежуточного УЦ подписывается сертификатом вышестоящего УЦ, и так до корневого УЦ. С помощью PKI, доверяя только одному корневому УЦ, вы можете построить цепочку доверия до любого подчиненного УЦ.  Я не раз слышал заблуждение, что удостоверяющие центры требуют передачи закрытых ключей пользователя. Это противоречит базовым требованиям безопасности: закрытый ключ никогда не должен покидать локальный компьютер. Опишу, как работает выдача ключей в инфраструктуре PKI: Пользователь генерирует свой закрытый ключ и получает из него публичный ключ. Закрытый ключ откладывается в сторону. По публичному ключу создается CSR (Certificate Signing Request). Фактически это и есть сертификат, только без подписи. В нем пользователь прописывает все атрибуты сертификата. В ряде юрисдикций (например, в России) пользователь также должен лично прийти в удостоверяющий центр для идентификации личности, чтобы установить связь с сертификатом. УЦ подписывает ваш CSR и возвращает сертификат. В зависимости от политики УЦ он может изменить некоторые атрибуты — например, проставить свой срок действия сертификата. Как понять, что сертификату больше нельзя верить? Если его срок действия истек, это происходит автоматически. Но иногда нужно отозвать сертификат, валидный по времени, и для этого есть два способа — CRL и OCSP. CRL, не раз упомянутый выше, это certificate revocation list — список отозванных сертификатов. УЦ публикует эти списки на регулярной основе, в среднем раз в сутки. Список содержит название сертификата, причину его отзыва и подпись УЦ. В каждом сертификате обычно есть ссылка на CRL выпустившего его УЦ. Этот способ имеет свои недостатки. Может возникнуть временной лаг между утечкой сертификата и обновлением CRL. Частота запроса CRL нигде не регламентирована. У каждого CRL прописывается время жизни, и если у пользователя нет доступа к актуальному CRL, он использует устаревший CRL из кеша. Это тоже создает уязвимости. Поэтому есть и альтернативы. OCSP (online certificate status protocol) позволяет без передачи списка отозванных сертификатов спросить у УЦ, валиден ли конкретный сертификат, и получить подписанный ответ. Очевидны недостатки такого решения. Нужно постоянно делать в УЦ дополнительный запрос, а OCSP сервер может быть недоступен. Представьте, что сертификационный центр выдал сертификат Google. В ответ Google будет генерировать миллионы запросов в секунду — каждый раз, когда кто-нибудь делает поисковый запрос. Справится ли с такой нагрузкой центр? Едва ли. Другое уязвимое место — приватность. Представьте, что вы идете на сомнительный сайт, получаете там сертификат, а потом спрашиваете у российского национального УЦ, валиден ли он. На основе таких запросов OCSP получает доступ к вашей истории браузинга, и это может сформировать компромат. Так что есть и третье, гибридное решение — OCSP stapling. Оно основан на том, что у ответа OCSP есть ограниченный срок действия. Периодически сервер запрашивает у УЦ, валиден ли некоторый сертификат. Затем сохраняет ответ, и в ответ на авторизацию клиента посылает сертификат вместе с актуальным результатом проверки. OCSP stapling снижает нагрузку на OCSP-сервисы и решает часть проблем, но все-таки является костыльным решением. PKI нельзя назвать идеальной инфраструктурой. Первая проблема: за количеством выданных сертификатов сложно следить, ведь мы не можем контролировать, кому выдаст сертификаты нижестоящий УЦ. Вторая проблема: что делать при недоступности CRL/OCSP? Кое-как проблемы решаются с помощью расширений PKI; среди них можно выделить Certificate Transparency. В нем каждый УЦ ведет лог выданных сертификатов, который доступен и легко мониторится. Его применяют чаще в глобальном интернете, а не в частных сетях. TLS (transport layer security) защищает соединение между клиентом и сервером. Как и PKI, это необходимая функция для реализации проектов на федеральном уровне в России. В общем случае TLS нужен для авторизации сервера перед клиентом. Опционально поддерживается и авторизация клиента перед сервером. Также TLS устанавливает конфиденциальный обмен данными, обеспечивая одностороннюю или двустороннюю аутентификацию. До TLS существовал протокол SSL, который полностью взломан и поэтому признан устаревшим. У TLS вышло четыре версии — 1.0, 1.1, 1.2, 1.3 — и актуальны две последние. TLS использует симметричные и асимметричные ключи. Для установки зашифрованного канала данных используется протокол Диффи — Хеллмана (о нем чуть ниже), по которому с помощью общего ключа стороны создают общий сессионный ключ. А сессионный ключ используется непосредственно для шифрования данных. Аутентификация проводится с помощью сертификатов, конфиденциальность устанавливается с помощью симметричных ключей. Самое простое объяснение протокола Диффи — Хеллмана на эллиптических кривых выглядит так: Смысл протокола в том, что каждая из сторон может выработать одинаковый общий ключ с помощью «умножения» собственного приватного ключа на публичный ключ другой стороны. Имея такой общий секрет, мы можем с его помощью зашифровать некий симметричный ключ обмена как сессионный ключ. И с его помощью уже шифровать весь трафик. С теорией закончили. Теперь я расскажу, как мы применяем это в своей блокчейн-платформе «Конфидент». Она может работать в двух режимах — без PKI на основе только TLS (в деморежиме) или с включенным PKI. В первом случае функционально всё совпадает с тем, что описано в разделе TLS выше. При включенном же PKI становится обязательным наличие авторизации и подписи УЦ у публичных ключей клиента, которые используются при установке защищенного соединения. В этом случае проходит две проверки. Первая — поиск по списку отозванных сертификатов (CRL) УЦ. При отсутствии совпадений проводится вторая проверка. В зависимости от того, подключается ли к ноде node-client (P2P) или api-client (API), она ищет в соответствующем списке сертификат с требуемыми параметрам — OID и подписью. Если совпадения нет, запрос на подключение отклоняется. В результате подключиться к блокчейн-сети могут только заранее известные, авторизованные пользователи. Поэтому PKI имеет смысл использовать, скорее, в приватных сетях, то есть в бизнес-проектах на основе блокчейна. Если вам интересно узнать требования к PKI во всех подробностях, можно обратиться к ГОСТ Р ИСО/МЭК 9594-8-98, посвященному аутентификации. А чтобы узнать больше о реализации именно в нашей платформе, перейдите в документацию.