## TÀI LIỆU THAM KHẢO
https://github.com/boppreh/aes/blob/master/aes.py
# AES
<iframe width="560" height="315" src="https://www.youtube.com/embed/gP4PqVGudtg?si=FlU4OXyDMHdY2SNt&clip=UgkxXshfWihgVUsVcQmdRggoQDLe9-4u11RS&clipt=EJh1GLDqAQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
- **AES (Advanced Encryption Standard)** là tiêu chuẩn mã hóa đối xứng (dùng chung một khóa để mã hóa và giải mã). Thuật toán này hoạt động bằng cách chia văn bản gốc thành các khối dữ liệu cố định, sau đó xử lý chúng qua hệ thống các bước toán học phức tạp kết hợp với khóa bí mật để truyền tải thông tin an toàn và hiệu quả.
- Đây là thuật toán mã khóa khối với kích thước khối cố định 128 bit, hỗ trợ khóa dài 128,192 hoặc 256 bit. Dữ liệu trong AES được tổ chức với dạng ma trận 4x4(State) chứa 16 byte.Tùy vào độ dài khóa ,số vòng biến đổi khác nhau:
+ 128 bit : 10 vòng , 11 khóa con
+ 192 bit : 12 vòng , 13 khóa con
+ 256 bit : 14 vòng , 15 khóa con
- Mỗi vòng gồm các bước biến đổi chính:
+ **AddRoundKey :** XOR dữ liệu với khóa con
+ **SubBytes :** Thay thế từng byte qua bảng S-box
+ **ShiftRows :** Dịch các hàng trong ma trận
+ **MixColumns :** Trộn dữ liệu giữa các cột
(**Vòng cuối cùng ko dùng MixColumns**)
## Quy trình mã hóa và giải mã AES
- Ta có sơ đồ mã hóa của AES như sau:
<iframe width="560" height="315" src="https://www.youtube.com/embed/gP4PqVGudtg?si=EU5MFsqDWbRatSBj&clip=UgkxgLuVIiGKwuRihHlOl1oBCqZVw5b769Mh&clipt=EMjfAhjYrQM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
### Quy trình mã hóa
#### 4. AddRoundKey
- Khối thông tin gốc và khối khóa sẽ được XOR với nhau
<iframe width="560" height="315" src="https://www.youtube.com/embed/gP4PqVGudtg?si=2De4YAyIW5UyhX5o&clip=UgkxeDRkntERrAAhcAqqePLObX1yMraHnFDn&clipt=EMjQBxiorgg" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
**Ví dụ**
```py
state = [
[206, 243, 61, 34],
[171, 11, 93, 31],
[16, 200, 91, 108],
[150, 3, 194, 51],
]
round_key = [
[173, 129, 68, 82],
[223, 100, 38, 109],
[32, 189, 53, 8],
[253, 48, 187, 78],
]
def add_round_key(s, k):
for i in range(4):
for j in range(4):
s[i][j] = s[i][j] ^ k[i][j]
return s
print(add_round_key(state, round_key))
# [ [99, 114, 121, 112],
# [116, 111, 123, 114],
# [48, 117, 110, 100],
# [107, 51, 121, 125] ]
```
#### 1. SubBytes (Substitude bytes)
<iframe width="560" height="315" src="https://www.youtube.com/embed/gP4PqVGudtg?si=aKWfH3IYIGYj0gxH&clip=UgkxW0RLUPlp_mYFuTk1oRuIwCXhMVKg-_Ff&clipt=EID0AxiA8QQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
- Các byte được thế thông qua bảng tra S-box. Đây chính là quá trình phi tuyến của thuật toán. Hộp S-box này được tạo ra từ một phép biến đổi khả nghịch trong trường hữu hạn GF (28) có tính chất phi tuyến.
- Để chống lại các tấn công dựa trên các đặc tính đại số, hộp S-box này được tạo nên bằng cách kết hợp phép nghịch đảo với một phép biến đổi affine khả nghịch. Hộp S-box này cũng được chọn để tránh các điểm bất động (fixed point).
+ **Nghịch đảo số học (SubBytes):** Đầu tiên, mỗi byte đầu vào $x$ được chuyển sang giá trị nghịch đảo của nó trong trường $GF(2^8)$. Nếu $x = 0$, giá trị đầu ra vẫn là $0$.$$f(x) = x^{-1} \pmod{P(x)}$$Trong đó $P(x)$ là đa thức bất khả quy: $x^8 + x^4 + x^3 + x + 1$.
+ **Affine (Affine Transformation):** Để phá vỡ cấu trúc đại số đơn giản của phép nghịch đảo và loại bỏ các điểm bất động ($S(x) = x$), một phép biến đổi affine được thực hiện. Công thức dạng ma trận như sau:$$b'_i = b_i \oplus b_{(i+4)\pmod 8} \oplus b_{(i+5)\pmod 8} \oplus b_{(i+6)\pmod 8} \oplus b_{(i+7)\pmod 8} \oplus c_i$$Hoặc viết dưới dạng ma trận tổng quát:$$\begin{bmatrix} s_0 \\ s_1 \\ s_2 \\ s_3 \\ s_4 \\ s_5 \\ s_6 \\ s_7 \end{bmatrix} = \begin{bmatrix} 1 & 0 & 0 & 0 & 1 & 1 & 1 & 1 \\ 1 & 1 & 0 & 0 & 0 & 1 & 1 & 1 \\ 1 & 1 & 1 & 0 & 0 & 0 & 1 & 1 \\ 1 & 1 & 1 & 1 & 0 & 0 & 0 & 1 \\ 1 & 1 & 1 & 1 & 1 & 0 & 0 & 0 \\ 0 & 1 & 1 & 1 & 1 & 1 & 0 & 0 \\ 0 & 0 & 1 & 1 & 1 & 1 & 1 & 0 \\ 0 & 0 & 0 & 1 & 1 & 1 & 1 & 1 \end{bmatrix} \begin{bmatrix} x_0 \\ x_1 \\ x_2 \\ x_3 \\ x_4 \\ x_5 \\ x_6 \\ x_7 \end{bmatrix} + \begin{bmatrix} 1 \\ 1 & \\ 0 \\ 0 \\ 0 \\ 1 \\ 1 \\ 0 \end{bmatrix}$$
```py
s_box = (
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,
)
inv_s_box = (
0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D,
)
```
- Đây là ví dụ cho bước SubBytes:
```python=
state = [
[251, 64, 182, 81],
[146, 168, 33, 80],
[199, 159, 195, 24],
[64, 80, 182, 255],
]
def sub_bytes(state):
for i in range(4):
for j in range(4):
state[i][j] = s_box[state[i][j]]
print(state)
# [ [251, 64, 182, 81],
# [146, 168, 33, 80],
# [199, 159, 195, 24],
# [64, 80, 182, 255] ]
```
#### 2. ShiftRows
<iframe width="560" height="315" src="https://www.youtube.com/embed/gP4PqVGudtg?si=ck0-0kGu8TGLExQl&clip=Ugkx6IkcsWVAKvdWgx-PdJh88heuvlN_Bb2e&clipt=EIiYBRiY5gU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
- Các hàng được dịch vòng một số bước nhất định. Đối với AES, hàng đầu được giữ nguyên. Mỗi byte của hàng thứ 2 được dịch vòng trái một vị trí. Tương tự, các hàng thứ 3 và 4 được dịch vòng 2 và 3 vị trí. Do vậy, mỗi cột khối đầu ra của bước này sẽ bao gồm các byte ở đủ 4 cột khối đầu vào. Đối với Rijndael với độ dài khối khác nhau thì số vị trí dịch chuyển cũng khác nhau.
```py
def shift_rows(s):
s[0][1], s[1][1], s[2][1], s[3][1] = s[1][1], s[2][1], s[3][1], s[0][1]
s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
s[0][3], s[1][3], s[2][3], s[3][3] = s[3][3], s[0][3], s[1][3], s[2][3]
```
#### 3. MixColumns
<iframe width="560" height="315" src="https://www.youtube.com/embed/gP4PqVGudtg?si=tQMPY8kSEsCTGDXk&clip=UgkxsGW-IZDEMIzuSV6bkO61CYR9j_NLvd27&clipt=EKCNBhiIkgc" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
- Bốn byte trong từng cột được kết hợp lại theo một phép biến đổi tuyến tính khả nghịch. Mỗi khối 4 byte đầu vào sẽ cho một khối 4 byte ở đầu ra với tính chất là mỗi byte ở đầu vào đều ảnh hưởng tới cả bốn byte đầu ra. Cùng với bước ShiftRows, MixColumns đã tạo ra tính chất khuếch tán cho thuật toán. Mỗi cột được xem như một đa thức trong trường hữu hạn và được nhân với đa thức $c(x) = 3x^3 + x^2 + x + 2( \ modulo \ x^4 +1)$. Đây được coi là phép nhân trong trường hữu hạn.
```py
xtime = lambda a: ((a << 1) ^ (0x1B if a & 0x80 else 0x00)) & 0xFF
def mix_columns(s):
for c in s:
t = c[0] ^ c[1] ^ c[2] ^ c[3]
c[:] = [c[i] ^ t ^ xtime(c[i] ^ c[(i+1)%4]) for i in range(4)]
```
### Quy trình giải mã
- Quy trình giải mã là thực hiện ngược lại hoàn toàn các bước mã hóa để khôi phục lại plaintext với các hàm nghịch đảo .
- Nhưng chúng ta có thể dùng quy trình giải mã tương đương để sắp xếp lại các bước sao cho giống hệt với quy trình mã hóa.
#### Key Expansion (Mở rộng khóa)
- Trước khi bắt đầu giải mã, từ khóa gốc (master_key) dài 16 bytes, thuật toán tạo ra 11 bộ khóa con (Round Keys).
- Mỗi bộ khóa con dùng cho một vòng (Round).Khóa vòng 10 được dùng đầu tiên, khóa vòng 0 được dùng cuối cùng.
```py
def key_expansion(key):
r_con = (0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36)
w = [list(key[i:i+4]) for i in range(0, 16, 4)]
for i in range(4, 44):
temp = list(w[i-1])
if i % 4 == 0:
temp = [s_box[b] for b in temp[1:] + temp[:1]]
temp[0] ^= r_con[i // 4]
w.append([w[i-4][j] ^ temp[j] for j in range(4)])
return [w[i:i+4] for i in range(0, 44, 4)]
```
#### InvSubBytes :
- Bước này thực hiện tra bảng Inverse S-box để đảo ngược các byte về giá trị gốc trước khi bị thay thế.
```py
def inv_sub_bytes(state):
for i in range(4):
for j in range(4):
state[i][j] = inv_s_box[state[i][j]]
```
#### InvShiftRows:
- Các hàng được dịch vòng ngược lại (dịch sang phải).
- Hàng 0 giữ nguyên, hàng 1 dịch phải 1 vị trí, hàng 2 dịch phải 2 vị trí, và hàng 3 dịch phải 3 vị trí.
```py
def inv_shift_rows(s):
s[0][1], s[1][1], s[2][1], s[3][1] = s[3][1], s[0][1], s[1][1], s[2][1]
s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
s[0][3], s[1][3], s[2][3], s[3][3] = s[1][3], s[2][3], s[3][3], s[0][3]
```
#### InvMixColumns:
- Sử dụng phép nhân ma trận nghịch đảo để "gỡ" các cột đã bị trộn.
```py
def inv_mix_columns(s):
for c in s:
u = xtime(xtime(c[0] ^ c[2]))
v = xtime(xtime(c[1] ^ c[3]))
c[0] ^= u; c[1] ^= v; c[2] ^= u; c[3] ^= v
mix_columns(s)
```
#### Cách giải mã
Trong kịch bản giả lập sẽ cho key và ciphertext là chuỗi ta có quy trình giải mã như sau
- **Vòng khởi đầu:**
+ AddRoundKey (với khóa vòng 10).
- **9 vòng lặp:**
+ InvSubBytes
+ InvShiftRows
+ InvMixColumns
+ AddRoundKey (với khóa vòng đã được biến đổi bằng InvMixColumns).
- **Vòng cuối:**
+ InvSubBytes
+ InvShiftRows
+ AddRoundKey (với khóa vòng 0).
**Bước AddRoundKey** không dùng trực tiếp khóa con lấy từ Key Expansion (trừ vòng đầu và vòng cuối).Phải lấy khóa con đó, chạy qua hàm InvMixColumns, rồi mới đem đi XOR với state.
```py
def decrypt(key, ciphertext):
round_keys = key_expansion(key)
state = bytes2matrix(ciphertext)
add_round_key(state, round_keys[10])
for i in range(N_ROUNDS - 1, 0, -1):
inv_sub_bytes(state)
inv_shift_rows(state)
inv_mix_columns(state)
tk = [list(r) for r in round_keys[i]]
inv_mix_columns(tk)
add_round_key(state, tk)
inv_sub_bytes(state)
inv_shift_rows(state)
add_round_key(state, round_keys[0])
plaintext = bytes([b for col in state for b in col])
return plaintext
```
#### Các hàm bổ sung:
- Chuyển 16 bytes thành ma trận 4x4 xếp theo cột.
```py
def bytes2matrix(text):
return [list(text[i:i+4]) for i in range(0, len(text), 4)]
```
- Chuyển ma trận ngược lại thành chuỗi bytes.
```py
def matrix2bytes(matrix):
return bytes([byte for col in matrix for byte in col])
```
# DES
**DES (Data Encryption Standard)**: là một thuật toán mã hóa đối xứng dạng khối, mã hóa dữ liệu theo khối 64 bit bằng khóa 56 bit, hoạt động qua 16 vòng theo cấu trúc Feistel. DES từng là chuẩn mã hóa phổ biến nhưng hiện không còn an toàn do khóa quá ngắn, dễ bị tấn công brute-force, và đã được thay thế bởi các thuật toán hiện đại như AES.
## 1.Giai đoạn khởi tạo
- **Hoán vị ban đầu (IP - Initial Permutation):** Khối 64-bit được hoán vị bit theo bảng cố định, không tăng bảo mật, chỉ chuẩn hóa dữ liệu cho các bước xử lý tiếp theo.
- **Sinh khóa con (Key Schedule):** Khóa chính 64-bit (56-bit hiệu dụng) được sinh 16 khóa con 48-bit, mỗi khóa chỉ dùng cho một vòng.
## 2. Giai đoạn 16 vòng lặp (The 16 Rounds)
- Dữ liệu sau khi hoán vị IP được chia làm hai nửa: Trái ($L_0$) và Phải ($R_0$), mỗi bên 32-bit. Với mỗi vòng $i$, công thức tổng quát là:$$L_i = R_{i-1}$$ $$R_i = L_{i-1} \oplus f(R_{i-1}, K_i)$$
- Trong đó, hàm $f$ (Feistel Function) là trái tim của DES, bao gồm 4 bước nhỏ:
+ **Expansion (E-box):** Mở rộng 32-bit lên 48-bit.
+ **Key Mixing:** XOR kết quả với khóa con $K_i$.
+ **Substitution (S-boxes):** Đây là bước quan trọng nhất. 48-bit được chia thành 8 nhóm, đi qua 8 hộp S-box để nén lại thành 32-bit. Đây là bước phi tuyến tính tạo nên độ bảo mật cho DES.
+ **Permutation (P-box):** Hoán vị lại 32-bit một lần nữa.
## 3. Giai đoạn Kết thúc (Post-processing)
- Sau vòng thứ 16, hai nửa $L_{16}$ và $R_{16}$ sẽ được hoán đổi vị trí cho nhau và ghép lại thành khối 64-bit.
- **Hoán vị nghịch đảo ($IP^{-1}$):** Thực hiện hoán vị ngược lại với bước IP ban đầu để cho ra văn bản mã (Ciphertext) cuối cùng.
### IP và nghịch đảo
**IP**
- Dữ liệu 64-bit đầu vào được coi là một ma trận gồm 64 vị trí. Bảng IP dưới đây quy định vị trí mới của các bit:

