# **Keyed Permutations**
Đề cập đến ánh xạ tương ứng 1-1.
Nghĩa là ánh xạ mỗi khối đầu vào có thể có thành một khối đầu ra duy nhất, với một khóa xác định. Thuật ngữ toán học cho mối quan hệ này là ánh xạ 1-1. Điều này đảm bảo mỗi khối dữ liệu đầu vào sẽ được ánh xạ đến một khối dữ liệu đầu ra duy nhất và ngược lại. Điều này đảm bảo tính duy nhất của quá trình mã hóa và giải mã, nơi mà Ciphertext (văn bản đã được mã hóa) sẽ được giải mã trở lại thành plaintext (văn bản gốc) tương ứng.
# **Resisting Bruteforce**
AES (Advanced Encryption Standard) là một thuật toán mã khối an toàn, đảm bảo rằng mỗi khối dữ liệu đầu vào được ánh xạ tới một khối dữ liệu đầu ra duy nhất thông qua một khóa. Đối với AES-128, khối dữ liệu là 128 bit (16 byte) và khóa cũng có độ dài 128 bit (nghĩa là phải thử 2^128 lần thì moiws tìm ra khóa đúng).
Việc thử mọi khóa trong không gian khóa 128-bit của AES-128 là rất khó khăn. Đã có vụ tấn công được phát hiện rằng giảm độ khó của việc tìm ra khóa từ 128 bit xuống còn khoảng 126.1 bit nhưng AES vẫn được xem là an toàn trong thực tế.
Cuộc tấn công đơn khóa tốt nhất đối với AES được gọi là "Biclique attacks"
# **Structure of AES**
Bài này cho ta cách chuyển đổi giữa dạng ma trận 4*4 và mảng 16 byte và ngược lại.
-Bài đã cho ta sẵn hàm để chuyển từ byte sang ma trận
```
def bytes2matrix(text):
""" Converts a 16-byte array into a 4x4 matrix. """
return [list(text[i:i+4]) for i in range(0, len(text), 4)]
```
Ý nghĩa đó là chia chuỗi text thành các phần bằng nhau, mỗi phần là 4 byte (4 ký tự). `text[i:i+4]`sẽ lấy text từ vị trí thứ i đến i+4 và i có các giá trị ban đầu bằng 0 và bước nhảy bằng 4 và nằm trong phạm vị độ dài của text.
Vì vậy, biểu thức [list(text[i:i+4]) for i in range(0, len(text), 4)] tạo ra một list, trong đó mỗi phần tử là một list con chứa 4 ký tự liên tiếp của chuỗi text.
-Để chuyển từ ma trận thành text, ta chỉ cần ghép các phần tử trong list con lại là xong.
```
def matrix2bytes(matrix):
""" Converts a 4x4 matrix into a 16-byte array. """
text = ''
for i in range(len(matrix)):
for j in range(4):
text += chr(matrix[i][j])
return text
```

# **Round Keys**
Bài này ta chỉ cần xor từng phần tử tương ứng giữa ma trận state và ma trận round_key, sau đó in ra ở dạng ascii.
```
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):
result = ""
for i in range(4):
for j in range(4):
result += chr(s[i][j] ^ k[i][j])
return result
print(add_round_key(state, round_key))
```

# **Confusion through Substitution**
s_box: được sử dụng trong quá trình mã hóa AES. Nó được thiết kế sao cho mỗi giá trị byte đầu vào được thay thế bằng một giá trị khác tương ứng trong phạm vi từ 0 đến 255, tạo ra một phép thay thế không tuyến tính và không dễ dàng đoán trước được.
inv_s_box: Đây là S-box nghịch đảo, được sử dụng trong quá trình giải mã để hoàn nguyên lại dữ liệu đã được mã hóa.

Hàm sub_bytes trong AES được sử dụng để thực hiện bước thay thế byte trong mỗi block của dữ liệu đầu vào bằng các giá trị từ một S-box cụ thể.
Trong quá trình giải mã, hàm sub_bytes sẽ thực hiện thao tác ngược lại, thay thế các byte của block đã mã hóa bằng các giá trị từ một S-box khác (S-box nghịch đảo inv_s_box), để khôi phục dữ liệu ban đầu.
```
def sub_bytes(s, sbox=s_box):
result=""
for i in range(4):
for j in range(4):
result += chr(sbox[s[i][j]])
return result
```
sbox[s[i][j]] sẽ trả về 1 index để truy cập đến vị trị tương ứng trong sbox và in ra ở dạng ascii.
# **Diffusion through Permutation**
Bước tiếp theo trong AES là shift row và mix column. Ở vòng cuối sẽ k có mix column.

