written on Sunday, June 1, 2014
Для начала напомню некоторые банальноести.
Шифрование бывает симметричное и асимметричное. Симметричное шифрование - это AES или GOST 28147-89. У нас есть заранее известный алгоритм, который описан стандартнами, есть данные, которые мы хотим зашифровать и есть ключ. Мы засовыеем данные и ключ в алгоритм и получаем зашифрованные данные.
Теперь, чтобы получить обратно расшифрованные данные, нам нужно засунуть их и тот же самый ключ обратно в алгоритм. То есть, при передаче данных от одного человека другому по шифрованному каналу, оба должны заранее знать ключ, которым они шифруются. Наивный подход заключается в том, что стороны обмениваются ключем непосредственно, после чего осуществляют передачу данных.
Проблема у такого подхода заключается в том, что одни и те же данные, зашифрованные одним и тем же ключем будут давать одинаковый шифротекст, а значит злоумышленник, сидящий на канале будет как минимум знать, что сообщение повторили. Намного хуже, если одним и тем же ключем шифруются похожие, но не одинаковые данные. Так можно спалить и данные и даже ключ.
Чтобы решить вторую проблему, используют KDF - key deriviation function. В данном случае стороны заранее договариваются об общем секрете, а уже из секрета по некой схеме получают ключи, различающиеся для разных сеансов обмена данными.
В качестве KDF может использоваться геш-функция. Например, у нас есть общий секрет Z, о котором мы заранее договорились. Мы берем этот секрет и случайную последовательность данных (соль) и засовываем на вход геш-функции: `HASH(Z + salt)`. На выходе получим свои 32 байта данных, которые уже будут использоваться в качестве ключа, которым мы шифруем данные. Теперь мы передаем другой стороне шифрованные данные и соль. На той стороне таким же самым образом из общего секрета и соли получает ключ, которым расшифровывают данные.
Следующая стадия - использование случайных ключей. Чтобы еще лучше защититься, мы не используем KDF и общий секрет для получения ключа шифрования данных (CEK). Вместо этого мы генерируем случайный ключ, шифруем данные им, после чего используем KDF и общий секрет, чтобы сгенерировать ключ шифрования ключа (KEK). Этоим ключем мы шифруем наш случайный ключ, которым зашифровали данные и передаем другой стороне шифротекст, зашифрованнй случайный ключ (wCEK) и соль для KDF.
Вот тут на арену выходит Діффі-Геллман и ассиметричное шифрование на эллиптичных кривых.
Теперь у нас нету общего секрета, но есть по паре ключей - приватному и публичному (privA, pubA). У другой стороны, которой мы отправляем данные, тоже есть пара ключей (privB, pubB) и мы имеем сертификат, в котором указан pubB.
В случае эллиптичных кривых, приватный ключ - это одно длинное число, а публичный - это координаты точки на кривой, которые мы генерируем, умножая базовую точку кривой (которая известна всем) на свой приватный ключ умноженный на единицу (pub = curve.base * priv * -1).
Дальше мы делаем совершенно тривиальный фокус - умножаем свой приватный ключ на чужой публичный. Фактически мы получаем результат умножения своего приватного ключа на чужой приватный ключ (privA * privB * curve.base * -1), хотя чужой приватный ключ не знаем. Принимающая сторона делает ровно то же самое - умножает свой приватный ключ на наш публичный. Результат конечно же совпадает, хотя мы предварительно не обменивались никакой секретной информацией. Чтобы нигде не закрылся злой мудак, все домноживаем на кофактор - например 4.
Тепер по уже накатанной схеме - координату X полученной точки мы используем, как общий секрет, из общего секрета и соли получаем KEK, ключем KEK врапаем рандомный ключ CEK, врапнутый ключ, зашифрованные им данные и соль отправляем получателю.
А чтобы передать эту кучу информации, мы ее пакуем в ASN.1 структуру, схема которой называется PKCS #7. Структура жирная, развесистая, включает вообще все - и шифротекст, и ключ, и соль, и сало, и параметры алгоритмов с идентификаторами и собственно сертификат, из которого взять публичный ключ.
Самое интересно, что я сходу очень быстро заимплементил все вышеописанное, но криво считал публичный ключ из сертификата, перепутав в одном месте плюс и xor, поэтому у меня очень долго все работало криво и выдавало "key unwrap failed".