1. Постановка завдання
Є користувачі А і Б, які обмінюються повідомленнями. Вони можуть використовувати протокол TCP, UDP, електронну пошту або чат - зараз це не має значення. Є зловмисник Е, який може перехоплювати, підміняти і переставляти ці повідомлення, змушувати їх безслідно зникнути, а також надсилати повідомлення від імені А і Б. Завдання - допомогти А і Б обмінюватися повідомленнями таким чином, щоб Е не міг дізнатися їх вмісту. Позбутися від Е ніяк не можна.
2. Шифрування даних
В якості алгоритму шифрування Шнайер і Фергюсон рекомендують використовувати AES (Rijndael з розміром блоку 128 біт) з довжиною ключа 256 біт в режимі CTR. В цьому режимі максимальний розмір одного повідомлення становить 16 · 2 32 байт. Навряд чи когось гнобити це обмеження. В крайньому випадку можна розбити повідомлення на частини.
Для шифрування повідомлення з номером i, що має розмір L біт, необхідно обчислити L біт наступним способом:
де функція EK (128 біт на виході) - алгоритм шифрування, K (256 біт) - ключ шифрування, аргумент функції (128 біт) - шіфруемие дані, а оператор || означає конкатенацію даних. Після обчислення бітів b1 ... bL складаємо їх по Жегалкина (виключає АБО, операція XOR, ⊕) з бітами повідомлення, в результаті отримуємо зашифрований текст. Для розшифровки потрібно виконати точно таку ж послідовність дій над шіфротекста.
Тут важливо звернути увагу на два моменти. По-перше, ключі шифрування при посилці даних від А до Б і від Б до А повинні бути різними. По-друге, необхідно узгодити сукупність електронних даних в аргументі функції EK. Порядок байт в 32-х розрядних числах може змінюватися в залежності від архітектури використовуваного процесора. Традиційно в мережевих протоколах і форматах файлів використовується порядок байт від старшого до молодшого.
3. Функція хешування
Припустимо, ми успішно встановили сеансовий ключ за допомогою алгоритму Діффі-Хеллмана. в результаті А і Б отримали загальний ключ К. Потім проводиться наступна послідовність дій:
// Позбавляємося від алгебраїчної структури ключа
K = SHAd -256 (K)
// Будуємо 4 дочірніх ключа:
// 1. Шифрування від А до Б
key_send_enc = SHAd -256 (K || «Enc from A to B»)
// 2. Шифрування від Б до А
key_recv_enc = SHAd -256 (K || «Enc from B to A»)
// 3. Аутентифікація від А до Б
key_send_auth = SHAd -256 (K || «Auth from A to B»)
// 4. Аутентификация від Б до А
key_recv_auth = SHAd -256 (K || «Auth from B to A»)
4. Коди аутентифікації повідомлень
Кожне повідомлення має супроводжуватися імітовставки (MAC). В якості опції MAC Шнайер і Фергюсон рекомендують використовувати HMAC-SHA-256:
Тут ai - код аутентифікації i-го повідомлення mi. HMACK (m) являє собою хеш-функцію, значення якої залежить не тільки від повідомлення m, а й від ключа K. Як і у випадку з шифруванням, в MAC має використовуватися два незалежних ключа для А і Б. Як ми вже відзначали, ⊕ - це операція побітового виключає АБО (вона ж XOR).
У «Практичної криптографії» робиться особливий акцент на тому, як важливо проводити аутентифікацію не тільки повідомлення mi. що є звичайною послідовністю байт, а й його сенсу. Нехай mi = a || b || c, тобто складається з декількох полів певної довжини. Уявімо, що після чергового оновлення протоколу розміри полів змінилося. Тоді, якщо зловмисник Е зможе підмінити версію протоколу, повідомлення буде інтерпретовано невірно. Притому неважливо як зловмисникові вдасться це зробити - безпека однієї частини системи не повинна залежати від безпеки інших.
Ось для чого потрібна додаткова інформація xi. що включає в себе id протоколу (сигнатуру, IP: порт одержувача і відправника), версію протоколу, id повідомлення, розмір і імена полів повідомлення. Притому інформація xi повинна бути закодована таким чином, щоб її можна було однозначно декодувати. L (xi) в нашій формулі - це, очевидно, довжина xi. На щастя, всього цього гемора можна уникнути, просто передаючи повідомлення в XML-подібному форматі даних, тобто такому, де додаткова інформація про сенс повідомлення вже включена в повідомлення електронної пошти. Тут важливо не забути включити в кожне повідомлення всю додаткову інформацію, перераховану вище.
Що ще варто знати про MAC?
- Можна обрізати значення HMAC-SHA-256 до 16-ї байт. Робити це не бажано, але таке рішення краще, ніж використання HMAC-MD5 з метою зменшити обсяг переданих даних.
- Рекомендується спочатку обчислювати MAC, а потім виробляти шифрування повідомлення разом з MAC. а не навпаки.
5. Опис обміну повідомленнями
Будемо вважати, що користувачі А і Б вже справили узгодження сеансового ключа і вирахували дочірні ключі, як це було описано в пункті 3.
Важливо! Для кожного нового сеансу зв'язку необхідно генерувати нові ключі шифрування і аутентифікації.
Угода щодо ключів заслуговує на написання окремого поста, якщо не двох, тому зараз ми його не розглядаємо.
Тепер А і Б обмінюються повідомленнями в форматі, подібному XML (або дотримуються інструкцій, які передбачені для іншої ситуації, см пункт 4). Кожному повідомленню присвоюється унікальний 32-х бітний номер. Нумерація повідомлень починається з одиниці - так простіше відстежити момент, коли номери закінчаться. Коли це станеться, необхідно заново провести узгодження ключів. Можна використовувати і 64-х бітні номери повідомлень, але тоді кожне повідомлення стане на 4 байта довше. До того ж, час від часу слід проводити оновлення ключів, так що 32 біта - в самий раз.
Якщо для передачі даних використовується протокол UDP, слід проводити повторне посилку повідомлення по деякому таймаут в разі, якщо інша сторона на нього не реагує. При використанні TCP цього не потрібно - транспортний рівень подбати про гарантовану поставку пакетів. Зловмисник Е може порушити сеанс зв'язку між А і Б, зіпсувавши одне з повідомлень або переставивши пару повідомлень місцями. Але раз у нас є «людина посередині», то він в будь-якому випадку може порушити зв'язок між А і Б. Звідси висновок:
Не варто витрачати час на написання «свого TCP» поверх іншого протоколу - просто використовуйте протокол TCP. Якщо, звичайно, у вашому додатку це можливо.
Коли користувач А хоче послати повідомлення mi. він обчислює MAC цього повідомлення ai згідно з пунктом 4. Потім він шифрує повідомлення mi || ai. як це описано в пункті 2 і посилає користувачеві Б наступне: i || m'i || a'i. Після цього А збільшує значення лічильника відправлених повідомлень i на одиницю.
Коли користувач Б отримує i || m'i || a'i. він розшифровує mi і ai. перевіряє код аутентифікації. Повідомлення з невірним MAC ігноруються (їх міг відправити зловмисник Е). Потім проводиться перевірка значення i - якщо воно менше очікуваного номера повідомлення, для визначеності назвемо його j, повідомлення відкидається. В іншому випадку (i> = j) присвоюємо j значення i + 1 і обробляємо повідомлення mi.
Тут є кілька моментів, на які варто загострити увагу. По-перше, як вже зазначалося в пункті 2, номера повідомлень слід передавати в порядку байт від старшого до молодшого. По-друге, перевірку номера повідомлення можна було б проводити і до розшифровки. Але в цьому випадку, якщо повідомлення з невірним номером пошле зловмисник Е, буде виявлена (а потім записана в логи) помилка «невірний номер повідомлення», коли насправді сталася помилка «невірний код аутентифікації повідомлення». Криптографи в першу чергу турбуються про безпеку та коректній роботі додатків, а не про продуктивність. Деяким програмістам є чому у них повчитися.
На жаль, в разі активного втручання зловмисника Е в передачу даних, користувач Б може отримати лише підмножина повідомлень, відправлених А. Якщо виявлена пропажа повідомлень, то поведінка користувачів має залежати від типу інформації, якою вони обмінюються. В одних випадках пропажа частини повідомлень може бути не критичною, в іншій - вимагати припинення сеансу зв'язку.
6. Висновок
Дуже багато моментів не були розглянуті в цій статті. Як А і Б узгодять сеансові ключі зв'язку? Звідки під час узгодження ключів користувачеві А відомо, що він спілкується з Б, якщо за умовами задачі Е може видавати себе за кого завгодно?
Не менш цікаві питання - як правильно генерувати псевдовипадкові числа? Як протидіяти таймінг-атак? Що робити, якщо операційна система помістить запущене застосування зі всіма ключами в файл підкачки? Як безслідно видалити файл із застарілою парою RSA-ключів? Зловмисник Е хоч і не знає вмісту повідомлень, але знає їх розмір і час відправлення - небезпечно це і як цьому протистояти?