- Зміст статті Дивись, як цікаво виходить: вже майже три роки надходження вільного хакера на государеву...
- WARNING
- INFO
- трохи теорії
- Базові функції стандарту
- Додавання двох довічних векторів по модулю 2
- Нелінійне биективное перетворення (перетворення S)
- Зворотне нелінійне биективное перетворення (зворотне перетворення S)
- Лінійне перетворення (перетворення L)
- Зворотне лінійне перетворення (зворотне перетворення L)
- Продовження доступно тільки учасникам
- Варіант 2. Відкрий один матеріал
Зміст статті
Дивись, як цікаво виходить: вже майже три роки надходження вільного хакера на государеву службу не вважається в очах колег по IT-ремеслу зашкваром. Те, що раніше здавалося принаймні дивним, зараз абсолютно нормально і виглядає до певної міри патріотично - на плечах і за спинами наших з тобою колег виразно проглядаються погони, червоні папки, колючий дріт та інші символи державності. Загалом, якщо слова «імпортозаміщення» і «цифровий суверенітет» не викликають у тебе легкого троллфейса, а ГОСТ ти вважаєш хорошим криптографічним алгоритмом (що, до речі, правда), то ця стаття тобі підійде точно.
Чотири слона вітчизняної криптографії
В даний час вітчизняна криптографія базується на кількох основних державних стандартах:
- ГОСТ 34.10. Інформаційна технологія. Криптографічний захист інформації. Процеси формування та перевірки електронного цифрового підпису (діюча на сьогодні редакція 2012 року);
- ГОСТ 34.11. Інформаційна технологія. Криптографічний захист інформації. Функція хешування (діюча на сьогодні редакція 2012 року);
- ГОСТ 34.12. Інформаційна технологія. Криптографічний захист інформації. Блокові шифри (діюча на сьогодні редакція 2015 року);
- ГОСТ 34.13. Інформаційна технологія. Криптографічний захист інформації. Режими роботи блокових шифрів (діюча на сьогодні редакція 2015 року).
Основні вітчизняні криптографічні ГОСТи
З ГОСТ 34.11-2012 ми вже познайомилися в минулій статті циклу. Це, якщо пам'ятаєш, був алгоритм обчислення хеш-суми під назвою «Стрибог». Сьогодні ми продовжимо розглядати вітчизняну криптографію, і на черзі у нас алгоритм блочного шифрування під назвою «Коник», який описаний в ГОСТ 34.12-2015.
Як і належить, даний ГОСТ розроблений в наукових надрах російських спецслужб і близько до них стоять організацій, а якщо говорити конкретніше, то це Центр захисту інформації та спеціального зв'язку ФСБ Росії і відкрите акціонерне товариство «Інформаційні технології та комунікаційні системи». Документ набув чинності з 1 січня 2016 року, і всі засоби шифрування, офіційно використовуються в різних структурах, які працюють з інформацією обмеженого доступу (держтаємниця, службова таємниця та інше), повинні йому відповідати.
У стандарті описуються два різновиди блочного шифру: «Коник» з довжиною блоку 128 біт і «Магма» з довжиною блоку 64 біта.
Алгоритм «Коник» більш сучасний і теоретично більш стійкий, ніж алгоритм «Магма» (який, по суті, практично без змін був узятий з старого ГОСТ 28147-89), і тому сьогодні ми розглянемо саме його. Що стосується «Магми», то про нього - як-небудь іншим разом в наступній статті циклу.
Як вже було сказано, довжина шифруемого блоку в алгоритмі «Коник» - 128 біт. Довжина ключа шифрування - 256 біт.
WARNING
При читанні ГОСТу врахуй, що в усіх 16-байтових масивах тестових послідовностей нульовий байт знаходиться в кінці масиву, а п'ятнадцятий, відповідно, на початку (якщо ти уважно читав статтю про «Стрибог», то ця особливість наших криптостандарта тобі повинна бути знайома).
INFO
Одна з головних легенд російської криптографії говорить, що назва алгоритму «Коник» ніякого відношення до зеленого комасі не має, а є скорочення від прізвищ творців алгоритму - Кузнєцова і Нечаєва. Швидше за все, це дійсно так, хоча ніде офіційного підтвердження цьому я знайти не зміг.
трохи теорії
Основу алгоритму становить не мережу Фейстеля, як в більшості блокових шифрів, а так звана SP - Substitution-Permutation network, або, по-російськи, підстановлювальний-перестановочность мережу. Шифр на основі SP-мережі отримує на вхід блок і ключ і робить кілька чергуються раундів, що складаються з стадій підстановки і стадій перестановки. У «коник» кожен раунд включає в себе лінійне і нелінійне перетворення плюс операцію накладення так званого итерационного ключа. Всього таких раундів дев'ять і один останній неповний раунд, в якому виконується тільки накладення останнього (десятого) итерационного ключа.
Схема роботи алгоритму при зашифрованими і при розшифрування
Ітераційні (або раундові) ключі виходять шляхом певних перетворень на основі майстер-ключа, довжина якого, як ми вже знаємо, становить 256 біт. Цей процес починається з розбиття майстер-ключа навпіл, так виходить перша пара раундовий ключів. Для генерації кожної наступної пари раундовий ключів застосовується вісім ітерацій мережі Фейстеля, в кожній ітерації використовується константа, яка обчислюється шляхом застосування лінійного перетворення алгоритму до значення номера ітерації.
Схема отримання ітераційних (раундовий) ключів
Отже, після короткого і невеликого занурення в теорію починаємо кодіть ...
Базові функції стандарту
Оскільки в алгоритмі використовуються 128-бітові блоки (у вигляді так званих довічних векторів), для початку визначимо цей самий блок:
#define BLOCK_SIZE 16 // Розмір блоку 16 байт (або 128 біт) ... typedef uint8_t vect [BLOCK_SIZE]; // Визначаємо тип vect як 16-байтовий масив
Додавання двох довічних векторів по модулю 2
Ця функція в стандарті визначена як X-перетворення. Кожен байт першого вектора КСОР з відповідним байтом другого вектора, і результат пишеться в третій (вихідний) вектор:
static void GOST_Kuz_X (const uint8_t * a, const uint8_t * b, uint8_t * c) {int i; for (i = 0; i <BLOCK_SIZE; i ++) c [i] = a [i] ^ b [i]; }
Нелінійне биективное перетворення (перетворення S)
Це перетворення в нашому випадку повторює S-перетворення з алгоритму «Стрибог» ГОСТ 34.11-2012. Масив S-перетворень теж аналогічний ГОСТ 34.11-2012:
static const unsigned char Pi [256] = {0xFC, 0xEE, 0xDD, ... 0x63, 0xB6};
Тут для економії місця показані в повному обсязі значення, визначені в стандарті, а тільки три перших і два останніх. Коли будеш писати код, не забудь про інші значення і про те, що в стандарті вони записані в десятковому вигляді.
Код самої функції перетворення S виходить такий:
static void GOST_Kuz_S (const uint8_t * in_data, uint8_t * out_data) {int i; for (i = 0; i <BLOCK_SIZE; i ++) out_data [i] = Pi [in_data [i]]; } перетворення S
Зворотне нелінійне биективное перетворення (зворотне перетворення S)
Оскільки нам потрібно не тільки зашифровувати повідомлення, але і розшифровувати теж, то кожному перетворенню зашифрования необхідно ставити у відповідність зворотне перетворення для розшифрування. Сама функція зворотного S-перетворення виглядає практично так само, як і пряме S-перетворення:
static void GOST_Kuz_reverse_S (const uint8_t * in_data, uint8_t * out_data) {int i; for (i = 0; i <BLOCK_SIZE; i ++) out_data [i] = reverse_Pi [in_data [i]]; }
Відмінність тільки в масиві перетворення, зворотне до масиву прямого перетворення (в стандарті цей масив не наводиться, тому напишемо тут його повністю):
static const unsigned char reverse_Pi [256] = {0xA5, 0x2D, 0x32, 0x8F, 0x0E, 0x30, 0x38, 0xC0, 0x54, 0xE6, 0x9E, 0x39, 0x55, 0x7E, 0x52, 0x91, 0x64, 0x03, 0x57, 0x5A, 0x1C, 0x60, 0x07, 0x18, 0x21, 0x72, 0xA8, 0xD1, 0x29, 0xC6, 0xA4, 0x3F, 0xE0, 0x27, 0x8D, 0x0C, 0x82, 0xEA, 0xAE, 0xB4, 0x9A, 0x63, 0x49, 0xE5, 0x42, 0xE4, 0x15, 0xB7, 0xC8, 0x06, 0x70, 0x9D, 0x41, 0x75, 0x19, 0xC9, 0xAA, 0xFC, 0x4D, 0xBF, 0x2A, 0x73, 0x84, 0xD5, 0xC3, 0xAF, 0x2B, 0x86, 0xA7, 0xB1, 0xB2, 0x5B, 0x46, 0xD3, 0x9F, 0xFD, 0xD4, 0x0F, 0x9C, 0x2F, 0x9B, 0x43, 0xEF, 0xD9, 0x79, 0xB6, 0x53, 0x7F, 0xC1, 0xF0, 0x23, 0xE7, 0x25, 0x5E, 0xB5, 0x1E, 0xA2, 0xDF, 0xA6, 0xFE, 0xAC, 0x22, 0xF9, 0xE2, 0x4A, 0xBC, 0x35, 0xCA, 0xEE, 0x78, 0x05, 0x6B, 0x51, 0xE1, 0x59, 0xA3, 0xF2, 0x71, 0x56, 0x11, 0x6A, 0x89, 0x94, 0x65, 0x8C, 0xBB, 0x77, 0x3C, 0x7B, 0x28, 0xAB, 0xD2, 0x31, 0xDE, 0xC4, 0x5F, 0xCC, 0xCF, 0x76, 0x2C, 0xB8, 0xD8, 0x2E, 0x36, 0xDB, 0x69, 0xB3, 0x14, 0x95, 0xBE, 0x62, 0xA1, 0x3B, 0x16, 0x66, 0xE9, 0x5C, 0x6C, 0x6D, 0xAD, 0x37, 0x61, 0x4B, 0xB9, 0xE3, 0xBA, 0xF1, 0xA0, 0x85, 0x83, 0xDA, 0x47, 0xC5, 0xB0, 0x33, 0xFA, 0x96, 0x6F, 0x6E, 0xC2, 0xF6, 0x50, 0xFF, 0x5D, 0xA9, 0x8E, 0x17, 0x1B, 0x97, 0x7D, 0xEC, 0x58, 0xF7, 0x1F, 0xFB, 0x7C, 0x09, 0x0D, 0x7A, 0x67, 0x45, 0x87, 0xDC, 0xE8, 0x4F, 0x1D, 0x4E, 0x04, 0xEB, 0xF8, 0xF3, 0x3E, 0x3D, 0xBD, 0x8A, 0x88, 0xDD, 0xCD, 0x0B, 0x13, 0x98, 0x02, 0x93, 0x80, 0x90, 0xD0, 0x24, 0x34, 0xCB, 0xED, 0xF4, 0xCE, 0x99, 0x10, 0x44, 0x40, 0x92, 0x3A, 0x01, 0x26, 0x12, 0x1A, 0x48, 0x68, 0xF5, 0x81, 0x8B, 0xC7, 0xD6, 0x20, 0x0A, 0x08, 0x00, 0x4C, 0xD7, 0x74};
Лінійне перетворення (перетворення L)
Для виконання даного перетворення необхідна функція множення чисел в кінцевому полі (або поле Галуа) над непріводімим полиномом x ^ 8 + x ^ 7 + x ^ 6 + x + 1. Це найскладніше місце для розуміння в даному стандарті (навіть Вікіпедія не надто допомагає ). Реалізується це таким чином:
static uint8_t GOST_Kuz_GF_mul (uint8_t a, uint8_t b) {uint8_t c = 0; uint8_t hi_bit; int i; for (i = 0; i <8; i ++) {if (b & 1) c ^ = a; hi_bit = a & 0x80; a << = 1; if (hi_bit) a ^ = 0xc3; // Полином x ^ 8 + x ^ 7 + x ^ 6 + x + 1 b >> = 1; } Return c; }
В цілому, якщо уважно подивитися на код, можна побачити, що це за великим рахунком множення в стовпчик з додаванням числа 0xс3, яке і являє потрібний нам поліном.
Далі, використовуючи наведену вище функцію, реалізуємо перетворення R, яке є частиною лінійного перетворення L. Перетворення R виконується з використанням лінійного регістра зсуву зі зворотним зв'язком. Кожен байт з блоку множиться за допомогою функції GOST_Kuz_GF_Mul на один з коефіцієнтів з ряду (148, 32, 133, 16, 194, 192, 1, 251, 1, 192, 194, 16, 133, 32, 148, 1) в залежності від порядкового номера байта. Байти складаються між собою за модулем 2, і всі 16 байт блоку зсуваються в бік молодшого розряду, а отримане число записується на місце ліченого байта.
Схема перетворення R
Для реалізації R-перетворення спочатку визначимо масив потрібних нам коефіцієнтів:
static const unsigned char l_vec [16] = {1, 148, 32, 133, 16, 194, 192, 1, 251, 1, 192, 194, 16, 133, 32, 148};
Далі пишемо саму функцію R-перетворення:
static void GOST_Kuz_R (uint8_t * state) {int i; uint8_t a_15 = 0; vect internal; for (i = 15; i> = 0; i--) {internal [i - 1] = state [i]; // Рухаємо байти в сторону молодшого розряду a_15 ^ = GOST_Kuz_GF_mul (state [i], l_vec [i] ); } // Пишемо в останній байт результат складання internal [15] = a_15; memcpy (state, internal, BLOCK_SIZE); }
Лінійне перетворення L утворюється зсувом регістра 16 разів, або шістнадцятикратний повторенням функції GOST_Kuz_R:
static void GOST_Kuz_L (const uint8_t * in_data, uint8_t * out_data) {int i; vect internal; memcpy (internal, in_data, BLOCK_SIZE); for (i = 0; i <16; i ++) GOST_Kuz_R (internal); memcpy (out_data, internal, BLOCK_SIZE); }
Зворотне лінійне перетворення (зворотне перетворення L)
Для того щоб можна було не тільки зашифровувати повідомлення, але і розшифровувати їх, нам необхідно зворотне лінійне перетворення.
Це запишеться наступним чином:
static void GOST_Kuz_reverse_L (const uint8_t * in_data, uint8_t * out_data) {int i; vect internal; memcpy (internal, in_data, BLOCK_SIZE); for (i = 0; i <16; i ++) GOST_Kuz_reverse_R (internal); memcpy (out_data, internal, BLOCK_SIZE); }
Як ти напевно здогадався, функція GOST_Kuz_reverse_R - це не що інше, як зворотне перетворення R. Виглядає це таким чином:
static void GOST_Kuz_reverse_R (uint8_t * state) {int i; uint8_t a_0; a_0 = state [15]; vect internal; for (i = 0; i <16; i ++) {internal [i] = state [i - 1]; // Рухаємо все на старі місця a_0 ^ = GOST_Kuz_GF_mul (internal [i], l_vec [i]); } Internal [0] = a_0; memcpy (state, internal, BLOCK_SIZE); }
Продовження доступно тільки учасникам
Варіант 1. Приєднайся до товариства «Xakep.ru», щоб читати всі матеріали на сайті
Членство в співтоваристві протягом зазначеного терміну відкриє тобі доступ до ВСІХ матеріалами «Хакера», збільшить особисту накопичувальну знижку і дозволить накопичувати професійний рейтинг Xakep Score! Детальніше
Варіант 2. Відкрий один матеріал
Зацікавила стаття, але немає можливості стати членом клубу «Xakep.ru»? Тоді цей варіант для тебе! Зверни увагу: цей спосіб підходить тільки для статей, опублікованих більше двох місяців тому.
Ru»?