- Sau bước **IP** thì, khối 64-bit mới được chia thành hai nửa .

- Hai nửa $L_0$ và $R_0$ này sẽ chính thức đi vào Vòng 1 (Round 1) của cấu trúc Feistel.
**$IP^{-1}$**

### Key Schedule(Sinh khóa con):
**1. Nén và Hoán vị chọn lọc (PC-1)**
- Khóa ban đầu nhập vào có 64-bit. Tuy nhiên, thuật toán sẽ đi qua một bảng gọi là **PC-1 (Permuted Choice 1):**
+ **Loại bỏ 8 bit:** Các bit ở vị trí thứ 8, 16, 24, 32, 40, 48, 56, 64 bị loại bỏ (đây là các bit kiểm tra chẵn lẻ).
+ **Còn lại 56-bit:** Đây mới là những bit thực sự tham gia vào quá trình bảo mật.
+ **Chia đôi:** 56 bit này được chia thành hai nửa, gọi là $C_0$ (trái) và $D_0$ (phải), mỗi nửa có 28-bit.

**2. Dịch vòng trái (Circular Left Shift)**
- Tại mỗi vòng lặp $i$ (từ 1 đến 16), cả $C_{i-1}$ và $D_{i-1}$ sẽ được dịch trái 1 hoặc 2 vị trí tùy theo số thứ tự của vòng đó:
+ Dịch 1 bit: Tại các vòng thứ 1, 2, 9 và 16.
+ Dịch 2 bit: Tại tất cả các vòng còn lại.
+ Mục đích: Việc dịch bit này giúp đảm bảo các bit của khóa chính được luân chuyển và xuất hiện ở các vị trí khác nhau trong từng khóa con.
**Ví dụ** ở vòng 3 với yêu cầu dịch trái 1 vị trí, khóa ban đầu 1000111100001010 sẽ tách bit 1 ở đầu dãy ra. Bit này sau đó được đẩy xuống cuối cùng, đồng thời toàn bộ các bit còn lại dịch sang trái một nấc. Kết quả thu được sau khi xoay vòng là khóa mới 0001111000010101.
**3. Hoán vị nén (PC-2) để tạo khóa con ($K_i$)**
- Sau khi dịch bit xong, hai nửa $C_i$ và $D_i$ (tổng cộng 56-bit) được ghép lại và đi qua một bảng hoán vị thứ hai gọi là **PC-2 (Permuted Choice 2):**
+ **Kết quả:** Bảng PC-2 sẽ chọn ra 48 bit từ 56 bit đầu vào và xáo trộn chúng.
+ **Đầu ra:** Chúng ta thu được khóa con $K_i$ dài 48-bit.

- Ta thấy rằng bit thứ 14 của khóa được đặt ở vị trí đầu tiên của khóa hoán vị mới, bit thứ 17 được đặt ở vị trí thứ hai … cứ thế tiếp tục. Chúng ta có thể thấy khóa mới được tạo ra chỉ có 48 bit, không giống như các khóa trước đó tạo ra 56 bit. Quá trình này được gọi là hoán vị nén (compression permutation).
- PC2 sẽ bỏ qua các bit 9, 18, 22, 25, 35, 38, 43, 54.
```=py
from Crypto.Cipher import DES
from Crypto.Util.Padding import pad, unpad
from Crypto.Random import get_random_bytes
key = get_random_bytes(8)
msg = b'DES{A good cipher}'
cipher = DES.new(key, DES.MODE_ECB)
ct = cipher.encrypt(pad(msg, DES.block_size))
print(ct.hex())
pt = unpad(cipher.decrypt(ct), DES.block_size).decode()
print(pt)
```
### Hàm F (The Feistel Function)
**Expansion - (E-box):**
- Khối đầu vào 32 bit được mở rộng thành 48 bit bằng bảng hoán vị mở rộng (E-table).

- Một số bit đầu vào được lặp lại. Giúp dữ liệu khớp với khóa con (từ 32bit lên 48bit).
**Key Mixing**
- Thực hiện phép XOR giữa kết quả vừa mở rộng với khóa con $K_i$
$$R = E(R_{i-1}) \oplus K_i$$
**Substitution - (S-box)**
- Tạo nên tính phi tuyến của DES.Ở đây 48bit được chia thành 8 nhóm.


