#
LD31D
Як працює AES: покроковий розбір алгоритму та власна реалізація

Як працює AES: покроковий розбір алгоритму та власна реалізація

AES - це симетричний блоковий шифр, який шифрує дані блоками по 128 біт через кілька раундів підстав, перестановок і лінійних перетворень, забезпечуючи швидкий і стандартизований криптографічний захист даних.

AES з’явився в результаті відкритого конкурсу NIST у 1997 році, де в 2000 році переміг алгоритм Rijndael від Вінсента Реймена і Йоана Даймена, після чого в 2001 році він був затверджений як федеральний стандарт шифрування США і став світовим стандартом симетричного шифрування.

Як працює AES

AES використовує блоки по 16 байтів та ключ.

Ключ та дані (які називають State) можна зобразити за допомогою матриці 4x4:

b0b4b8b12b1b5b9b13b2b6b10b14b3b7b11b15\def\arraystretch{1.5} \begin{array}{c:c:c:c} b_\text{0} & b_\text{4} & b_\text{8} & b_\text{12}\\ \hdashline b_\text{1} & b_\text{5} & b_\text{9} & b_\text{13}\\ \hdashline b_\text{2} & b_\text{6} & b_\text{10} & b_\text{14}\\ \hdashline b_\text{3} & b_\text{7} & b_\text{11} & b_\text{15}\\ \end{array}

В AES байти записуються в state по стовпцях, а не по рядках. Це означає, що перші 4 байти утворюють перший стовпець, наступні 4 - другий і т.д.

AES використовує 4 операції над state:

  1. SubBytes
  2. ShiftRows
  3. MixColumns
  4. AddRoundKey

Увесь процес буде виглядати так:

SubBytes

Замінює кожен байт на інший за таблицею, щоб дані перестали бути впізнаваними.

Кожен байт в state замінюється за допомогою S-box:

Рядки - це перша частина байту, стовпчики - це друга частина байту, їх перетин дає нам вихідний байт.

Наприклад:

0063ff1620b7a449\text{00} \to \text{63} \\ \text{ff} \to \text{16} \\ \text{20} \to \text{b7} \\ \text{a4} \to \text{49} \\

ShiftRows

Зсуває байти в рядках, щоб вони перемішалися між собою.

Зсув в рядку:

Рядок 0: без змін
Рядок 1: зсув на 1
Рядок 2: зсув на 2
Рядок 3: зсув на 3

Приклад:

b0b4b8b12b1b5b9b13b2b6b10b14b3b7b11b15\def\arraystretch{1.5} \begin{array}{c:c:c:c} b0 & b4 & b8 & b12\\ \hdashline b1 & b5 & b9 & b13\\ \hdashline b2 & b6 & b10 & b14\\ \hdashline b3 & b7 & b11 & b15\\ \end{array}

Перетворюємо на:

b0b4b8b12b5b9b13b1b10b14b2b6b15b3b7b11\def\arraystretch{1.5} \begin{array}{c:c:c:c} b0 & b4 & b8 & b12\\ \hdashline b5 & b9 & b13 & b1\\ \hdashline b10 & b14 & b2 & b6\\ \hdashline b15 & b3 & b7 & b11\\ \end{array}

MixColumns

Перемішує байти всередині кожного стовпця, посилюючи плутанину.

Ми беремо стовпчик:

[abcd]\begin{bmatrix} a\\ b\\ c\\ d\\ \end{bmatrix}

Після чого множимо його за допомогою матриці:

[02030101010203010101020303010102]\begin{bmatrix} 02 & 03 & 01 & 01\\ 01 & 02 & 03 & 01\\ 01 & 01 & 02 & 03\\ 03 & 01 & 01 & 02\\ \end{bmatrix}

В результаті отримуємо нові значення:

a=(02a)(03b)(01c)(01d)b=(01a)(02b)(03c)(01d)c=(01a)(01b)(02c)(03d)d=(03a)(01b)(01c)(02d)a = (02 * a) \oplus (03 * b) \oplus (01 * c) \oplus (01 * d) \\ b = (01 * a) \oplus (02 * b) \oplus (03 * c) \oplus (01 * d) \\ c = (01 * a) \oplus (01 * b) \oplus (02 * c) \oplus (03 * d) \\ d = (03 * a) \oplus (01 * b) \oplus (01 * c) \oplus (02 * d)

