written on Sunday, June 1, 2014
Для початку нагадаю деякі банальності.
Шифрування буває симетричне і асиметричне. Симетричне шифрування — це AES або ГОСТ 28147-89. У нас є заздалегідь відомий алгоритм, описаний стандартами, є дані, які ми хочемо зашифрувати, і є ключ. Ми засовуємо дані та ключ у алгоритм і отримуємо зашифровані дані.
Тепер, щоб отримати назад розшифровані дані, нам потрібно засунути їх і той самий ключ назад у алгоритм. Тобто, при передачі даних від однієї людини до іншої зашифрованим каналом, обидва мають заздалегідь знати ключ, яким вони шифруються. Наївний підхід — сторони обмінюються ключем напряму, після чого передають дані.
Проблема такого підходу в тому, що одні й ті самі дані, зашифровані одним і тим самим ключем, даватимуть однаковий шифротекст, а отже зловмисник, який сидить на каналі, як мінімум знатиме, що повідомлення повторили. Набагато гірше, якщо одним і тим самим ключем шифруються схожі, але не однакові дані. Так можна спалити і дані, і навіть ключ.
Щоб вирішити другу проблему, використовують KDF — key derivation 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".