- **Quy luật của S-box :** Với đầu vào 6 bit (ví dụ: $b_1b_2b_3b_4b_5b_6$):
+ Hai bit ngoài cùng ($b_1b_6$) quyết định chỉ số Hàng (0-3).
+ Bốn bit giữa ($b_2b_3b_4b_5$) quyết định chỉ số Cột (0-15).
+ Giá trị tại giao điểm Hàng và Cột là một số 4 bit.
+ Kết quả từ 8 hộp S-box được ghép lại thành chuỗi 32 bit.
**Permutation - (P-box)**
- Chuỗi 32-bit đầu ra từ S-box được hoán vị một lần nữa theo bảng P (P-box). Mục đích là để phân tán các bit đầu ra của S-box này sang đầu vào của các S-box khác trong vòng kế tiếp.

**FP (Final Permutation - $IP^{-1}$)**
- Sau khi hoàn tất vòng lặp thứ 16, thuật toán thu được hai nửa dữ liệu $L_{16}$ và $R_{16}$.
- Tại thời điểm này, một bước hoán đổi cuối cùng được thực hiện để chuyển cặp dữ liệu thành $(R_{16}, L_{16})$ trước khi chúng được ghép lại (concatenation) thành một khối duy nhất dài 64-bit.
- Khối dữ liệu tổng hợp này sau đó được đưa qua bảng Hoán vị nghịch đảo ($IP^{-1}$), đây chính là phép toán bù nghịch đảo hoàn toàn của bước hoán vị ban đầu (IP).
- Quá trình xáo trộn vị trí các bit lần cuối này giúp đưa dữ liệu về định dạng chuẩn, và đầu ra cuối cùng thu được chính là Ciphertext (văn bản mã).
**Decryption (Giải mã)**
- Một ưu điểm lớn của cấu trúc mạng Feistel trong DES là quá trình giải mã sử dụng cùng một sơ đồ thuật toán như quá trình mã hóa.
- Điều này giúp tối ưu hóa việc thiết kế phần cứng và phần mềm.
+ **Dữ liệu đầu vào:** Là văn bản mã (Ciphertext) 64-bit.
+ **Cấu trúc thực hiện:** Các bước hoán vị ban đầu ($IP$), 16 vòng lặp, hàm $F$, và hoán vị nghịch đảo ($IP^{-1}$) hoàn toàn giữ nguyên.
+ **Sự khác biệt duy nhất:** Thứ tự sử dụng các khóa con. Để đảo ngược quá trình mã hóa, các khóa con được đưa vào 16 vòng lặp theo thứ tự ngược lại: $(K_{16}, K_{15}, \dots, K_1)$.
### 2DES
- 2DES là ý tưởng sơ khai nhằm tăng độ dài khóa lên gấp đôi bằng cách thực hiện mã hóa hai lần liên tiếp với hai khóa độc lập $K_1$ và $K_2$.
- **Quy trình thực hiện**
- **Mã hóa:** Văn bản gốc $P$ được mã hóa bằng $K_1$, kết quả tiếp tục được mã hóa bằng $K_2$ để tạo ra bản mã $C$.
$$C = E_{K_2}(E_{K_1}(P))$$
- **Giải mã:** Thực hiện ngược lại, giải mã bằng $K_2$ trước, sau đó giải mã bằng $K_1$.$$P = D_{K_1}(D_{K_2}(C))$$
- **Nhược điểm:** Tấn công Meet-in-the-Middle (MitM)
+ Về lý thuyết, 2DES có độ dài khóa là $56 + 56 = 112$ bit, tương đương $2^{112}$ khả năng.
+ Tuy nhiên, thực tế nó hoàn toàn bị phá vỡ bởi tấn công Meet-in-the-Middle:
+ Kẻ tấn công mã hóa $P$ bằng mọi khóa $K_1$ khả thi và lưu vào bảng, đồng thời giải mã $C$ bằng mọi khóa $K_2$ khả thi.
+ Khi tìm thấy giá trị trung gian khớp nhau, cặp khóa $(K_1, K_2)$ đã bị lộ.Độ an toàn thực tế giảm từ $2^{112}$ xuống chỉ còn xấp xỉ $2^{57}$.
+ Do đó, 2DES không được sử dụng trong thực tế vì hiệu quả bảo mật không tương xứng với chi phí tính toán.
#### Meet-in-the-Middle
- Double DES thực hiện mã hóa bản rõ $P$ qua hai lần với hai khóa độc lập $K_1$ và $K_2$:$$C = E_{K2}(E_{K1}(P))$$Về lý thuyết, nếu mỗi khóa dài 56-bit, tổng độ dài khóa là 112-bit, tạo ra không gian khóa $2^{112}$. Tuy nhiên, tấn công MitM đã làm giảm độ phức tạp này xuống chỉ còn khoảng $2^{57}$.
- Tấn công này không cố gắng phá vỡ toàn bộ chuỗi mã hóa cùng lúc. Thay vào đó, nó "gặp nhau ở giữa" bằng cách khai thác giá trị trung gian.
- Giả sử kẻ tấn công có một cặp (Plaintext $P$, Ciphertext $C$):
+ Kẻ tấn công mã hóa $P$ với tất cả các khả năng của $K_1$. Kết quả thu được là giá trị trung gian $M = E_{K1}(P)$. Tất cả các giá trị $M$ này được lưu vào một bảng tra cứu (Lookup Table) cùng với khóa $K_1$ tương ứng.
+ Kẻ tấn công giải mã $C$ với tất cả các khả năng của $K_2$ để tìm giá trị $M' = D_{K2}(C)$.
+ Với mỗi giá trị $M'$ vừa tính, kẻ tấn công kiểm tra xem nó có tồn tại trong bảng tra cứu ở bước 1 hay không.Nếu $M = M'$, tức là $E_{K1}(P) = D_{K2}(C)$.Cặp $(K_1, K_2)$ tìm thấy chính là cặp khóa cần tìm.
### 3DES
- 3DES ra đời nhằm khắc phục lỗ hổng của 2DES và trở thành tiêu chuẩn trong ngành tài chính, ngân hàng trong một thời gian dài.
- **Cấu trúc EDE (Encrypt-Decrypt-Encrypt)**
- Thay vì chỉ mã hóa (EEE), 3DES sử dụng chuỗi mã hóa - giải mã - mã hóa để tăng tính linh hoạt:
- **Công thức mã hóa:** $$C = E_{K_3}(D_{K_2}(E_{K_1}(P)))$$
- **Công thức giải mã:** $$P = D_{K_1}(E_{K_2}(D_{K_3}(C)))$$
- Nếu thiết lập $K_1 = K_2 = K_3$, bước giải mã ở giữa sẽ triệt tiêu bước mã hóa đầu tiên, khiến hệ thống hoạt động y hệt như một DES đơn:$$C = E_{K_1}(D_{K_1}(E_{K_1}(P))) = E_{K_1}(P)$$
#### Các chế độ khóa (Keying Options)
- **Option 1 (3 khóa độc lập):** $K_1 \neq K_2 \neq K_3$. Độ dài khóa 168 bit, độ an toàn thực tế 112 bit. Đây là chế độ an toàn nhất.
- **Option 2 (2 khóa):** $K_1 = K_3$ và $K_1 \neq K_2$. Độ dài khóa 112 bit.
- **Option 3 (1 khóa):** $K_1 = K_2 = K_3$. Tương đương DES đơn, không còn an toàn.
#### Weak Key
- Một khóa $K$ được gọi là Weak Key nếu việc mã hóa một văn bản hai lần với cùng khóa đó sẽ trả lại văn bản gốc ban đầu.
+ Công thức:$$E_K(E_K(P)) = P$$
- Điều này đồng nghĩa với việc hàm mã hóa ($E$) và hàm giải mã ($D$) lúc này là hoàn toàn giống nhau: $$E_K(P) = D_K(P)$$.
- Trong DES ,khóa 56- bit được chia thành 16 khóa con $K_1,...K_16$ cho 16 vòng lặp.
+ Thông thường , các khóa con này khác nhau.
+ Với Weak Key thì (toàn 0 hay toàn 1 hoặc xen kẽ), tất cả 16 khóa con tạo ra đều giồng hệt nhau.
- Vì quá trình giải mã của DES đơn giản là dùng các khóa con theo thứ tự ngược lại($K_{16}-> K_1$) nếu các khóa con như nhau thì việc mã hóa và giải mã trở thành thao tác duy nhất.
- 4 khóa yếu kinh điển trong DES
```python=
0101010101010101 Toàn bộ các khóa con là 0
FEFEFEFEFEFEFEFE Toàn bộ các khóa con là 1
1F1F1F1F0E0E0E0E Các khóa con có nửa đầu là 0, nửa sau là 1
E0E0E0E0F1F1F1F1 Các khóa con có nửa đầu là 1, nửa sau là 0
```
#### Semi-Weak Keys (Khóa nửa yếu)
- Ngoài Weak Keys, DES còn có các cặp Semi-Weak Keys $(K_1, K_2)$. Nếu mã hóa bằng $K_1$ rồi mã hóa tiếp bằng $K_2$, ta sẽ thu được văn bản gốc:$$E_{K2}(E_{K1}(P)) = P$$
- Điều này có nghĩa là mã hóa bằng khóa này tương đương với giải mã bằng khóa kia: $E_{K1} = D_{K2}$
- Chúng ta đã tận dụng chính tính chất $E_K = D_K$ này để biến server (vốn chỉ cho phép mã hóa) thành một công cụ giải mã Flag cho chúng ta.
#### Challenges Triple DES
https://aes.cryptohack.org/triple_des/
Source code:
```python=
from Crypto.Cipher import DES3
from Crypto.Util.Padding import pad
IV = os.urandom(8)
FLAG = ?
def xor(a, b):
# xor 2 bytestrings, repeating the 2nd one if necessary
return bytes(x ^ y for x,y in zip(a, b * (1 + len(a) // len(b))))
@chal.route('/triple_des/encrypt/<key>/<plaintext>/')
def encrypt(key, plaintext):
try:
key = bytes.fromhex(key)
plaintext = bytes.fromhex(plaintext)
plaintext = xor(plaintext, IV)
cipher = DES3.new(key, DES3.MODE_ECB)
ciphertext = cipher.encrypt(plaintext)
ciphertext = xor(ciphertext, IV)
return {"ciphertext": ciphertext.hex()}
except ValueError as e:
return {"error": str(e)}
@chal.route('/triple_des/encrypt_flag/<key>/')
def encrypt_flag(key):
return encrypt(key, pad(FLAG.encode(), 8).hex())
```
- Khi ta cung cấp một khóa 16 bytes ($K_1$ và $K_2$), thuật toán sẽ hoạt động ở chế độ: $$E_{K1}(D_{K2}(E_{K1}(P)))$$
Nếu ta chọn $K_1 = K_2$, thì bước giải mã $D_{K2}$ sẽ triệt tiêu bước mã hóa $E_{K1}$, biến 3DES trở thành DES đơn.
- DES có các khóa yếu mà tại đó hàm mã hóa chính là hàm giải mã ($E_K = D_K$). Điều này dẫn đến tính chất tự nghịch đảo :$$E_K(E_K(P)) = P$$
- Vì IV được XOR ở cả trước và sau khi mã hóa, nếu ta thực hiện mã hóa hai lần chồng lên nhau, các IV ở giữa sẽ tự triệt tiêu nhau:$$( \dots \oplus IV) \oplus IV = \dots \oplus 0$$
$=>$
$$C_{flag} = E_K(P_{flag} \oplus IV) \oplus IV$$ Gửi $C_{flag}$ vào hàm encrypt thông thường với cùng khóa yếu $K$ $$Result = E_K(C_{flag} \oplus IV) \oplus IV$$ $$Result = E_K([E_K(P \oplus IV) \oplus IV] \oplus IV) \oplus IV$$$$Result = E_K(E_K(P \oplus IV)) \oplus IV$$ Do tính chất khóa yếu ($E(E(x)) = x$):$$Result = (P \oplus IV) \oplus IV = P$$
```cpp
import requests
url = "https://aes.cryptohack.org/triple_des/"
key = "0101010101010101FEFEFEFEFEFEFEFE"
r1 = requests.get(url + "encrypt_flag/"+key)
ct = r1.json()['ciphertext']
r2 = requests.get(url + "encrypt/"+ key + '/'+ ct)
res = r2.json()['ciphertext']
flag = unpad(bytes.fromhex(res), 8).decode()
print(flag)
```
- Ta được flag là "**crypto{n0t_4ll_k3ys_4r3_g00d_k3ys}**"
# BLOCK CIPHER MODE OF OPERATION
- Trong AES : Mọi thứ đều được quy đổi ra byte. Một khối AES tiêu chuẩn luôn dài 16 bytes.
- AES là một "Block Cipher" . Thay vì mã hóa từng chữ cái một, nó gom dữ liệu thành từng cụm cố định để xử lý một lần.Trong AES, mỗi block luôn luôn là 16 bytes (128 bit). Nếu thông điệp của dài 32 bytes, nó sẽ được chia làm 2 blocks.
- **Pad (Padding):** Là hành động thêm các byte vào cuối dữ liệu gốc cho đủ 16 byte.
+ Ví dụ: Mình có chuỗi "HELLO" (5 bytes), mình cần thêm 11 bytes nữa để đủ 1 block 16 bytes.
- **Unpad:** Sau khi giải mã xong, sẽ thấy dữ liệu gốc kèm theo đống byte rác đã thêm lúc nãy. Unpad là hành động cắt bỏ các byte rác đó để lấy lại đúng chữ "HELLO" ban đầu.
- **Oracle :** Oracle là một thực thể (thường là máy chủ hoặc một hàm) mà bạn có thể gửi dữ liệu tới, và nó sẽ trả về cho bạn một phản hồi nào đó dựa trên dữ liệu bạn gửi.
+ **Padding Oracle:** Đây là một loại lỗ hổng cực kỳ nổi tiếng.
+ Khi bạn gửi một bản mã sai đến máy chủ, thay vì chỉ báo "Lỗi", máy chủ lại trả về chi tiết: "Lỗi Padding rồi!" hoặc "Padding đúng nhưng sai Key".
+ Kẻ tấn công sẽ dựa vào việc máy chủ báo "Padding đúng hay sai" để đoán dần từng byte của thông điệp gốc mà không cần biết Key.
## Electronic codebook (ECB)
- **Encryption:** $C_i = E_K(P_i)$
- **Decryptino:** $P_i = D_K(C_i)$
#### Mã hóa
- Sử dụng công thức $C_i = E_K(P_i)$, trong đó từng khối Plaintext 16 byte ($P_i$) được biến đổi thành Ciphertext ($C_i$) bằng Key $K$.