Робимо це і для інших стовпчиків:

a0a1a2a3b0b1b2b3c0c1c2c3d0d1d2d3\def\arraystretch{1.5} \begin{array}{c:c:c:c} a_0 & a_1 & a_2 & a_3 \\ b_0 & b_1 & b_2 & b_3 \\ c_0 & c_1 & c_2 & c_3 \\ d_0 & d_1 & d_2 & d_3 \\ \end{array}

Правила множення:

01x=x02x={x1,якщо MSB(x)=0(x1)    0x1b,якщо MSB(x)=103x=(02x)    x\begin{aligned} 01 * x &= x \\[8pt] \\[-2pt] \\[8pt] 02 * x &= \begin{cases} x \ll 1, & \text{якщо } \mathrm{MSB}(x)=0 \\[4pt] (x \ll 1)\;\oplus\;0x1b, & \text{якщо } \mathrm{MSB}(x)=1 \end{cases} \\[12pt] \\[-2pt] \\[8pt] 03 * x &= (02 * x)\;\oplus\;x \end{aligned}

AddRoundKey

Пов’язує стан із секретним ключем, роблячи дешифрування неможливим без знання ключа.

Найпростіша операція, все що нам потрібно зробити XOR між байтами ключа та state.

Key Schedule

Key Schedule в AES — це алгоритм, який з одного короткого секретного ключа генерує набір раундових ключів, по одному на кожен раунд шифрування.

Початковий ключ, як і state, представляється у вигляді матриці 4×4 байтів:

b0b4b8b12b1b5b9b13b2b6b10b14b3b7b11b15\def\arraystretch{1.5} \begin{array}{c:c:c:c} b_\text{0} & b_\text{4} & b_\text{8} & b_\text{12}\\ \hdashline b_\text{1} & b_\text{5} & b_\text{9} & b_\text{13}\\ \hdashline b_\text{2} & b_\text{6} & b_\text{10} & b_\text{14}\\ \hdashline b_\text{3} & b_\text{7} & b_\text{11} & b_\text{15}\\ \end{array}

На практиці зручніше думати про ключ як про 4 слова по 4 байти:

W0, W1, W2, W3W_0, \ W_1, \ W_2, \ W_3

де кожне W — це:

Wi=[b4ib4i + 1b4i + 2b4i + 3]W_i = \begin{bmatrix} b_\text{4i} \\ b_\text{4i + 1} \\ b_\text{4i + 2} \\ b_\text{4i + 3} \\ \end{bmatrix}
Генеруємо перше слово в новому ключі
6671336e613432656a33667266346433\def\arraystretch{1.5} \begin{array}{c:c:c:c} 66 & 71 & 33 & 6e \\ \hdashline 61 & 34 & 32 & 65 \\ \hdashline 6a & 33 & 66 & 72 \\ \hdashline 66 & 34 & 64 & 33 \end{array}
  1. Беремо останнє слово з минулого ключа:
[6e657233]\begin{bmatrix} 6e \\ 65 \\ 72 \\ 33 \end{bmatrix}
  1. Робимо RotWord (Циклічно зсуваємо байти вліво):
[6e657233][6572336e]\begin{bmatrix} 6e \\ 65 \\ 72 \\ 33 \end{bmatrix} \to \begin{bmatrix} 65 \\ 72 \\ 33 \\ 6e \end{bmatrix}
  1. SubWord. Кожен байт отриманого слова пропускається через той самий S-box, що й у SubBytes.
[6572336e][4d40c39f]\begin{bmatrix} 65 \\ 72 \\ 33 \\ 6e \end{bmatrix} \to \begin{bmatrix} 4d \\ 40 \\ c3 \\ 9f \end{bmatrix}
  1. До першого байта слова застосовується XOR з раундовою константою Rcon:
Rcon=01,02,04,08,10,20,40,80,1b,36[4d40c39f][4c40c39f]Rcon = {01, 02, 04, 08, 10, 20, 40, 80, 1b, 36} \\[8pt] \begin{bmatrix} 4d \\ 40 \\ c3 \\ 9f \end{bmatrix} \to \begin{bmatrix} 4c \\ 40 \\ c3 \\ 9f \end{bmatrix}
  1. Також потрібно зробити XOR з першим словом в минулому ключі:
[4c40c39f][66616a66][2a21a9f9]\begin{bmatrix} 4c \\ 40 \\ c3 \\ 9f \end{bmatrix} \oplus \begin{bmatrix} 66 \\ 61 \\ 6a \\ 66 \end{bmatrix} \to \begin{bmatrix} 2a \\ 21 \\ a9 \\ f9 \end{bmatrix}

Це буде перше слово нашого нового ключа.

Генеруємо інші слова

Наступні слова будуть генеруватися за правилом:

Wi=Wi-1Wi-4W_i = W_\text{i-1} \oplus W_\text{i-4}

Тобто друге слово це - минуле слово та друге слово в початковаму ключі:

[2a21a9f9][71343334][5b159acd]\begin{bmatrix} 2a \\ 21 \\ a9 \\ f9 \end{bmatrix} \oplus \begin{bmatrix} 71 \\ 34 \\ 33 \\ 34 \end{bmatrix} \to \begin{bmatrix} 5b \\ 15 \\ 9a \\ cd \end{bmatrix}

Трете слово - минуле слово та третье слово з початкового ключа:

[5b159acd][33326664][6827fca9]\begin{bmatrix} 5b \\ 15 \\ 9a \\ cd \end{bmatrix}\oplus \begin{bmatrix} 33 \\ 32 \\ 66 \\ 64 \end{bmatrix} \to \begin{bmatrix} 68 \\ 27 \\ fc \\ a9 \end{bmatrix}

І останнє слово - це минуле слова та останнє слово з початкового ключа:

[6827fca9][6e657233][06428e9a]\begin{bmatrix} 68 \\ 27 \\ fc \\ a9 \end{bmatrix}\oplus \begin{bmatrix} 6e \\ 65 \\ 72 \\ 33 \end{bmatrix} \to \begin{bmatrix} 06 \\ 42 \\ 8e \\ 9a \end{bmatrix}

Новий ключ буде виглядати так:

2a5b680621152742a99afc8ef9cda99a\def\arraystretch{1.5} \begin{array}{c:c:c:c} 2a & 5b & 68 & 06 \\ \hdashline 21 & 15 & 27 & 42 \\ \hdashline a9 & 9a & fc & 8e \\ \hdashline f9 & cd & a9 & 9a \end{array}

Або можемо записати як строку:

2a21a9f95b159acd6827fca906428e9a

За таким алгоритмом нам потрібно згенерувати ключі для раундів 2-10.

Шифруємо текст власноруч

Спочатку потрібно обрати текст для шифрування:

Hello from LD31D

Та перетворити на Hex:

48 65 6c 6c 6f 20 66 72 6f 6d 20 4c 44 33 31 44

486f6f4465206d336c6620316c724c44\def\arraystretch{1.5} \begin{array}{c:c:c:c} 48 & 6f & 6f & 44\\ \hdashline 65 & 20 & 6d & 33 \\ \hdashline 6c & 66 & 20 & 31 \\ \hdashline 6c & 72 & 4c & 44 \end{array}

Також потрібно обрати ключ:

fajfq43432fdner3

В hex:

66 61 6a 66 71 34 33 34 33 32 66 64 6e 65 72 33

6671336e613432656a33667266346433\def\arraystretch{1.5} \begin{array}{c:c:c:c} 66 & 71 & 33 & 6e \\ \hdashline 61 & 34 & 32 & 65 \\ \hdashline 6a & 33 & 66 & 72 \\ \hdashline 66 & 34 & 64 & 33 \end{array}

Initial Round (тільки AddRoundKey)

StateRoundKey0State \oplus RoundKey0

Результат (новий State):

2e1e5c2a04145f56065546430a462877\def\arraystretch{1.5} \begin{array}{c:c:c:c} 2e & 1e & 5c & 2a \\ \hdashline 04 & 14 & 5f & 56 \\ \hdashline 06 & 55 & 46 & 43 \\ \hdashline 0a & 46 & 28 & 77 \end{array}

9 основних раундів

Нам потрібно зробити 9 раундів в кожному з яких виконати такі операції:

  1. SubBytes
  2. ShiftRows
  3. MixColumns
  4. AddRoundKey

Для прикладу ми розглянемо один раунд, всі інші будуть виконуватись за таким самим принципом.

SubBytes

Використовуючи S-box замінуюємо байти:

Результат:

31724ae5f2facfb16ffc5a1a675a34f5\def\arraystretch{1.5} \begin{array}{c:c:c:c} 31 & 72 & 4a & e5 \\ \hdashline f2 & fa & cf & b1 \\ \hdashline 6f & fc & 5a & 1a \\ \hdashline 67 & 5a & 34 & f5 \end{array}

ShiftRows

Результат:

31724ae5facfb1f25a1a6ffcf5675a34\def\arraystretch{1.5} \begin{array}{c:c:c:c} 31 & 72 & 4a & e5 \\ \hdashline fa & cf & b1 & f2 \\ \hdashline 5a & 1a & 6f & fc \\ \hdashline f5 & 67 & 5a & 34 \end{array}

MixColumns

Цей етап може здаватися не зрозумілим, тому розглянемо його більш детально.

Наш state зараз виглядає так:

31724ae5facfb1f25a1a6ffcf5675a34\def\arraystretch{1.5} \begin{array}{c:c:c:c} 31 & 72 & 4a & e5 \\ \hdashline fa & cf & b1 & f2 \\ \hdashline 5a & 1a & 6f & fc \\ \hdashline f5 & 67 & 5a & 34 \end{array}

Візьмемо з нього перший стовпчик:

[31fa5af5]\begin{bmatrix} 31 \\ fa \\ 5a \\ f5 \end{bmatrix}

Та помножимо його на задану матрицю:

[02030101010203010101020303010102]\begin{bmatrix} 02 & 03 & 01 & 01 \\ 01 & 02 & 03 & 01 \\ 01 & 01 & 02 & 03 \\ 03 & 01 & 01 & 02 \\ \end{bmatrix}

Нові значення будуть обчислені за такими формулами:

(0231)(03fa)(015a)(01f5)(0131)(02fa)(035a)(01f5)(0131)(01fa)(025a)(03f5)(0331)(01fa)(015a)(02f5)(02 * 31) \oplus (03 * fa) \oplus (01 * 5a) \oplus (01 * f5) \\ (01 * 31) \oplus (02 * fa) \oplus (03 * 5a) \oplus (01 * f5) \\ (01 * 31) \oplus (01 * fa) \oplus (02 * 5a) \oplus (03 * f5) \\ (03 * 31) \oplus (01 * fa) \oplus (01 * 5a) \oplus (02 * f5) \\

Множення на 3:

03x=(02x)x03 * x = (02 * x) \oplus x

Множення на 1:

01x=x01 * x = x

Спростимо формули:

(0231)((02fa)fa)5af531(02fa)((025a)5a)f531fa(025a)((02f5)f5)((0231)31)fa5a(02f5)(02 * 31) \oplus ((02 * fa) \oplus fa) \oplus 5a \oplus f5 \\ 31 \oplus (02 * fa) \oplus ((02 * 5a) \oplus 5a) \oplus f5 \\ 31 \oplus fa \oplus (02 * 5a) \oplus ((02 * f5) \oplus f5) \\ ((02 * 31) \oplus 31) \oplus fa \oplus 5a \oplus (02 * f5) \\

Тепер щоб обчислити множення на два нам потрібно перевести значення з hex до binary.

Це можна зробити за допомогою таблиці:

Наприклад щоб перевести 31 з hex до бінарного коду, ділемо байт на два півбайти та замінюємо значення:

316=00112116=000123116=0011000123_\text{16} = 0011_2 \\ 1_\text{16} = 0001_2 \\[8pt] 31_\text{16} = 00110001_2

Тепер дивлячись на байт в бінарному вигляді, потрібно зʼясувати чи MSB (старший біт байта, тобто найлівіший біт) 0 чи 1.

Якщо MSB - 0:

x<<100110001+0x << 1 \\[8pt] \xcancel{0}0110001 + 0

Якщо MSB - 1:

x<<11b1610001001+01b16x << 1 \oplus 1b_\text{16} \\[8pt] \xcancel{1}0001001 + 0 \oplus 1b_\text{16}

Щоб зробити xor нам потрібно перевести байти до бінарного вигляду та залишити одиниці тільки на тих позиціях де є лише 1 одиниця:

1b16=000110112XOR: 0001001000011011000010011b_\text{16} = 00011011_2 \\[8pt] \begin{array}{c} \text{XOR: } \\ 00010010 \\ 00011011 \\ \hline 00001001 \end{array}

Обчислимо множення та отримуємо:

62(effa)5af531ef(b45a)f531fab4(f1f5)(6231)fa5af162 \oplus (ef \oplus fa) \oplus 5a \oplus f5 \\ 31 \oplus ef \oplus (b4 \oplus 5a) \oplus f5 \\ 31 \oplus fa \oplus b4 \oplus (f1 \oplus f5) \\ (62 \oplus 31) \oplus fa \oplus 5a \oplus f1

Тепер виконаємо XOR та отримаємо такі значення:

d8c57b02d8 \\ c5 \\ 7b \\ 02

Це і будуть нові значення для нашої колонки. Тепер потрібно виконати множення на статичну матрицю для інших колонок.

Результат:

d8d36914c5bed8317b20cba8028db452\def\arraystretch{1.5} \begin{array}{c:c:c:c} d8 & d3 & 69 & 14 \\ \hdashline c5 & be & d8 & 31 \\ \hdashline 7b & 20 & cb & a8 \\ \hdashline 02 & 8d & b4 & 52 \end{array}

AddRoundKey

Не забуваємо використовувати новий RoundKey.

Результат:

f2880112e4abff73d2ba3726fb401dc8\def\arraystretch{1.5} \begin{array}{c:c:c:c} f2 & 88 & 01 & 12 \\ \hdashline e4 & ab & ff & 73 \\ \hdashline d2 & ba & 37 & 26 \\ \hdashline fb & 40 & 1d & c8 \end{array}

Тепер нам потрібно повторити ці дії ще 8 раундів.

Final Round

В останьому раунді нам потрібно зробити всього 3 операції:

  1. SubBytes
  2. ShiftRows
  3. AddRoundKey

В результаті наш state буде виглядати так:

15ee8634affdb99f73387ed51c356dec\def\arraystretch{1.5} \begin{array}{c:c:c:c} 15 & ee & 86 & 34 \\ \hdashline af & fd & b9 & 9f \\ \hdashline 73 & 38 & 7e & d5 \\ \hdashline 1c & 35 & 6d & ec \end{array}

Залишилось повернути state до hex-строки:

15af731ceefd383586b97e6d349fd5ec

Це і є наш шифротекст, можемо розшифрувати його за допомогою CyberChef:

Код на Golang

package main

import (
	"encoding/hex"
	"fmt"
)

var sbox = [256]byte{
	0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5,
	0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
	0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0,
	0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
	0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc,
	0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
	0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a,
	0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
	0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0,
	0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
	0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b,
	0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
	0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85,
	0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
	0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
	0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
	0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17,
	0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
	0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88,
	0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
	0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c,
	0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
	0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9,
	0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
	0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6,
	0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
	0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e,
	0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
	0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94,
	0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
	0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68,
	0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16,
}

var rcon = [10]byte{
	0x01, 0x02, 0x04, 0x08, 0x10,
	0x20, 0x40, 0x80, 0x1b, 0x36,
}

func addRoundKey(state *[16]byte, roundKey [16]byte) {
	for i := 0; i < len(state); i++ {
		state[i] ^= roundKey[i]
	}
}

func subBytes(state *[16]byte) {
	for i := 0; i < 16; i++ {
		state[i] = sbox[state[i]]
	}

}

func shiftRows(state *[16]byte) {
	// Row 1
	state[1], state[5], state[9], state[13] = state[5], state[9], state[13], state[1]
	// Row 2
	state[2], state[6], state[10], state[14] = state[10], state[14], state[2], state[6]
	// Row 3
	state[3], state[7], state[11], state[15] = state[15], state[3], state[7], state[11]
}

func xtime(x byte) byte {
	if x&0x80 != 0 {
		return (x << 1) ^ 0x1b
	}
	return x << 1
}

func multiplication(x byte, y int) byte {
	switch y {
	case 3:
		return xtime(x) ^ x
	case 2:
		return xtime(x)
	default:
		return x
	}
}

func mixColumns(state *[16]byte) {
	for column := 0; column < 4; column++ {
		aIndex := column * 4
		bIndex := column*4 + 1
		cIndex := column*4 + 2
		dIndex := column*4 + 3

		a := state[aIndex]
		b := state[bIndex]
		c := state[cIndex]
		d := state[dIndex]

		// 2 3 1 1
		state[aIndex] = multiplication(a, 2) ^ multiplication(b, 3) ^ c ^ d
		// 1 2 3 1
		state[bIndex] = a ^ multiplication(b, 2) ^ multiplication(c, 3) ^ d
		// 1 1 2 3
		state[cIndex] = a ^ b ^ multiplication(c, 2) ^ multiplication(d, 3)
		// 3 1 1 2
		state[dIndex] = multiplication(a, 3) ^ b ^ c ^ multiplication(d, 2)
	}
}

func rotateWord(word [4]byte) [4]byte {
	return [4]byte{word[1], word[2], word[3], word[0]}
}

func subWord(word [4]byte) [4]byte {
	for i := 0; i < 4; i++ {
		word[i] = sbox[word[i]]
	}
	return word
}

func keySchedule(key [16]byte) [11][16]byte {
	var roundKeys [11][16]byte
	roundKeys[0] = key

	for round := 1; round <= 10; round++ {
		prev := roundKeys[round-1]
		var next [16]byte

		word := [4]byte{prev[12], prev[13], prev[14], prev[15]}

		word = rotateWord(word)
		word = subWord(word)
		word[0] ^= rcon[round-1]

		for i := 0; i < 4; i++ {
			next[i] = prev[i] ^ word[i]
		}

		for i := 4; i < 16; i++ {
			next[i] = prev[i] ^ next[i-4]
		}

		roundKeys[round] = next
	}

	return roundKeys
}

func encryptBlock(block [16]byte, key [16]byte) [16]byte {
	state := &block

	roundKeys := keySchedule(key)
	addRoundKey(state, roundKeys[0])

	for i := 1; i < 10; i++ {
		subBytes(state)
		shiftRows(state)
		mixColumns(state)
		addRoundKey(state, roundKeys[i])
	}

	subBytes(state)
	shiftRows(state)
	addRoundKey(state, roundKeys[10])

	return *state
}

func main() {
	plaintext := "Hello from LD31D"
	key := "fajfq43432fdner3"

	if len(plaintext) != 16 {
		fmt.Println("Plaintext must be 16 bytes long")
		return
	}

	if len(key) != 16 {
		fmt.Println("Key must be 16 bytes long")
		return
	}

	fmt.Println("Plaintext:", plaintext)
	fmt.Println("Key:", key)

	var plaintextBytes [16]byte
	copy(plaintextBytes[:], []byte(plaintext))
	var keyBytes [16]byte
	copy(keyBytes[:], []byte(key))

	ciphertext := encryptBlock(plaintextBytes, keyBytes)
	hexCiphertext := hex.EncodeToString(ciphertext[:])
	fmt.Println("Ciphertext (hex):", hexCiphertext)
}

Додаткові ресурси:

Резюме

Сьогодні ми розібралися як працює симетричний блоковий шифр AES та написали власну інмплементацію.

Ця реалізація зовсім не оптимізована та вразлива до Side-Channel атак, вона була створена лише для демонстрації принципа роботи.

Не в якому разі не cлід використовувати її у власних проектах, краще покладатися на вже готові популярні бібліотеки.

Завдання для самоперевірки

  1. Подивіться додаткові матеріали
  2. Опишіть які операції виконує AES та яку роль вони виконують
  3. Спробуйте самостійно зашифрувати якась повідомлення за допомогою звичайного блокнота
  4. Самостійно напишить код на комфортній для вас мові програмування, використовуючі засвоєні знання
  5. Проаналізуте cвій код та опишіть які вразливості є в реалізації
  6. Пошукайте в інтернеті інформацю про Padding та додайте його до своєї реалізації
  7. Додайте можливіть шифрування декількох блоків
  8. Додайте можливіть розшифрування шифротексту