```
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]
def inv_shift_rows(s):
s[1][1], s[2][1], s[3][1], s[0][1] = s[0][1], s[1][1], s[2][1], s[3][1]
s[2][2], s[3][2], s[0][2], s[1][2] = s[0][2], s[1][2], s[2][2], s[3][2]
s[3][3], s[0][3], s[1][3], s[2][3] = s[0][3], s[1][3], s[2][3], s[3][3]
# learned from http://cs.ucsb.edu/~koc/cs178/projects/JT/aes.c
xtime = lambda a: (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1)
def mix_single_column(a):
# see Sec 4.1.2 in The Design of Rijndael
t = a[0] ^ a[1] ^ a[2] ^ a[3]
u = a[0]
a[0] ^= t ^ xtime(a[0] ^ a[1])
a[1] ^= t ^ xtime(a[1] ^ a[2])
a[2] ^= t ^ xtime(a[2] ^ a[3])
a[3] ^= t ^ xtime(a[3] ^ u)
def mix_columns(s):
for i in range(4):
mix_single_column(s[i])
def inv_mix_columns(s):
# see Sec 4.1.3 in The Design of Rijndael
for i in range(4):
u = xtime(xtime(s[i][0] ^ s[i][2]))
v = xtime(xtime(s[i][1] ^ s[i][3]))
s[i][0] ^= u
s[i][1] ^= v
s[i][2] ^= u
s[i][3] ^= v
mix_columns(s)
state = [
[108, 106, 71, 86],
[96, 62, 38, 72],
[42, 184, 92, 209],
[94, 79, 8, 54],
]
inv_mix_columns(state)
inv_shift_rows(state)
result=""
for i in range(4):
for j in range(4):
result += chr(state[i][j])
print(result)
```
Bài này chỉ cần viết mỗi hàm inv_shift_rows(s) bằng cách viết ngược lại với hàm shift_rows.
# **Bringing It All Together**
Bài cuối này là áp dụng tất cả các bước của mật mã AES.
Quá trình mã hóa:
1. AddRoundKey
2. SubBytes
3. ShiftRows
4. MixColumns
5. Round Key Expansion
Giải mã:
1. Round Key Expansion (tính toán trước mỗi vòng, nhưng không phải là một phần của mỗi vòng)
2. InvShiftRows
3. InvSubBytes
4. AddRoundKey
5. InvMixColumns
Ta copy hết tất cả các hàm đã dùng ở challenge trước và dùng 1 cách hopw lý là được. (vẫn đang trong quá trình đọc hiểu 1 số hàm họ viết sẵn, hiện đang nhìn tên hàm để đoán chức năng).
Cần chú ý là ở vòng cuối k có mix colimn nên ta sẽ bớt 1 lần inv_mix_column. Vì có 10 vòng nên các bước sẽ thực hiện 10 lần trừ inv_mix_column là 9 lần.
Bước cuối cùng đó là chuyển từ ma trận về flag nhờ hàm matrix2bytes.
```
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,
)
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,
)
def add_round_key(s, k):
for i in range(4):
for j in range(4):
s[i][j]^=k[i][j]
def bytes2matrix(text):
""" Converts a 16-byte array into a 4x4 matrix. """
return [list(text[i:i+4]) for i in range(0, len(text), 4)]
def matrix2bytes(matrix):
""" Converts a 4x4 matrix into a 16-byte array. """
res = ""
for i in range(4):
for j in range(4):
res += chr(matrix[i][j])
return res
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]
def inv_shift_rows(s):
s[1][1], s[2][1], s[3][1], s[0][1] = s[0][1], s[1][1], s[2][1], s[3][1]
s[2][2], s[3][2], s[0][2], s[1][2] = s[0][2], s[1][2], s[2][2], s[3][2]
s[3][3], s[0][3], s[1][3], s[2][3] = s[0][3], s[1][3], s[2][3], s[3][3]
xtime = lambda a: (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1)
def mix_single_column(a):
# see Sec 4.1.2 in The Design of Rijndael
t = a[0] ^ a[1] ^ a[2] ^ a[3]
u = a[0]
a[0] ^= t ^ xtime(a[0] ^ a[1])
a[1] ^= t ^ xtime(a[1] ^ a[2])
a[2] ^= t ^ xtime(a[2] ^ a[3])
a[3] ^= t ^ xtime(a[3] ^ u)
def mix_columns(s):
for i in range(4):
mix_single_column(s[i])
def inv_mix_columns(s):
# see Sec 4.1.3 in The Design of Rijndael
for i in range(4):
u = xtime(xtime(s[i][0] ^ s[i][2]))
v = xtime(xtime(s[i][1] ^ s[i][3]))
s[i][0] ^= u
s[i][1] ^= v
s[i][2] ^= u
s[i][3] ^= v
mix_columns(s)
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 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)]
def decrypt(key, ciphertext):
round_keys = expand_key(key) # Remember to start from the last round key and work backwards through them when decrypting
# Convert ciphertext to state matrix
text = bytes2matrix(ciphertext)
# Initial add round key step
add_round_key(text, round_keys[10])
for i in range(N_ROUNDS - 1, 0, -1):
inv_shift_rows(text)
inv_sub_bytes(text)
add_round_key(text, round_keys[i])
inv_mix_columns(text)
# Run final round (skips the InvMixColumns step)
inv_shift_rows(text)
inv_sub_bytes(text)
add_round_key(text, round_keys[0])
# Convert state matrix to plaintext
plaintext = matrix2bytes(text)
return plaintext
print(decrypt(key, ciphertext))
```