```py
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
key = b'Ban_Huyn_rat_buc'
cipher = AES.new(key,AES.MODE_ECB)
plaintext = b"Huyn_phat_din"
ciphertext = cipher.encrypt(pad(plaintext,16))
print(ciphertext.hex())
# 421f88408e9eab5c2e40b5f026883b49
```
#### Giải mã
- Sử dụng công thức nghịch đảo $P_i = D_K(C_i)$.

- Trong AES, chìa khóa $K$ được dùng để XOR vào dữ liệu ở mỗi vòng (bước AddRoundKey). Vì phép XOR có tính chất đảo ngược ($A \oplus B \oplus B = A$), nên khi bạn dùng đúng chìa khóa đó để giải mã, các lớp mặt nạ toán học sẽ bị gỡ bỏ, để lộ ra văn bản gốc ban đầu.
```py
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
key = b'Ban_Huyn_rat_buc'
ciphertext_hex = "421f88408e9eab5c2e40b5f026883b49"
ciphertext = bytes.fromhex(ciphertext_hex)
decipher = AES.new(key,AES.MODE_ECB)
rw = decipher.decrypt(ciphertext)
plaintext = unpad(rw,AES.block_size)
print( plaintext)
# b'Huyn_phat_din'
```
## Cipher block chaining (CBC)
- Trong chế độ CBC, khối đầu tiên sẽ được xor với (IV),đưa kết quả xor đó mã hóa với key ta được ciphertext 1. Sau đó Lấy ciphertext 1 đó đi xor với plaintext 2,... và tiếp tục quá trình đó đến hết.
- Mã hóa: $C_i = E_K(P_i \oplus C_{i-1})$.

```py
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from Crypto.Random import get_random_bytes
key = b'munxongtasksomhe'
huyn = b'Huynratmuondingu'
iv = get_random_bytes(16)
cipher = AES.new(key,AES.MODE_CBC,iv)
ciphertext = cipher.encrypt(pad(huyn,AES.block_size))
print("iv:",iv.hex())
print("ciphertext:",ciphertext.hex())
# iv: 7e5fbf471c16dc9399a31e4413ca9bf8
# ciphertext: 0445a5a18f3fefb9080db9732a5fde7478073bfe65c36a6b1a2f33634e0a6e3c
```
- Giải mã: $P_i = D_K(C_i) \oplus C_{i-1}$.

```py
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
iv = bytes.fromhex('97e4c6f7ac73a2a580538b7674ace4eb')
ciphertext = 'b56eeac8bfd68f1ef64e1060cf726cc642fe3b1fbee0aad32d176f7f7815237c'
key = b'munxongtasksomhe'
cipher = bytes.fromhex(ciphertext)
decipher = AES.new(key,AES.MODE_CBC,iv)
rw = decipher.decrypt(cipher)
plaintext = unpad(rw,AES.block_size)
print(plaintext)
# b'anh_qi_la_do_toi'
```
- Chế độ mã hóa CBC hoạt động theo nguyên lý chuỗi, trong đó mỗi khối dữ liệu được kết hợp (XOR) với khối mã hóa trước đó, dẫn đến việc mã hóa bắt buộc phải thực hiện tuần tự và cần có bù dữ liệu (padding) cho đủ kích thước khối.
- Một thay đổi nhỏ ở bản rõ hoặc vector khởi tạo (IV) sẽ ảnh hưởng đến toàn bộ các khối mã phía sau.
- Ngược lại, quá trình giải mã lại có lợi thế là có thể song song hóa vì mỗi khối bản rõ có thể được khôi phục chỉ từ hai khối mã liên tiếp mà không cần chờ khối trước đó giải mã xong.
- Tuy nhiên, đặc tính này cũng tạo ra lỗ hổng: nếu giải mã sai IV thì chỉ khối đầu tiên bị lỗi, hoặc một lỗi bit trong bản mã sẽ chỉ làm hỏng khối hiện tại và thay đổi đúng 1 bit tương ứng ở khối kế tiếp.
- Đây chính là cơ sở cho các cuộc tấn công padding oracle (như POODLE). Để khắc phục vấn đề IV, phương pháp Explicit IV thường được dùng bằng cách thêm một khối ngẫu nhiên vào đầu bản rõ để bảo vệ tính toàn vẹn cho các dữ liệu quan trọng phía sau.
## PCBC
- PCBC được thiết kế để tăng khả năng lan truyền lỗi bằng cách XOR mỗi khối plaintext với cả khối plaintext và ciphertext trước đó trước khi mã hóa.
- Do đó, chỉ cần một sai sót nhỏ ở IV hoặc một bit lỗi trên đường truyền, toàn bộ các khối bản rõ phía sau sẽ bị hỏng hoàn toàn, thay vì chỉ ảnh hưởng cục bộ như CBC.

- Nếu tráo đổi vị trí của hai khối ciphertext cạnh nhau, lỗi sẽ bị triệt tiêu ngay sau đó.
## CFB
- CFB biến bộ mã hóa khối thành mã dòng tự đồng bộ, với quá trình giải mã gần như tương tự mã hóa CBC thực hiện ngược lại.
$$
\begin{aligned}
&C_i =
\begin{cases}
IV, & i = 0 \\
E_K(C_{i-1}) \oplus P_i, & \text{otherwise}
\end{cases} \\
&P_i = E_K(C_{i-1}) \oplus C_i
\end{aligned}
$$

$$
\begin{aligned}
&I_0 = IV \\
&C_i = E_K(P_i \oplus P_{i-1} \oplus C_{i-1}), \quad P_0 \oplus C_0 = IV \\
&P_i = D_K(C_i) \oplus P_{i-1} \oplus C_{i-1}, \quad P_0 \oplus C_0 = IV \\
&P_i = MSB_s(E_K(I_{i-1})) \oplus C_i
\end{aligned}
$$
- CFB mã hóa từng đoạn $s$-bit bằng thanh ghi dịch, giúp xử lý dữ liệu lẻ mà không cần padding.
- Hệ thống chỉ dùng hàm Mã hóa ($E_K$) cho cả hai chiều, giúp tiết kiệm tài nguyên và không cần hàm giải mã. Và nó có khả năng tự đồng bộ: lỗi sẽ tự biến mất sau khi bị đẩy hết ra khỏi thanh ghi (CFB-1 phục hồi tốt nhất, CFB-128 dễ hỏng nếu mất bit).
## OFB
- OFB lấy đầu ra trực tiếp của hàm $E_K$ làm đầu vào tiếp theo; lỗi bit đơn không lan truyền nhưng không thể tự đồng bộ nếu bị mất bit.
$$
\begin{aligned}
&I_0 = IV \\
&I_j = O_{j-1} \\
&O_j = E_K(I_j) \\
&C_j = P_j \oplus O_j \\
&P_j = C_j \oplus O_j
\end{aligned}
$$

- Do tính đối xứng nên quá trình mã hóa và giải mã của OFB là giống nhau.
## CTR
### Stream Cipher
- CTR không mã hóa trực tiếp dữ liệu. Thay vào đó, nó mã hóa một giá trị kết hợp giữa Nonce và Counter:

- Nonce (Number used once): Giá trị ngẫu nhiên, duy nhất cho mỗi phiên mã hóa.
- Counter: Bộ đếm tăng dần (thường bắt đầu từ 0) cho mỗi khối dữ liệu.
- Input Block: $Nonce \parallel Counter$ (Tổng độ dài phải bằng Block Size, ví dụ: 16 bytes cho AES).
- Quá trình mã hóa và giải mã sử dụng chung một logic (chỉ dùng hàm Encryption của Cipher):
**Mã hóa:** $C_i = P_i \oplus E_K(Nonce \parallel Counter_i)$
**Giải mã:** $P_i = C_i \oplus E_K(Nonce \parallel Counter_i)$
# ATTACK
## ECB Byte-at-a-time
- ECB Byte-at-a-time là một kỹ thuật tấn công kinh điển trong mật mã học, cho phép kẻ tấn công giải mã từng byte của một chuỗi bí mật (thường là Flag hoặc Cookie) được thêm vào sau dữ liệu của người dùng trước khi mã hóa ở chế độ ECB (Electronic Codebook).
$$
\begin{aligned}
&\text{Target Block: } E_K(P_{padding} || S_{1 \dots n}) \\
&\text{Brute-force byte } b: E_K(P_{padding} || S_{known} || b) \\
&\text{Condition: } \text{Match} \implies b = S_{next}
\end{aligned}
$$
- Chúng ta không phá mã AES, chúng ta đang "ép" thuật toán tiết lộ thông tin thông qua việc so sánh các mẫu bản mã tại một vị trí cố định.
- Hãy tưởng tượng bộ mã hóa AES như một cái khuôn cố định chỉ đúc được các khối 16 byte.
- Bình thường: Flag nằm cố định ở một vị trí nào đó trong các khối.
- Tấn công: Ta dùng Padding (chuỗi 'A') để làm "vật đẩy". Khi ta thêm 1 byte 'A', ta đẩy Flag dịch sang phải 1 byte.
--> Mục tiêu là đẩy Flag sao cho byte đầu tiên chưa biết luôn nằm ở vị trí thứ 16 của một khối. Tại vị trí này, nó trở thành "điểm yếu" duy nhất của khối đó.
- Sự khác biệt lớn trong cách tiếp cận này là việc chia làm 2 giai đoạn trong cùng một bước:
+ **Giai đoạn A:**
+ **Tạo Bản mẫu (The Template):** Ta gửi chuỗi Padding = 'A' * (Block_Size - 1 - len(found_flag)).Lúc này, byte cần tìm sẽ bị kéo vào vị trí cuối cùng của khối hiện tại.
+ Ví dụ: Khối 1 = [A A A A A A A A A A A A A A A ?] (Dấu ? là byte bí mật).Oracle trả về bản mã $C_{target}$. Ta chỉ quan tâm đến khối chứa dấu ?.
+ **Giai đoạn B:**
+ **Chế tạo bản sao (The Clone):** Ta tự tay xây dựng một khối giả lập: [A A A A A A A A A A A A A A A + char].Với char chạy từ 0-255.Nếu $E_K(\text{Khối giả lập}) == C_{target}$, nghĩa là char chính là byte bí mật.
- **Khi Flag vượt quá 16 byte** ta không cần thay đổi vị trí kiểm tra, mà ta "cuốn" dần Flag vào cửa sổ 16 byte đó.
+ Byte 1-16: Padding giảm dần từ 15 về 0.
+ Byte 17-32: Padding lại bắt đầu giảm từ 31 về 16.
+ Byte bí mật thứ 17 lúc này sẽ nằm ở vị trí cuối cùng của khối thứ
**Công thức tổng quát cho Padding:** $$len(padding) = (Block\_Size - 1) - (len(found\_flag) \pmod{Block\_Size})$$
https://aes.cryptohack.org/ecb_oracle/
- **ECB Oracle:**

```py=
import requests
url = "https://aes.cryptohack.org/ecb_oracle/"
s = requests.Session()
char = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_{}"
blocksize = 16
flag = "crypto{"
while True:
found = False
for a in char:
g = flag + a
pad_len = blocksize-(len(g)%blocksize)
pad = "A"* pad_len
pt = pad + g +pad
r = s.get(url + "encrypt/" + pt.encode().hex() + "/")
ct = bytes.fromhex(r.json()['ciphertext'])
size = pad_len + len(g)
if ct[: size]==ct[size: 2*size ]:
flag += a
found = True
break
if not found or flag.endswith("}"):
break
print(flag)
```
## CBC Bit Flipping Attack
- Điểm yếu của CBC nằm ở chỗ Ciphertext của khối trước ($C_{i-1}$) được dùng làm tham số XOR để tạo ra Plaintext của khối hiện tại ($P_i$).
- Khi thay đổi 1 byte tại vị trí k trong $C_{i-1}$, thì tại vị trí k tương ứng trong $P_i$ sẽ bị thay đổi theo ý muốn.$$C_{i-1}' = C_{i-1} \oplus P_i \oplus P_i'$$
- Khối $P_{i-1}$ sẽ bị "nát" hoàn toàn (garbage) vì nó trực tiếp đi qua hàm giải mã $D_K$ với đầu vào là Ciphertext đã bị sửa.
$$P_ {1}' = D_{K}(C_{1}') + P_0 + C_0$$ $$P_ {2}' = D_{K}(C_{2}') + P_{1}' + C_{1}'$$
- Nếu nội dung cần sửa nằm ở ngay khối $P_1$, không sửa Ciphertext mà sửa trực tiếp Vector khởi tạo (IV). Đây là cách tấn công "sạch" nhất vì nó không làm hỏng bất kỳ khối Plaintext nào.
- **Cốt lõi của CBC Bit-Flipping dựa trên việc triệt tiêu giá trị cũ bằng phép XOR và chèn vào giá trị mới thông qua khối Ciphertext liền trước.**
- https://aes.cryptohack.org/flipping_cookie/
**Challenges Flipping Cookie**

- Thao tác lật `True` thành `False`
```=py
old_str = "False"
new_str = "True;"
for i in range(len(old_str)):
iv[6 + i] = iv[6 + i] ^ ord(old_str[i]) ^ ord(new_str[i])
```
Hay cụ thể hơn là:
```=py
iv[6] = flip_byte(iv[6], 'F', 'T')
iv[7] = flip_byte(iv[7], 'a', 'r')
iv[8] = flip_byte(iv[8], 'l', 'u')
iv[9] = flip_byte(iv[9], 's', 'e')
iv[10] = flip_byte(iv[10], 'e', ';')
```
```=py
import requests
ciphertext_hex = "ae1f4e646a9833437ea939612f10fbe5e9c3499aaac939ed2d0ce9c7ffa71722"
iv = list(bytes.fromhex("3adf4b842546eae0cfcf2feb558b1fc7"))
old_str = "False"
new_str = "True;"
for i in range(len(old_str)):
iv[6 + i] = iv[6 + i] ^ ord(old_str[i]) ^ ord(new_str[i])
new_iv_hex = bytes(iv).hex()
url = f"https://aes.cryptohack.org/flipping_cookie/check_admin/{ciphertext_hex}/{new_iv_hex}/"
r = requests.get(url)
print(r.text)
```
- Ta được flag là"crypto{4u7h3n71c4710n_15_3553n714l}"
## CBC Padding Oracle Attack
- Tấn công Padding Oracle khai thác cơ chế kiểm tra tính hợp lệ của đệm (padding) trong chế độ mã hóa CBC. Mục tiêu cuối cùng là tìm ra Intermediate Bytes (giá trị sau khi giải mã khối nhưng trước khi XOR), từ đó suy ra nội dung Plaintext mà không cần biết khóa $K$.
- Trong chế độ CBC, quá trình giải mã một khối bản mã $C_i$ được thực hiện như sau:
+ Giải mã khối $C_i$ bằng khóa $K$ để thu được giá trị trung gian (Intermediate Value): $I_i = D_K(C_i)$.
+ XOR giá trị trung gian với khối bản mã trước đó $C_{i-1}$ (hoặc $IV$ nếu là khối đầu tiên) để có Plaintext:$$P_i = I_i \oplus C_{i-1}$$Đối với từng byte thứ $j$ trong khối:$$P_i[j] = I_i[j] \oplus C_{i-1}[j]$$Kẻ tấn công có thể thay đổi các byte của $C_{i-1}$ (gọi là $C_{i-1}'$) và gửi cặp $(C_{i-1}', C_i)$ tới máy chủ.
+ Dựa vào phản hồi (Padding hợp lệ hay lỗi), kẻ tấn công sẽ tìm được $I_i[j]$.
**Quy trình tấn công PKCS#7(Block size = 16)**
- Giả sử ta đang tấn công để tìm các byte của khối $P_i$ từ cuối lên đầu.
+ **Bước 1:** Tìm byte cuối cùng $I_i[16]$Ta muốn thay đổi byte cuối của khối trước đó $C_{i-1}'[16]$ sao cho sau khi giải mã, byte cuối của Plaintext mới là $0x01$.
+ Thử lần lượt $C_{i-1}'[16]$ từ $0x00$ đến $0xFF$.
+ Gửi $(C_{i-1}', C_i)$ tới Oracle. Khi Oracle trả về "Hợp lệ", ta có:$$I_i[16] \oplus C_{i-1}'[16] = 0x01 \implies I_i[16] = 0x01 \oplus C_{i-1}'[16]$$
+ Plaintext gốc: $P_i[16] = I_i[16] \oplus C_{i-1}[16]$
+ **Bước 2:** **Tìm byte kế cuối $I_i[15]$**
Lúc này ta cần đệm đúng là $0x02$ cho cả 2 byte cuối ($0x02, 0x02$).
+ **Cố định byte cuối:** Dựa trên $I_i[16]$ đã tìm ở Bước 1, ta điều chỉnh byte cuối của khối giả mạo:$$C_{i-1}''[16] = I_i[16] \oplus 0x02$$
+ **Tìm byte thứ 15:** Thử các giá trị $C_{i-1}''[15]$ cho đến khi Oracle trả về "Hợp lệ":$$I_i[15] \oplus C_{i-1}''[15] = 0x02 \implies I_i[15] = 0x02 \oplus C_{i-1}''[15]$$
+ **Plaintext gốc:** $P_i[15] = I_i[15] \oplus C_{i-1}[15]$
+ **Bước 3:** **Tổng quát hóa cho byte thứ $j$**
Để tìm byte $I_i[j]$, ta cần thiết lập các byte từ $j+1$ đến $16$ sao cho chúng giải mã ra giá trị đệm là $PaddingValue = (17 - j)$.
+ **Thiết lập các byte đã biết:** Với các byte $k > j$:$$C_{i-1}^*[k] = I_i[k] \oplus PaddingValue$$
+ **Brute-force byte $j$:** Thử $C_{i-1}^*[j]$ cho đến khi padding hợp lệ:$$I_i[j] = PaddingValue \oplus C_{i-1}^*[j]$$
+ **Plaintext gốc:** $P_i[j] = I_i[j] \oplus C_{i-1}[j]$
### Challenges Pad Thai

```python=
from Crypto.Util.Padding import unpad
from Crypto.Cipher import AES
from os import urandom
from utils import listener
FLAG = 'crypto{?????????????????????????????????????????????????????}'
class Challenge:
def __init__(self):
self.before_input = "Let's practice padding oracle attacks! Recover my message and I'll send you a flag.\n"
self.message = urandom(16).hex()
self.key = urandom(16)
def get_ct(self):
iv = urandom(16)
cipher = AES.new(self.key, AES.MODE_CBC, iv=iv)
ct = cipher.encrypt(self.message.encode("ascii"))
return {"ct": (iv+ct).hex()}
def check_padding(self, ct):
ct = bytes.fromhex(ct)
iv, ct = ct[:16], ct[16:]
cipher = AES.new(self.key, AES.MODE_CBC, iv=iv)
pt = cipher.decrypt(ct) # does not remove padding
try:
unpad(pt, 16)
except ValueError:
good = False
else:
good = True
return {"result": good}
def check_message(self, message):
if message != self.message:
self.exit = True
return {"error": "incorrect message"}
return {"flag": FLAG}
#
# This challenge function is called on your input, which must be JSON
# encoded
#
def challenge(self, msg):
if "option" not in msg or msg["option"] not in ("encrypt", "unpad", "check"):
return {"error": "Option must be one of: encrypt, unpad, check"}
if msg["option"] == "encrypt": return self.get_ct()
elif msg["option"] == "unpad": return self.check_padding(msg["ct"])
elif msg["option"] == "check": return self.check_message(msg["message"])
import builtins; builtins.Challenge = Challenge # hack to enable challenge to be run locally, see https://cryptohack.org/faq/#listener
listener.start_server(port=13421)
```
- Challenge tạo một chuỗi message ngẫu nhiên rồi tiến hành encrypt nó bằng CBC mode
Bên cạnh đó ta có hàm `check_padding()`
```python=
import json, string
from pwn import remote, logging
logging.disable(logging.CRITICAL)
HEX_CHARS = (string.digits + "abcdef").encode()
def solve():
def connect():
conn = remote('socket.cryptohack.org', 13421)
conn.recvuntil(b"send you a flag.\n")
return conn
io = connect()
def req(opt, **kwargs):
payload = {"option": opt, **kwargs}
io.sendline(json.dumps(payload).encode())
return json.loads(io.recvline().decode())
ct_raw = bytes.fromhex(req("encrypt")["ct"])
iv, blocks = ct_raw[:16], [ct_raw[i:i+16] for i in range(16, len(ct_raw), 16)]
res_msg = ""
history = [iv] + blocks
for b in range(2):
target, prev = blocks[b], history[b]
inter, pt = bytearray(16), bytearray(16)
i = 15
while i >= 0:
pad = 16 - i
t_iv = bytearray(16)
for j in range(i + 1, 16):
t_iv[j] = inter[j] ^ pad
found = False
for char_code in HEX_CHARS:
if b == 1 and i == 15 and (char_code ^ pad ^ prev[i]) == prev[i]:
continue
t_iv[i] = char_code ^ pad ^ prev[i]
if req("unpad", ct=(t_iv + target).hex()).get("result"):
inter[i], pt[i] = t_iv[i] ^ pad, char_code
print(f"Block {b+1} | Byte {i:2}: {chr(char_code)}", end='\r')
found, i = True, i - 1
break
if not found:
io.close()
io = connect()
res_msg += pt.decode()
print(f"\n[+] Khối {b+1}: {pt.decode()}")
print(f"\n[DONE] Message: {res_msg}")
print(f"[FLAG] {req('check', message=res_msg)}")
io.close()
if __name__ == "__main__":
solve()
```
- Ta được flag là **"crypto{if_you_ask_enough_times_you_usually_get_what_you_want}"**
### Challenges Paper Plane

- Source code:
```=python
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import os
KEY = ?
FLAG = ?
class AesIge:
def __init__(self, key):
self.cipher = AES.new(key, AES.MODE_ECB)
def encrypt(self, data, m0=os.urandom(16), c0=os.urandom(16)):
data = pad(data, 16, 'pkcs7')
last_block_plaintext = m0
last_block_ciphertext = c0
result = b''
for i in range(0, len(data), 16):
block = data[i: i + 16]
x = AesIge._xor_blocks(block, last_block_ciphertext)
x = self.cipher.encrypt(x)
x = AesIge._xor_blocks(x, last_block_plaintext)
result += x
last_block_plaintext = block
last_block_ciphertext = x
return result, m0, c0
def decrypt(self, data, m0, c0):
last_block_plaintext = m0
last_block_ciphertext = c0
result = b''
for i in range(0, len(data), 16):
block = data[i: i + 16]
x = AesIge._xor_blocks(block, last_block_plaintext)
x = self.cipher.decrypt(x)
x = AesIge._xor_blocks(x, last_block_ciphertext)
result += x
last_block_ciphertext = block
last_block_plaintext = x
if AesIge._is_pkcs7_padded(result):
return unpad(result, 16, 'pkcs7')
else:
return None
@staticmethod
def _is_pkcs7_padded(message):
padding = message[-message[-1]:]
return all(padding[i] == len(padding) for i in range(0, len(padding)))
@staticmethod
def _xor_blocks(a, b):
return bytes([x ^ y for x, y in zip(a, b)])
@chal.route('/paper_plane/encrypt_flag/')
def encrypt_flag():
ciphertext, m0, c0 = AesIge(KEY).encrypt(FLAG.encode())
return {"ciphertext": ciphertext.hex(), "m0": m0.hex(), "c0": c0.hex()}
@chal.route('/paper_plane/send_msg/<ciphertext>/<m0>/<c0>/')
def send_msg(ciphertext, m0, c0):
ciphertext = bytes.fromhex(ciphertext)
m0 = bytes.fromhex(m0)
c0 = bytes.fromhex(c0)
if len(ciphertext) % 16 != 0:
return {"error": "Data length must be a multiple of the blocksize!"}
if len(c0) != 16 or len(m0) != 16:
return {"error": "m0 and c0 must be 16 bytes long!"}
plaintext = AesIge(KEY).decrypt(ciphertext, m0, c0)
if plaintext is not None:
return {"msg": "Message received"}
else:
return {"error": "Can't decrypt the message."}
```
- Bài này là một biến thể nâng cao của Padding Oracle Attack, sử dụng IGE mode (Infinite Garble Extension) thay vì CBC thông thường.
- Chế độ IGE (Infinite Garble Extension) có công thức giải mã phức tạp hơn CBC một chút vì nó sử dụng cả Ciphertext và Plaintext của khối trước đó để giải mã khối hiện tại.
- Phân tích class AesIge.decrypt():
```python=
def decrypt(self, data, m0, c0):
last_block_plaintext = m0
last_block_ciphertext = c0
result = b''
for i in range(0, len(data), 16):
block = data[i: i + 16]
x = AesIge._xor_blocks(block, last_block_plaintext)
x = self.cipher.decrypt(x)
x = AesIge._xor_blocks(x, last_block_ciphertext)
result += x
last_block_ciphertext = block
last_block_plaintext = x
```
$$P_i = D_K(C_i \oplus M_{i-1}) \oplus C_{i-1}$$
- Trong tấn công này, chúng ta lợi dụng việc có thể kiểm soát hoàn toàn bộ ba tham số đầu vào $(C, M, C_{prev})$ của hàm send_msg().Để giải mã khối ciphertext $C_i$, ta cố định hai tham số đầu và chỉ thay đổi tham số thứ ba để biến nó thành một "IV giả" phục vụ tấn công Padding Oracle.
- Mục tiêu: Tìm giá trị trung gian $I = D_K(C_i \oplus M_{i-1})$.
+ Tham số gửi đi: * ciphertext: Khối $C_i$ thật cần giải mã.
+ m0: Plaintext $M_{i-1}$ thật (đã tìm được từ bước giải mã khối trước).
+ c0: Khối giả lập $c_{fake}$ dùng để brute-force từng byte.
- Khi gửi request lên server, server thực hiện tính toán:$$P'_i = I \oplus c_{fake}$$
- **Tìm byte cuối của $I$:** Thay đổi byte cuối của $c_{fake}$ từ 0x00 đến 0xFF. Khi server trả về Message received, ta biết byte cuối của $P'_i$ là 0x01.$ $\implies I[16] = 0x01 \oplus c_{fake}[16]$$
- **Tìm các byte tiếp theo:** Tương tự như CBC, ta điều chỉnh các byte cuối đã biết của $c_{fake}$ để tạo ra padding mong muốn ($0x02, 0x03,...$) và tiếp tục brute-force byte đứng trước nó.
- **Tính Plaintext thật ($P_i$):** Sau khi tìm đủ 16 byte của giá trị trung gian $I$, ta tính nội dung thực tế bằng cách XOR với ciphertext thật của khối trước ($C_{i-1}$):$$P_i = I \oplus C_{i-1}$$
```python=
import requests
import time
BASE_URL = "https://aes.cryptohack.org/paper_plane/"
session = requests.Session()
def get_encrypt_flag():
r = session.get(f"{BASE_URL}/encrypt_flag/")
data = r.json()
return (
bytes.fromhex(data['ciphertext']),
bytes.fromhex(data['m0']),
bytes.fromhex(data['c0'])
)
def oracle(ct_block, m0, c0):
url = f"{BASE_URL}/send_msg/{ct_block.hex()}/{m0.hex()}/{c0.hex()}/"
for _ in range(10):
try:
r = session.get(url, timeout=5)
resp = r.json()
if "error" in resp:
return False
return True
except (requests.exceptions.RequestException, Exception):
time.sleep(0.5)
continue
return False
def decrypt_block(target_ct_block, prev_pt, prev_ct):
I = [0] * 16
pt = [0] * 16
fake_c0 = bytearray(16)
for byte_index in range(15, -1, -1):
pad_val = 16 - byte_index
for k in range(byte_index + 1, 16):
fake_c0[k] = I[k] ^ pad_val
found = False
for guess in range(256):
fake_c0[byte_index] = guess
if oracle(target_ct_block, prev_pt, fake_c0):
i_val = guess ^ pad_val
I[byte_index] = i_val
pt_val = i_val ^ prev_ct[byte_index]
pt[byte_index] = pt_val
char = chr(pt_val) if 32 <= pt_val <= 126 else '?'
print(f" [Byte {byte_index:02}] Guess: {hex(guess)} -> PT: {char}")
found = True
break
return bytes(pt)
def main():
full_ct, real_m0, real_c0 = get_encrypt_flag()
blocks = [full_ct[i:i+16] for i in range(0, len(full_ct), 16)]
prev_pt = real_m0
prev_ct = real_c0
for i, block in enumerate(blocks):
decrypted_block = decrypt_block(block, prev_pt, prev_ct)
final_plaintext += decrypted_block
prev_pt = decrypted_block
prev_ct = block
print(final_plaintext.decode())
if __name__ == "__main__":
main()
```
- Ta được flag là "crypto{h3ll0_t3l3gr4m}"
# SYMMETRIC CIPHERS
- Mật mã khóa đối xứng là thuật toán sử dụng cùng một khóa để mã hóa và giải mã dữ liệu.Mục đích là sử dụng các khóa bí mật ngắn để gửi các tin nhắn dài một cách an toàn và hiệu quả.
- Mật mã khóa đối xứng nổi tiếng nhất là Tiêu chuẩn mã hóa nâng cao (AES), được tiêu chuẩn hóa vào năm 2001. Nó phổ biến đến mức các bộ xử lý hiện đại thậm chí còn chứa các bộ hướng dẫn đặc biệt để thực hiện các hoạt động AES.Chuỗi thử thách đầu tiên ở đây sẽ hướng dẫn bạn cách hoạt động bên trong của AES, cho bạn thấy cách các thành phần riêng biệt của nó phối hợp với nhau để biến nó thành một mật mã an toàn.Cuối cùng, bạn sẽ xây dựng được mã của riêng mình để thực hiện giải mã AES!
- Chúng ta có thể chia mật mã khóa đối xứng thành hai loại, mật mã khối và mật mã dòng.Mật mã khối chia văn bản gốc thành các khối có độ dài cố định và gửi từng khối thông qua chức năng mã hóa cùng với một khóa bí mật.Trong khi đó, các luồng mật mã mã hóa từng byte văn bản gốc bằng cách XOR một luồng khóa giả ngẫu nhiên với dữ liệu.AES là mật mã khối nhưng có thể được chuyển thành mật mã luồng bằng các chế độ hoạt động như CTR.
- Mật mã khối chỉ xác định cách mã hóa và giải mã các khối riêng lẻ và phải sử dụng một chế độ hoạt động để áp dụng mật mã cho các tin nhắn dài hơn.Đây là điểm mà việc triển khai trong thế giới thực thường thất bại một cách ngoạn mục, vì các nhà phát triển không hiểu được những tác động tinh vi của việc sử dụng các chế độ cụ thể.Phần còn lại của các thử thách cho thấy bạn tấn công các hành vi lạm dụng phổ biến ở nhiều chế độ khác nhau.
## HOW AES WORKS
### Keyed Permutations

- Với mỗi khóa cụ thể, AES sẽ biến đổi một khối dữ liệu đầu vào thành một khối đầu ra duy nhất.AES-128 xử lý các khối dữ liệu có kích thước 128 bit (16 byte) bằng một khóa cũng dài 128 bit.Vì là phép hoán vị, ta có thể đảo ngược quá trình (giải mã) để tìm lại dữ liệu gốc từ bản mã, miễn là dùng đúng khóa.Phải có sự tương ứng 1-1 giữa khối đầu vào và đầu ra để đảm bảo dữ liệu sau khi giải mã luôn chính xác và duy nhất.
- Thuật ngữ toán học cho "Sự tương ứng 1-1" này là song ánh (Bijection)
- Vậy ta được flag là **`crypto{bijection}`**
### Resisting Bruteforce

- AES được coi là an toàn vì kết quả của nó không khác gì một phép hoán vị ngẫu nhiên và cách duy nhất để phá giải là tấn công vét cạn (bruteforce) — một việc bất khả thi với khóa 128-bit (mất hàng tỷ năm ngay cả với sức mạnh của mạng lưới Bitcoin). Dù về mặt lý thuyết, AES được coi là đã bị "phá" bởi một cuộc tấn công làm giảm độ bảo mật xuống còn 126.1 bit, nhưng khoảng cách an toàn vẫn là cực kỳ lớn và không gây rủi ro thực tế. Ngoài ra, để đối phó với tương lai của máy tính lượng tử (vốn có thể giảm một nửa độ bảo mật của các hệ mã hóa đối xứng), người ta khuyến nghị dùng AES-256 để duy trì mức an toàn tương đương 128-bit.
- Tên của cuộc tấn công đơn khóa (single-key attack) tốt nhất chống lại AES là **Biclique Attack.**
- Cuộc tấn công này được công bố vào năm 2011 bởi Bogdanov, Khovratovich và Rechberger. Nó giúp giảm độ phức tạp của AES-128 xuống còn khoảng $2^{126.1}$ phép tính, nhanh hơn khoảng 4 lần so với vét cạn, nhưng vẫn là một con số khổng lồ mà máy tính hiện nay không thể thực hiện được.
- Vậy ta có flag là **`crypto{biclique}`**
### Structure of AES


- Đề kiu viết hàm Matrix2bytes để biến ma trận đó trở lại thành byte và gửi văn bản gốc kết quả làm cờ.
- Mở file `matrix.py` đề cho ta thấy hàm matrix2bytes thì thấy còn trống nên mik sẽ hoàn thiện hàm đó ròi chạy.
```=py
def matrix2bytes(matrix):
return bytes([byte for col in matrix for byte in col])
```
- Ta được flag là **`crypto{inmatrix}`**
### Round Keys

- Yêu cầu của đề là hoàn thành hàm add_round_key, sau đó sử dụng hàm Matrix2bytes để lấy cờ tiếp theo.
```=py
state = [
[206, 243, 61, 34],
[171, 11, 93, 31],
[16, 200, 91, 108],
[150, 3, 194, 51],
]
round_key = [
[173, 129, 68, 82],
[223, 100, 38, 109],
[32, 189, 53, 8],
[253, 48, 187, 78],
]
def add_round_key(s, k):
for i in range(4):
for j in range(4):
s[i][j] = s[i][j] ^ k[i][j]
return s
print(add_round_key(state, round_key))
```
- Hoàn thành hàm add_round_key ta được một ma trận 4x4
```=py
[[99, 114, 121, 112], [116, 111, 123, 114], [48, 117, 110, 100], [107, 51, 121, 125]]
```
- Đưa vào hàm Matrix2bytes ta được flag **`crypto{r0undk3y}`**
### Confusion through Substitution

- Trong hình đang mô tả 1 bước quen thuộc là`SubByte`
- Yêu cầu triển khai sub_byte, gửi ma trận trạng thái qua hộp S nghịch đảo rồi chuyển đổi nó thành byte để lấy flag.
- Ta sẽ hoàn thiện hàm sub_byte để lấy flag.
```=py
def sub_bytes(state):
for i in range(4):
for j in range(4):
state[i][j] = s_box[state[i][j]]
```
hay
```=py
def sub_bytes(state, sbox):
return [[sbox[b] for b in row] for row in state]
print(sub_bytes(state, sbox=inv_s_box))
```
- Sau khi chạy chương trình sẽ cho ra 1 ma trận 4x4. Đưa vào file matrix.py hàm matrix2bytes sẽ chuyển ma trận thành chuỗi byte ta được flag là **'crypto{l1n34rly}'**
### Diffusion through Permutation

```=py
matrix = [
[99, 114, 121, 112],
[116, 111, 123, 105],
[110, 109, 97, 116],
[114, 105, 120, 125],
]
matrix = [
[99, 114, 121, 112],
[116, 111, 123, 114],
[48, 117, 110, 100],
[107, 51, 121, 125]
]
matrix = [[99, 114, 121, 112], [116, 111, 123, 100], [49, 102, 102, 85], [115, 51, 82, 125]]
plaintext = int.from_bytes([item for row in matrix for item in row])
print(long_to_bytes((plaintext)))
```
### Bringing It All Together

- Mở file `aes_decrypt.py` ta thấy đây là sự kết hợp của các chall trước mô tả đầy đủ quá trình decrypt. Nhưng còn thiếu 4 hàm,chúng ta chỉ việc hoàn thành 4 hàm này là InvShiftRows,AddRoundKey,InvSubBytes,InvMixColumns.
```python
N_ROUNDS = 10
key = b'\xc3,\\\xa6\xb5\x80^\x0c\xdb\x8d\xa5z*\xb6\xfe\\'
ciphertext = b'\xd1O\x14j\xa4+O\xb6\xa1\xc4\x08B)\x8f\x12\xdd'
s_box = (
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,
)
def bytes2matrix(data):
return [list(data[i:i+4]) for i in range(0, 16, 4)]
def expand_key(master_key):
"""
Expands and returns a list of key matrices for the given master_key.
"""
# Round constants https://en.wikipedia.org/wiki/AES_key_schedule#Round_constants
r_con = (
0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40,
0x80, 0x1B, 0x36, 0x6C, 0xD8, 0xAB, 0x4D, 0x9A,
0x2F, 0x5E, 0xBC, 0x63, 0xC6, 0x97, 0x35, 0x6A,
0xD4, 0xB3, 0x7D, 0xFA, 0xEF, 0xC5, 0x91, 0x39,
)
# Initialize round keys with raw key material.
key_columns = bytes2matrix(master_key)
iteration_size = len(master_key) // 4
# Each iteration has exactly as many columns as the key material.
i = 1
while len(key_columns) < (N_ROUNDS + 1) * 4:
# Copy previous word.
word = list(key_columns[-1])
# Perform schedule_core once every "row".
if len(key_columns) % iteration_size == 0:
# Circular shift.
word.append(word.pop(0))
# Map to S-BOX.
word = [s_box[b] for b in word]
# XOR with first byte of R-CON, since the others bytes of R-CON are 0.
word[0] ^= r_con[i]
i += 1
elif len(master_key) == 32 and len(key_columns) % iteration_size == 4:
# Run word through S-box in the fourth iteration when using a
# 256-bit key.
word = [s_box[b] for b in word]
# XOR with equivalent word from previous iteration.
word = bytes(i^j for i, j in zip(word, key_columns[-iteration_size]))
key_columns.append(word)
# Group key words in 4x4 byte matrices.
return [key_columns[4*i : 4*(i+1)] for i in range(len(key_columns) // 4)]
inv_s_box = (
0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D,
)
r_con = (0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36)
xtime = lambda a: ((a << 1) ^ (0x1B if a & 0x80 else 0x00)) & 0xFF
def add_round_key(s, k):
for i in range(4):
for j in range(4): s[i][j] ^= k[i][j]
def inv_sub_bytes(s):
for i in range(4):
for j in range(4): s[i][j] = inv_s_box[s[i][j]]
def inv_shift_rows(s):
s[0][1], s[1][1], s[2][1], s[3][1] = s[3][1], s[0][1], s[1][1], s[2][1]
s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
s[0][3], s[1][3], s[2][3], s[3][3] = s[1][3], s[2][3], s[3][3], s[0][3]
def mix_columns(s):
for c in s:
t = c[0] ^ c[1] ^ c[2] ^ c[3]
c[:] = [c[i] ^ t ^ xtime(c[i] ^ c[(i+1)%4]) for i in range(4)]
def inv_mix_columns(s):
for c in s:
u, v = xtime(xtime(c[0]^c[2])), xtime(xtime(c[1]^c[3]))
c[0]^=u; c[1]^=v; c[2]^=u; c[3]^=v
mix_columns(s)
def key_expansion(key):
w = [list(key[i:i+4]) for i in range(0, 16, 4)]
for i in range(4, 44):
temp = list(w[i-1])
if i % 4 == 0:
temp = [s_box[b] for b in temp[1:] + temp[:1]]
temp[0] ^= r_con[i//4]
w.append([w[i-4][j] ^ temp[j] for j in range(4)])
return [w[i:i+4] for i in range(0, 44, 4)]
def decrypt(key, ciphertext):
round_keys = expand_key(key)
state = bytes2matrix(ciphertext)
add_round_key(state, round_keys[10])
for i in range(N_ROUNDS - 1, 0, -1):
inv_sub_bytes(state)
inv_shift_rows(state)
inv_mix_columns(state)
tk = [list(r) for r in round_keys[i]]
inv_mix_columns(tk)
add_round_key(state, tk)
inv_sub_bytes(state)
inv_shift_rows(state)
add_round_key(state, round_keys[0])
plaintext = bytes([b for col in state for b in col])
return plaintext
print(decrypt(key, ciphertext))
```
- Ta được flag là "crypto{MYAES128}"
## Symmetric Starter
### Modes of Operation Starter

- Nhấp vào liên kết ta dẫn đến 1 trang web

- Nhìn vào đây


- Sau khi lấy được ciphertext ta gửi ciphertext này đến web và lấy plaintext.

- Sau khi được plaintext thì mik sẽ chuyển sang dạng bytes để lấy flag

flag là "crypto{bl0ck_c1ph3r5_4r3_f457_!}"
### Passwords as Keys

- Làm như chall trước ta lấy được ciphertext

- Cái mik cần bây giờ là password.Ta sẽ đi tới trang web https://gist.githubusercontent.com/wchargin/8927565/raw/d9783627c731268fb2935a731a618aa8e95cf465/words
- Thấy rất nhiều password. Chúng ta ko thể thử từng cái 1. Mà mik sẽ brute-force xem cái password nào đúng.
```python
from Crypto.Cipher import AES
import hashlib
import requests
url = 'https://gist.githubusercontent.com/wchargin/8927565/raw/d9783627c731268fb2935a731a618aa8e95cf465/words'
r = requests.get(url)
if r.status_code==200:
b = r.text.splitlines()
ciphertext_hex = 'c92b7734070205bdf6c0087a751466ec13ae15e6f1bcdd3f3a535ec0f4bbae66'
ciphertext = bytes.fromhex(ciphertext_hex)
for key in b:
keys = hashlib.md5(key.encode()).digest()
cipher = AES.new(keys, AES.MODE_ECB)
decrypted = cipher.decrypt(ciphertext)
if b'crypto' in decrypted:
print(decrypted)
```
- Ta được flag là "crypto{k3y5__r__n07__p455w0rdz?}"
## Block Ciphers 1
### ECB CBC WTF

- Trong CBC :
$Plaintext_1 = Decrypt(Ciphertext_1) \oplus IV$$Plaintext_i = Decrypt(Ciphertext_i) \oplus Ciphertext_{i-1}$ (với $i > 1$)
Để có $P_1$: Lấy $D_1$ XOR với $IV$ (vì khối đầu tiên trong CBC luôn được "trộn" với IV).$P_1 = D_1 \oplus IV$
Để có $P_2$: Lấy $D_2$ XOR với khối bản mã đầu tiên ($C_1$).$P_2 = D_2 \oplus C_1$
Kết hợp $P_1$ và $P_2$ lịa thì ta sẽ có flag hoàn chỉnh
```python
import requests
url = "https://aes.cryptohack.org/ecbcbcwtf/"
ct = (url+'encrypt_flag/')
c = requests.get(ct)
ciphertext = c.json()['ciphertext']
iv = bytes.fromhex(ciphertext[:32])
ct1 = bytes.fromhex(ciphertext[32:64])
ct2 = bytes.fromhex(ciphertext[64:96])
pt = (requests.get(url+'decrypt/'+ciphertext)).json()['plaintext']
plain1 = bytes.fromhex(pt[32:64])
plain2 = bytes.fromhex(pt[64:96])
p1 =bytes(a ^ b for a, b in zip(plain1, iv))
p2= (bytes(a ^ b for a, b in zip(plain2, ct1)))
print(p1+p2)
```
- Ta được flag là "crypto{3cb_5uck5_4v01d_17_!!!!!}"
### Lazy CBC

```pyth=
from Crypto.Cipher import AES
KEY = ?
FLAG = ?
@chal.route('/lazy_cbc/encrypt/<plaintext>/')
def encrypt(plaintext):
plaintext = bytes.fromhex(plaintext)
if len(plaintext) % 16 != 0:
return {"error": "Data length must be multiple of 16"}
cipher = AES.new(KEY, AES.MODE_CBC, KEY)
encrypted = cipher.encrypt(plaintext)
return {"ciphertext": encrypted.hex()}
@chal.route('/lazy_cbc/get_flag/<key>/')
def get_flag(key):
key = bytes.fromhex(key)
if key == KEY:
return {"plaintext": FLAG.encode().hex()}
else:
return {"error": "invalid key"}
@chal.route('/lazy_cbc/receive/<ciphertext>/')
def receive(ciphertext):
ciphertext = bytes.fromhex(ciphertext)
if len(ciphertext) % 16 != 0:
return {"error": "Data length must be multiple of 16"}
cipher = AES.new(KEY, AES.MODE_CBC, KEY)
decrypted = cipher.decrypt(ciphertext)
try:
decrypted.decode() # ensure plaintext is valid ascii
except UnicodeDecodeError:
return {"error": "Invalid plaintext: " + decrypted.hex()}
return {"success": "Your message has been received"}
```
- Hệ thống sử dụng chế độ CBC với $IV = Key$. Đây là lỗi nghiêm trọng vì có thể lộ Key thông qua quá trình giải mã.
- **Mã hóa khối 0:** Thực hiện mã hóa $P_1 = 0$ để lấy $C_1$. Khi đó:$$C_1 = E_K(P_1 \oplus IV) = E_K(0 \oplus Key) = E_K(Key)$$$$\implies D_K(C_1) = Key$$
- Hàm **receive()** chỉ in ra plaintext dưới dạng hex nếu giải mã ra ký tự ASCII không hợp lệ. Để bắt server in ra $D_K(C_1)$, ta chèn thêm một khối đệm (pad) lên trước $C_1$:$$C'_{fake} = pad \parallel C_1$$
- Khi giải mã khối thứ 2 của chuỗi giả mạo:$$P'_2 = D_K(C_1) \oplus pad$$Nếu chọn $pad = 0$, ta có: $Key = P'_2$.
```pyth=
import requests
from Crypto.Util.number import *
url = "https://aes.cryptohack.org/lazy_cbc/"
ct1 =(requests.get(url+ "encrypt/"+"00000000000000000000000000000000")).json()['ciphertext']
ct = ct1 + 16*"00" + ct1
pt = (requests.get(url+"receive/"+ct)).json()['error'].split(": ")[1]
p1 = bytes.fromhex(pt[:32])
p3 = bytes.fromhex(pt[64:96])
key = bytes([a^b for a,b in zip(p1,p3)]).hex()
flag = bytes.fromhex((requests.get(url + "get_flag/" + key )).json()['plaintext'])
print(flag.decode())
```
- Ta được flag là **"crypto{50m3_p30pl3_d0n7_7h1nk_IV_15_1mp0r74n7_?}"**
# BT AES
(https://hackmd.io/@5YUTfT57RUSXetk6ivHtSw/Hy0GPysL-l)