---
# System prepended metadata

title: AES - Advanced Encryption Standard
tags: [cryptography]

---

# AES - Advanced Encryption Standard
https://hackmd.io/@L1ttl3NoPro/SyUIJ_Su-e
Là kỹ thuật mã hóa khối, dữ liệu đưa vào được chia thành các block 128 bit tạo thành ma trận 4x4 ( mỗi ô 8 bit ) - gọi là state, sau đó kết hợp với key 128, 192, 256 bit ( ở đây ta nói về AES-128 nên sử dụng key 128 bit ) qua các bước Add Round Key, Sub Bytes, Shift Rows, Mix columns nhiều lần ( 10, 12, 14 tùy theo độ dài của key) tạo thành ciphertext.
Lưu ý: Trong AES các bước thực hiện đều xử lí theo cột ( column major ), khi chuyển từ ma trận sang bytes thì xử lí theo hàng ( row major ).
# Cấu trúc
![image](https://hackmd.io/_uploads/rkvBUAmb-l.png)
## KeyExpansion
Là bước biến key 128 bit thành ma trận RoundKey 4x4 để cho bước AddRoundKey ở sau.
## AddRoundKey
Là bước xor các bytes trong state với RoundKey ở vị trí tương ứng nhau tạo thành 1 ma trận state mới.
![image](https://hackmd.io/_uploads/rkhiORXbWg.png)
### Rounds Key
```
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):
    return [[s[j][i] ^ k[j][i] for j in range(4)] for i in range(4)]
def matrix2bytes(matrix):
    return bytes(matrix[b][a] for a in range (4) for b in range(4)).decode()

print(matrix2bytes(add_round_key(state, round_key)))
```
## SubBytes
Ở bước này ta có một ma trận S_box( Substitution box ):
```
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,
)
```
S_box được tạo theo công thức trong GF(2^8):
![image](https://hackmd.io/_uploads/Bk7usCX-Zg.png)

Trong bước SubBytes, thay các bytes trong state thành các bytes trong S_box được quy định tức state[i][j] = s_box[ state[i][j] ].
Khi giải mã, ta sử dụng bảng inverse S_box để giải.
### Confusion through Substitution
```=
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,
)

state = [
    [251, 64, 182, 81],
    [146, 168, 33, 80],
    [199, 159, 195, 24],
    [64, 80, 182, 255],
]
def matrix2bytes(matrix):
    return     bytes(sum(matrix,[])).decode()
def sub_bytes(s, sbox=s_box):
    
    for i in range(4):
        for j in range(4):
            s[i][j] = sbox[s[i][j]]
    return s

print(matrix2bytes(sub_bytes(state, sbox=inv_s_box)))
```
## ShiftRows & MixColumns
### ShiftRows
Là bước dịch xoay vòng state qua trái, hàng đầu giữ nguyên, hàng 2 dịch 1 lần, hàng 3 dịch 2 lần, hàng 4 dịch 3 lần. 
![image](https://hackmd.io/_uploads/r1SrSJEbWl.png)
### MixColumns
Bước này xem các cột của state là đa thức và nhân với đa thức c(x) = 3 * $x^3$ + $x^2$ + $x$ + 2 mod( $x^4$ + 1) 
![image](https://hackmd.io/_uploads/BJkHL1E--l.png)
### Diffusion through Permutation
```=
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]
'''
 0 1 2 3    0 5 A F 
 4 5 6 7    4 9 E 3
 8 9 A B    8 D 2 7
 C D E F    C 1 6 B 
 '''

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]


# 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)

def matrix2bytes(matrix):
    return bytes(sum(matrix, []))

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)
flag = matrix2bytes(state).decode()
print(flag)
```
## Bringing It All Together
Khi biết được các bước encrypt của AES thì decrypt chỉ cần thực hiện theo hướng ngược lại, gộp các bước lại ta được 1 bài decrypt hoàn chỉnh:
```=
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 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]


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 sub_bytes(s, sbox=s_box):
    for c in range(4):
        for r in range(4):
            s[c][r] = sbox[s[c][r]]


def add_round_key(s, round_key, idx):
    # round_key is same shape as s: [col0, col1, col2, col3]
    for c in range(4):
        for r in range(4):
            s[c][r] ^= round_key[idx][c][r]
    return s


def bytes2matrix(text):
    return [list(text[i:i+4]) for i in range(0, len(text), 4)]

def matrix2bytes(matrix):
    out = []
    for r in matrix:
        for c in r:
            out.append(c.to_bytes(2,byteorder='little').decode())
    return ''.join(out)

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
    state = bytes2matrix(ciphertext)
    # Initial add round key step
    add_round_key(state, round_keys, 10)

    for i in range(N_ROUNDS - 1, 0, -1):
        inv_shift_rows(state)
        sub_bytes(state, sbox = inv_s_box)
        add_round_key(state, round_keys, i)
        inv_mix_columns(state)
        

    # Run final round (skips the InvMixColumns step)
    inv_shift_rows(state)
    sub_bytes(state, sbox = inv_s_box)
    add_round_key(state, round_keys, 0)
    # Convert state matrix to plaintext
    plaintext = matrix2bytes(state)
    return plaintext


print(decrypt(key, ciphertext))
```

#    Cryptohack
##    Modes of Operation Starter
###    Đề :
```=
from Crypto.Cipher import AES


KEY = ?
FLAG = ?


@chal.route('/block_cipher_starter/decrypt/<ciphertext>/')
def decrypt(ciphertext):
    ciphertext = bytes.fromhex(ciphertext)

    cipher = AES.new(KEY, AES.MODE_ECB)
    try:
        decrypted = cipher.decrypt(ciphertext)
    except ValueError as e:
        return {"error": str(e)}

    return {"plaintext": decrypted.hex()}


@chal.route('/block_cipher_starter/encrypt_flag/')
def encrypt_flag():
    cipher = AES.new(KEY, AES.MODE_ECB)
    encrypted = cipher.encrypt(FLAG.encode())

    return {"ciphertext": encrypted.hex()}

```
![image](https://hackmd.io/_uploads/Hyk5b7N-Wx.png)
Bài này cho ta biết về Mode ECB trong AES: plaintext được chia thành các block và đưa vào block encrypt cùng với key để ra được block ciphertext. Vì ECB là mã hóa block đơn thuẩn, không có sự liên kết giữa các block với nhau nên nếu cùng 1 block, cùng 1 plaintext và key thì ciphertext sẽ giống nhau. Đây là nhược điểm lớn nhất của ECB.

Cách giải bài này chỉ cần lấy ciphertext và đưa lại vào ô decrypt và unhex là được 
> crypto{bl0ck_c1ph3r5_4r3_f457_!}

##    Passwords as Keys
###    Đề :
```=
from Crypto.Cipher import AES
import hashlib
import random


# /usr/share/dict/words from
# https://gist.githubusercontent.com/wchargin/8927565/raw/d9783627c731268fb2935a731a618aa8e95cf465/words
with open("/usr/share/dict/words") as f:
    words = [w.strip() for w in f.readlines()]
keyword = random.choice(words)

KEY = hashlib.md5(keyword.encode()).digest()
FLAG = ?


@chal.route('/passwords_as_keys/decrypt/<ciphertext>/<password_hash>/')
def decrypt(ciphertext, password_hash):
    ciphertext = bytes.fromhex(ciphertext)
    key = bytes.fromhex(password_hash)

    cipher = AES.new(key, AES.MODE_ECB)
    try:
        decrypted = cipher.decrypt(ciphertext)
    except ValueError as e:
        return {"error": str(e)}

    return {"plaintext": decrypted.hex()}


@chal.route('/passwords_as_keys/encrypt_flag/')
def encrypt_flag():
    cipher = AES.new(KEY, AES.MODE_ECB)
    encrypted = cipher.encrypt(FLAG.encode())

    return {"ciphertext": encrypted.hex()}
```
Chạy tất cả các word trong file words.txt, chuyển thành key và decrypt, nếu như trong decrypt có b"crypto{" thì key đó đúng, và đó cũng là flag.

### Script giải : 
```=
import requests
import hashlib
from Crypto.Cipher import AES
BASE = "https://aes.cryptohack.org/passwords_as_keys"

def encrypt():
    url = f"{BASE}/encrypt_flag/"
    r = requests.get(url)
    data = r.json()
    return data["ciphertext"]
def decrypt( ct, key ):
    url = f"{BASE}/decrypt/{ct}/{key}/"
    r = requests.get(url)
    data = r.json()
    return data["plaintext"]

ct = encrypt()
with open("words.txt", "r", encoding="utf-8") as f:
    for line in f:
        line = line.strip()        
        if line:
            key = hashlib.md5(line.encode()).hexdigest()  
            pt = decrypt( ct , key )
            pt = bytes.fromhex( pt )
            print(line)
            if( b"crypto{" in pt ):
                print( pt )
                exit(0)
```
> crypto{k3y5__r__n07__p455w0rdz?}
##    ECB Oracle
###    Đề : 
```=
@chal.route('/ecb_oracle/encrypt/<plaintext>/')
def encrypt(plaintext):
    plaintext = bytes.fromhex(plaintext)

    padded = pad(plaintext + FLAG.encode(), 16)
    cipher = AES.new(KEY, AES.MODE_ECB)
    try:
        encrypted = cipher.encrypt(padded)
    except ValueError as e:
        return {"error": str(e)}

    return {"ciphertext": encrypted.hex()}

```
Với tính chất mã hóa độc lập của ECB, ta có thể sử dụng nó để tìm ra flag bằng cách brute từng kí tự của flag.
Khi nhập vào 15 kí tự "a" thì trong hàm encrypt sẽ thêm flag vào và pad thêm đủ độ dài là bội của 16 -> padded = 15 * "a" + flag + padding
Vậy thì block đầu của plaintext là 15 * "a" + bytes đầu của flag ( ở đây ta biết byte đầu của flag là b"c" -> aaaaaaaaaaaaaaac )
###    VD : ( Vd bằng bytes thay vì hex cho dễ hình dung)
#### Giả sử flag có dạng crypto{FLAG}:
pt = b""
Tìm byte đầu:
plaintext = b"aaaaaaaaaaaaaaa"
padded = b"aaaaaaaaaaaaaaa" + b"crypto{FLAG}" + b"0" * 5
block đầu = b"aaaaaaaaaaaaaaac"
server sẽ tự đưa block đầu vào và trả ciphertex, sau đó ta chỉ việc brute tìm ra byte thỏa điều kiện 2 ciphertext bằng nhau.
guess = b"aaaaaaaaaaaaaaa" + pt + byte dự đoán

Cứ lặp lại các bước cho đến khi tìm ra flag.
### Script giải : 
```=
import requests
import hashlib
from Crypto.Cipher import AES
BASE = "https://aes.cryptohack.org/ecb_oracle"

def encrypt( pt ):
    pt = pt.hex()
    url = f"{BASE}/encrypt/{ pt }/"
    r = requests.get(url)
    data = r.json()
    return data["ciphertext"]
flag = b""
block = 0 
idx = 15
letters = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_{}"
while ( b"}" not in flag ):
    if( (len(flag) + 1) % 16 != 0 ):
        send = b"a" * ( idx - len(flag) % 16 ) 
    else:
        send = b"a" * 16
    ct = encrypt( send )
    block = len(flag) // 16
    for i in letters :
        if( (len(flag) + 1) % 16 != 0 ):
            test = b"a" * ( idx - len(flag) % 16 ) + flag + bytes([ i ])
            test_enc = encrypt( test )
            if( test_enc[block * 32 : (block + 1) * 32 ] == ct[block * 32 : (block + 1 ) * 32 ] ):
                flag += bytes([ i ])
                break
        else:
            test = b"a" * 16 + flag + bytes([ i ]) 
            test_enc = encrypt( test )
            if( test_enc[(block + 1) * 32 : (block + 2) * 32 ] == ct[(block + 1) * 32 : (block + 2 ) * 32 ] ):
                flag += bytes([ i ])
                break
        
print( flag )
    

```
> crypto{p3n6u1n5_h473_3cb}

##    ECB CBC WTF
###    Đề : 
```=
from Crypto.Cipher import AES


KEY = ?
FLAG = ?


@chal.route('/ecbcbcwtf/decrypt/<ciphertext>/')
def decrypt(ciphertext):
    ciphertext = bytes.fromhex(ciphertext)

    cipher = AES.new(KEY, AES.MODE_ECB)
    try:
        decrypted = cipher.decrypt(ciphertext)
    except ValueError as e:
        return {"error": str(e)}

    return {"plaintext": decrypted.hex()}


@chal.route('/ecbcbcwtf/encrypt_flag/')
def encrypt_flag():
    iv = os.urandom(16)

    cipher = AES.new(KEY, AES.MODE_CBC, iv)
    encrypted = cipher.encrypt(FLAG.encode())
    ciphertext = iv.hex() + encrypted.hex()

    return {"ciphertext": ciphertext}

```
Bài này encrypt bằng Mode CBC nhưng decrypt bằng Mode ECB.
Về CBC Mode:
![image](https://hackmd.io/_uploads/HkGYS7r--x.png)

![image](https://hackmd.io/_uploads/S1SDMmr--g.png)
CBC là dạng block cipher an toàn hơn ECB vì có kết hợp thêm IV vào, và các IV thay đổi sau mỗi encrypted block, ở block đầu tiên IV giữ nguyên, block kế tiếp sử dụng ciphertext để làm IV, việc này khiến cho CBC có tính chất giống với stream cipher.
Khi lấy ciphertext ta được 1 đoạn 48 bytes bao gồm 16 bytes IV và 32 bytes ciphertext.
Ta decrypt bằng cách đưa từng block ciphertext vào và xor với IV.
###    Script giải : 
```=
import requests
from Crypto.Util.number import long_to_bytes
from Crypto.Cipher import AES
BASE = "https://aes.cryptohack.org/ecbcbcwtf"

def encrypt():
    url = f"{BASE}/encrypt_flag/"
    r = requests.get(url)
    data = r.json()
    return data["ciphertext"]
def decrypt( ct ):
    url = f"{BASE}/decrypt/{ct}/"
    r = requests.get(url)
    data = r.json()
    return data["plaintext"]

ct = encrypt()
iv = ct[:32]
ct = ct[32:]
pt = b""
blocks = len( ct ) // 32
for i in range ( blocks ):
    send = ct[ i * 32 : ( i + 1 ) * 32 ]
    data = decrypt( send )
    text = int( data, 16 ) ^ int( iv, 16 )
    pt += long_to_bytes( text )
    iv = send
print( pt.decode() )
```
> crypto{3cb_5uck5_4v01d_17_!!!!!}

##    Flipping Cookie
###    Đề : 
```=
from Crypto.Cipher import AES
import os
from Crypto.Util.Padding import pad, unpad
from datetime import datetime, timedelta


KEY = ?
FLAG = ?


@chal.route('/flipping_cookie/check_admin/<cookie>/<iv>/')
def check_admin(cookie, iv):
    cookie = bytes.fromhex(cookie)
    iv = bytes.fromhex(iv)

    try:
        cipher = AES.new(KEY, AES.MODE_CBC, iv)
        decrypted = cipher.decrypt(cookie)
        unpadded = unpad(decrypted, 16)
    except ValueError as e:
        return {"error": str(e)}

    if b"admin=True" in unpadded.split(b";"):
        return {"flag": FLAG}
    else:
        return {"error": "Only admin can read the flag"}


@chal.route('/flipping_cookie/get_cookie/')
def get_cookie():
    expires_at = (datetime.today() + timedelta(days=1)).strftime("%s")
    cookie = f"admin=False;expiry={expires_at}".encode()

    iv = os.urandom(16)
    padded = pad(cookie, 16)
    cipher = AES.new(KEY, AES.MODE_CBC, iv)
    encrypted = cipher.encrypt(padded)
    ciphertext = iv.hex() + encrypted.hex()

    return {"cookie": ciphertext}
```
Công thức encypt và decrypt là:
**Encrypt:**

Block đầu:
$$
C_0 = E_k(P_0 \oplus IV)
$$

Các block sau:
$$
C_i = E_k(P_i \oplus C_{i-1})
\quad\text{với } i \ge 1
$$

**Decrypt:**

Block đầu:
$$
P_0 = D_k(C_0) \oplus IV
$$

Các block sau:
$$
P_i = D_k(C_i) \oplus C_{i-1}
\quad\text{với } i \ge 1
$$


###    Script giải : 
```=
import requests
from Crypto.Util.number import long_to_bytes, bytes_to_long
from Crypto.Cipher import AES
BASE = "https://aes.cryptohack.org/flipping_cookie"

def check( cookie , iv ):
    url = f"{BASE}/check_admin/{cookie}/{iv}/"
    r = requests.get(url)
    data = r.json()
    return data["flag"]
def get_cookie():
    url = f"{BASE}/get_cookie/"
    r = requests.get(url)
    data = r.json()
    return data["cookie"]


cookie = get_cookie()
iv = cookie[:32]
cookie = cookie[32:]

iv = bytearray.fromhex(iv)
iv[6] ^= ord("F") ^ ord("T")
iv[7] ^= ord("a") ^ ord("r")
iv[8] ^= ord("l") ^ ord("u")
iv[9] ^= ord("s") ^ ord("e")
iv[10] ^= ord("e") ^ ord(";")

iv = iv.hex()
pt = check( cookie , iv )
print( pt )
```
> crypto{4u7h3n71c4710n_15_3553n714l}

##    Symmetry
###    Đề : 
```=
from Crypto.Cipher import AES


KEY = ?
FLAG = ?


@chal.route('/symmetry/encrypt/<plaintext>/<iv>/')
def encrypt(plaintext, iv):
    plaintext = bytes.fromhex(plaintext)
    iv = bytes.fromhex(iv)
    if len(iv) != 16:
        return {"error": "IV length must be 16"}

    cipher = AES.new(KEY, AES.MODE_OFB, iv)
    encrypted = cipher.encrypt(plaintext)
    ciphertext = encrypted.hex()

    return {"ciphertext": ciphertext}


@chal.route('/symmetry/encrypt_flag/')
def encrypt_flag():
    iv = os.urandom(16)

    cipher = AES.new(KEY, AES.MODE_OFB, iv)
    encrypted = cipher.encrypt(FLAG.encode())
    ciphertext = iv.hex() + encrypted.hex()

    return {"ciphertext": ciphertext}

```
Bài này được giới thiệu thêm về Mode OFB trong AES
![image](https://hackmd.io/_uploads/r1gtGBESWZl.png)
![image](https://hackmd.io/_uploads/SJdQHNrW-e.png)

OFB là một block cipher có tính chất stream cipher.
Công thức: 
Keystream tạo như sau:
$$
K_0 = E_k(IV)
$$
$$
K_i = E_k(K_{i-1}) \quad \text{với } i \ge 1
$$

Encrypt:
$$
C_i = P_i \oplus K_i
$$

Decrypt:
$$
P_i = C_i \oplus K_i
$$
###    Script giải : 
```=
import requests
from Crypto.Util.number import long_to_bytes, bytes_to_long
from Crypto.Cipher import AES
BASE = "https://aes.cryptohack.org/symmetry"

def encrypt( plaintext , iv ):
    url = f"{BASE}/encrypt/{plaintext}/{iv}/"
    r = requests.get(url)
    data = r.json()
    return data["ciphertext"]
def encrypt_flag():
    url = f"{BASE}/encrypt_flag/"
    r = requests.get(url)
    data = r.json()
    return data["ciphertext"]

ct = encrypt_flag()
iv = ct[:32]
ct = ct[32:]

pt = encrypt( ct , iv )
print( bytes.fromhex( pt ).decode() )
```
> crypto{0fb_15_5ymm37r1c4l_!!!11!}

##    Bean Counter
###    Đề : 
```=
from Crypto.Cipher import AES


KEY = ?


class StepUpCounter(object):
    def __init__(self, step_up=False):
        self.value = os.urandom(16).hex()
        self.step = 1
        self.stup = step_up

    def increment(self):
        if self.stup:
            self.newIV = hex(int(self.value, 16) + self.step)
        else:
            self.newIV = hex(int(self.value, 16) - self.stup)
        self.value = self.newIV[2:len(self.newIV)]
        return bytes.fromhex(self.value.zfill(32))

    def __repr__(self):
        self.increment()
        return self.value



@chal.route('/bean_counter/encrypt/')
def encrypt():
    cipher = AES.new(KEY, AES.MODE_ECB)
    ctr = StepUpCounter()

    out = []
    with open("challenge_files/bean_flag.png", 'rb') as f:
        block = f.read(16)
        while block:
            keystream = cipher.encrypt(ctr.increment())
            xored = [a^b for a, b in zip(block, keystream)]
            out.append(bytes(xored).hex())
            block = f.read(16)

    return {"encrypted": ''.join(out)}
```
![image](https://hackmd.io/_uploads/H1RvoEHbZg.png)
![image](https://hackmd.io/_uploads/SyrYiVr-We.png)

Mode CTR là dạng block cipher độc lập, điều đặc biệt ở đây là thay vì sử dụng IV thì Mode CTR dùng 1 nonce và bộ đếm counter thay đổi. Mỗi block cipher được encrypt và decrypt theo công thức:
Keystream:
$$
K_i = E_k(\text{nonce} \,\|\, \text{counter}_i)
$$

Encrypt:
$$
C_i = P_i \oplus K_i
$$

Decrypt:
$$
P_i = C_i \oplus K_i
$$
Đề này có 1 lỗ hổng là khi gọi class $StepUpCounter$, nó đặt step_up = 0 và stup cũng bằng 0.
![image](https://hackmd.io/_uploads/HJCRTDBbZe.png)
Hàm increment dùng để tăng/ giảm bộ đếm nhưng vì stup = 0 nên newIV vẫn bằng IV cũ. Vậy là counter không đổi trong cả quá trình này => Cùng keystream
![image](https://hackmd.io/_uploads/HkT8RvrbWl.png)
Vậy nên ta chỉ cần tìm ra keystream thì sẽ tìm ra plaintext.
16 bytes đầu của file png là b"\x89PNG\r\n\x1a\n\x00\x00\x00\x0dIHDR" và cũng là 16 bytes đầu của plaintext. Ta chỉ cần xor ngược plaintext với cipher text là tìm được keystream.


###    Script giải : 
```=
import requests
from Crypto.Util.number import long_to_bytes, bytes_to_long
from Crypto.Cipher import AES
BASE = "https://aes.cryptohack.org/bean_counter"

def encrypt():
    url = f"{BASE}/encrypt/"
    r = requests.get(url)
    data = r.json()
    return data["encrypted"]
def xor_bytes(a, b):
    return bytes([x ^ y for x, y in zip(a, b)])

data = encrypt()
ct = [bytes.fromhex( data[ i : i + 32 ] ) for i in  range(0, len(data), 32) ]
pt = b""
png_header = b"\x89PNG\r\n\x1a\n\x00\x00\x00\x0dIHDR"
keystream = xor_bytes( png_header , ct[0])
for block in ct:
    pt += xor_bytes( keystream , block )
with open("flag.png" , "wb") as f:
    f.write( pt )
```
> ![flag](https://hackmd.io/_uploads/Bk2zaDHZbe.png)

##    Lazy CBC
###    Đề : 
```python= 
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"}
```

Đề bài yêu cầu tìm lại key và gửi để server xác nhận, đồng thời iv cũng là key, vậy trong mode CBC này key và iv là một.
Tìm kiếm trên mạng em thấy blog về cách khôi phục key khi key và iv là một
https://bernardoamc.com/ecb-iv-as-key/
Các bước thực hiện:
- Tạo một plaintext có độ dài ít nhất 3 block và mã hóa thành ciphertext.
- Thay đổi block thứ hai của ciphertext vừa tạo thành block giá trị 0.
- Thay đổi block thứ ba của ciphertext thành block 1.
- Giải mã đoạn ciphertext vừa đổi được thành đoạn invalid plaintext.
- Thực hiện xor block đầu tiên và block cuối của invalid plaintext, kết quả là key cần tìm.

**Giải thích:**
Công thức mã hóa và giải mã của mode CBC là:
Mã hóa:
$$C_1=E_k(P_1 \oplus IV )$$
$$C_i=E_k(P_i \oplus C_{i-1} ) $$ 

Giải mã:
$$P_1=IV  \oplus D_k(C_1) $$ 
$$P_i= C_{i-1} \oplus D_k(C_i)$$

Sau khi gửi plaintext và nhận được ciphertext là C, thay đổi block thứ 2 và 3 ta được C có dạng: block1 + "0"*16 + block 1.
Giải mã từng block : 
- $P_1=IV  \oplus D_k(C_1)$
- $P_2=C_1 \oplus D_k(C_2)$
- $P_3=C_2 \oplus D_K(C_3)$

Khi xor $P_3$ với $P_1$ ta khử được $D_k(C_1)$ và $D_k(C_3)$ vì $C_1=C_3$, còn lại $IV \oplus C_2 = IV$ vì $C_2=$"0"*16. IV lại chính là key!
##    Triple DES
###    Đề:
```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())
```

![image](https://hackmd.io/_uploads/HkrdJjtt-g.png)


Triple DES là kĩ thuật mã hóa khối với 3 DES khác nhau ( 3 key khác nhau ).
Công thức mã hóa là $$ C=E_{K_3}(D_{K_2}(E_{K_1}(P)))$$
Công thức giải mã ngược lại là $$P=D_{K_1}(E_{K_2}(D_{K_3}(C)))$$
3DES có thể bị tấn công bằng Meet-in-the-middle( MITM ), và DES hay 3DES có Weak key, weak key là key làm cho quá trình giải mã và mã hóa trở thành một.
Các weak key : 
![image](https://hackmd.io/_uploads/ryKnZ6YYbx.png)
![image](https://hackmd.io/_uploads/r1w6-CKKbg.png)

Tuy nhiên không phải mọi weak key đều dùng được, ở đây em đã thử và thấy key "0000000000000000FFFFFFFFFFFFFFFF" dùng được.

Ta chỉ cần đưa key vào và lấy encrypted flag, sau đó encrypt tiếp với key và encrypted flag để thu được flag.

https://en.wikipedia.org/wiki/Weak_key
https://crypto.stackexchange.com/questions/12214/can-you-explain-weak-keys-for-des
##    CTRIME
###    Đề: 
```python=
from Crypto.Cipher import AES
from Crypto.Util import Counter
import zlib


KEY = ?
FLAG = ?


@chal.route('/ctrime/encrypt/<plaintext>/')
def encrypt(plaintext):
    plaintext = bytes.fromhex(plaintext)

    iv = int.from_bytes(os.urandom(16), 'big')
    cipher = AES.new(KEY, AES.MODE_CTR, counter=Counter.new(128, initial_value=iv))
    encrypted = cipher.encrypt(zlib.compress(plaintext + FLAG.encode()))

    return {"ciphertext": encrypted.hex()}
```

Chall chỉ có 1 hàm encrypt, và trả về enrypted là mã hóa của đoạn nén plaintext + flag. 
Tính chất của zlib.compress là thực hiện nén các đoạn/ từ lặp lại với nhau và đánh mức độ từ 1-9, và zlib thường sẽ xét theo đoạn giống nhau ( > 3 bytes ) để xác định lặp lại. Nếu có các đoạn lặp lại thì sau khi nén sẽ giảm được bớt độ dài.
CTR mode không làm thay đổi độ dài của ciphertext, tức là plaintext và ciphertext đều cùng độ dài.
Vì thế có thể lợi dụng zlib.compress để brute-force các kí tự của flag, nếu như kí tự đó ở trong flag thì độ dài của bytes sẽ thay đổi ít nhất hoặc không thay đổi.

Đây cũng là CRIME attack: https://hpc-notes.soton.ac.uk/talks/bullrun/Crime_slides.pdf

**Note:** Vì tính chất của zlib.compress là xét theo đoạn từ 3 bytes trở lên để nén nên khi brute từng từ sẽ thấy ở chữ E tức sau chữ M độ dài sẽ tăng thêm, em có 2 hướng là tự đoán được sau chữ M là E và đặt luôn, hoặc là gửi đoạn ( flag + w ) lặp lại nhiều lần để tăng sự lặp lại và khiến zlib nhận ra .
###    Script giải: 
```python=
import requests
BASE = "https://aes.cryptohack.org/ctrime"
def encrypt( pt ):
    url = f"{BASE}/encrypt/{pt}/"
    r = requests.get(url)
    data = r.json()
    return data["ciphertext"]

alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789{}"

flag = "crypto{" 
while ( "}" not in flag ):
    enc = encrypt( flag.encode().hex() )
    if( flag.endswith( "M" ) ):
        flag += "E"
        continue
    for w in alphabet:
        pt = encrypt( ( flag + w ).encode().hex() )
        if( len( pt ) == len( enc ) ):
            print( w )
            flag += w
            break
print( flag )
```
##    Logon Zero
###    Đề: 
```python=
#!/usr/bin/env python3

from Crypto.Cipher import AES
from Crypto.Util.number import bytes_to_long
from os import urandom
from utils import listener

FLAG = "crypto{???????????????????????????????}"


class CFB8:
    def __init__(self, key):
        self.key = key

    def encrypt(self, plaintext):
        IV = urandom(16)
        cipher = AES.new(self.key, AES.MODE_ECB)
        ct = b''
        state = IV
        for i in range(len(plaintext)):
            b = cipher.encrypt(state)[0]
            c = b ^ plaintext[i]
            ct += bytes([c])
            state = state[1:] + bytes([c])
        return IV + ct

    def decrypt(self, ciphertext):
        IV = ciphertext[:16]
        ct = ciphertext[16:]
        cipher = AES.new(self.key, AES.MODE_ECB)
        pt = b''
        state = IV
        for i in range(len(ct)):
            b = cipher.encrypt(state)[0]
            c = b ^ ct[i]
            pt += bytes([c])
            state = state[1:] + bytes([ct[i]])
        return pt


class Challenge():
    def __init__(self):
        self.before_input = "Please authenticate to this Domain Controller to proceed\n"
        self.password = urandom(20)
        self.password_length = len(self.password)
        self.cipher = CFB8(urandom(16))

    def challenge(self, your_input):
        if your_input['option'] == 'authenticate':
            if 'password' not in your_input:
                return {'msg': 'No password provided.'}
            your_password = your_input['password']
            if your_password.encode() == self.password:
                self.exit = True
                return {'msg': 'Welcome admin, flag: ' + FLAG}
            else:
                return {'msg': 'Wrong password.'}

        if your_input['option'] == 'reset_connection':
            self.cipher = CFB8(urandom(16))
            return {'msg': 'Connection has been reset.'}

        if your_input['option'] == 'reset_password':
            if 'token' not in your_input:
                return {'msg': 'No token provided.'}
            token_ct = bytes.fromhex(your_input['token'])
            if len(token_ct) < 28:
                return {'msg': 'New password should be at least 8-characters long.'}

            token = self.cipher.decrypt(token_ct)
            new_password = token[:-4]
            self.password_length = bytes_to_long(token[-4:])
            self.password = new_password[:self.password_length]
            return {'msg': 'Password has been correctly reset.'}


import builtins; builtins.Challenge = Challenge # hack to enable challenge to be run locally, see https://cryptohack.org/faq/#listener
listener.start_server(port=13399)
```

Khi search trên mang Logon Zero attack thì em thấy đây là một CVE và thấy bài này [Link](https://www.sophos.com/en-us/blog/zerologon-hacking-windows-servers-with-a-bunch-of-zeros
![image](https://hackmd.io/_uploads/SyaFxeoFbl.png)

Tức là với tính chất của CFB8-AES thì nếu ta gửi được token là chuỗi 8 số 0 thì sẽ có tỉ lệ trùng khớp với password = ""

Trong đoạn code decrypt
```python=
def decrypt(self, ciphertext):
        IV = ciphertext[:16]
        ct = ciphertext[16:]
        cipher = AES.new(self.key, AES.MODE_ECB)
        pt = b''
        state = IV
        for i in range(len(ct)):
            b = cipher.encrypt(state)[0]
            c = b ^ ct[i]
            pt += bytes([c])
            state = state[1:] + bytes([ct[i]])
        return pt
```
Ta thấy là pt được tạo từ c, mà c thì được tạo từ b xor ct[i], tức là nếu ta gửi token = b"0"*28 thì c = b. Mà b là một trong 256 bytes, tỉ lệ là 1/256 b = b"\x00". Vì thế c là dãy byte toàn số 0.
Cách tạo password và password length lại lấy dãy c. Nếu c là dãy toàn số 0 và password length được tạo từ 4 bytes cuối của c thì độ dài của password là 0, tức là chuỗi rỗng.

###    Script giải:
```python=
import json
from pwn import *

HOST = "socket.cryptohack.org"
PORT = 13399
r = remote( HOST, PORT )

def send( data ):
    r.sendline( json.dumps( data ).encode() )

token = b"\x00" * 28
reset_password = {
    "option" : "reset_password",
    "token" : token.hex()
}
reset_connection = {
   "option" : "reset_connection",
}
auth = {
    "option" : "authenticate",
    "password" : ""
}
r.recvline()
while True:
    send( reset_password )
    r.recvline()
    send( auth )
    r.recvuntil( b"msg" )
    msg = r.recvline()
    if( b"flag" in msg ):
        print( msg )
        sys.exit()
    else:
        send( reset_connection )
        r.recvline()
```

##    Stream of Consciousness
```python=
from Crypto.Cipher import AES
from Crypto.Util import Counter
import random


KEY = ?
TEXT = ['???', '???', ..., FLAG]


@chal.route('/stream_consciousness/encrypt/')
def encrypt():
    random_line = random.choice(TEXT)

    cipher = AES.new(KEY, AES.MODE_CTR, counter=Counter.new(128))
    encrypted = cipher.encrypt(random_line.encode())

    return {"ciphertext": encrypted.hex()}
```
Ta thấy là hàm encrypt sẽ chọn random phần tử trong mảng TEXT và giải mã. Vì thế bước đầu em sẽ chạy và xem kích cỡ của mảng và thấy có khoảng 22 phần tử.
![image](https://hackmd.io/_uploads/HyDFugiFbe.png)

```python=
for i in range( 100 ):
    data = int( encrypt(), 16 )
    if( data not in ct ):
        ct.append( data )
with open( "ciphertext.txt", "w" ) as f:
    for i in ct:
        f.write( str( i ) + "\n" )
```

Vì biết được format của flag là **crypto{** nên ta có thể brute được 7 bytes đầu của key ( đoạn sau khi đưa vào block encryption ) bằng cách xor flag với từng phần tử trong của TEXT sau khi mã hóa. Sau đó thực hiện xor key lại với tất cả phần tử để xem plaintext của tất cả, sau khi thực hiện và xem các key thì em thấy key dưới đây xor tạo được các plaintext đọc được.
![image](https://hackmd.io/_uploads/SyvU_liFZe.png)
```python=
for i in ct:
    key = xor( flag, long_to_bytes( int( i )) )
    print( " key: ", key )
    for i in ct:
        print( xor( key, long_to_bytes( int( i )) ) )
```
Sau đó chơi trò đoán từ tiếng anh để prefix và tìm lại key hoàn chỉnh :)))
### Script:
```python=
import requests
import json
from Crypto.Util.number import long_to_bytes, bytes_to_long
BASE = "https://aes.cryptohack.org/stream_consciousness"
def encrypt():
    url = f"{BASE}/encrypt/"
    r = requests.get(url)
    data = r.json()
    return data["ciphertext"]
def xor( bytes1, bytes2 ):
    return bytes(a ^ b for a, b in zip(bytes1, bytes2))
flag = b"crypto{"
ct = []
''' Brute độ dài mảng TEXT
for i in range( 100 ):
    data = int( encrypt(), 16 )
    if( data not in ct ):
        ct.append( data )
with open( "ciphertext.txt", "w" ) as f:
    for i in ct:
        f.write( str( i ) + "\n" )
'''
with open( "ciphertext.txt", "r" ) as f:
    for line in f:
        ct.append( line.strip() )
'''
for i in ct:
    key = xor( flag, long_to_bytes( int( i )) )
    print( " key: ", key )
    for i in ct:
        print( xor( key, long_to_bytes( int( i )) ) )
'''
key = b'uN1\xce\xf4\x1d\xc5'
'''
pt = [ "I shall", "Dress-m", "These h", "Love, p", "How pro", "No, I'l", "I'm unh", "crypto{", "As if I", "It can'", "Why do ", "Three b", "The ter", "Dolly w", "Our? Wh", "I shall", "But I w", "And I s", "What a ", "Perhaps", "Would I", "What a "]

pt = ['I shall ', 'Dress-ma', 'These ho', 'Love, pr', 'How prou', "No, I'll", "I'm unha", 'crypto{k', 'As if I ', "It can't", 'Why do t', 'Three bo', 'The terr', 'Dolly wi', 'Our? Why', 'I shall,', 'But I wi', 'And I sh', 'What a l', 'Perhaps ', 'Would I ', 'What a n']

pt =  ['I shall l', 'Dress-mak', 'These hor', 'Love, pro', 'How proud', "No, I'll ", "I'm unhap", 'crypto{k3', 'As if I h', "It can't ", 'Why do th', 'Three boy', 'The terri', 'Dolly wil', 'Our? Why ', 'I shall, ', 'But I wil', 'And I sha', 'What a lo', 'Perhaps h', 'Would I h', 'What a na']

pt =  ['I shall los', 'Dress-makin', 'These horse', 'Love, proba', 'How proud a', "No, I'll go", "I'm unhappy", 'crypto{k3y5', 'As if I had', "It can't be", 'Why do they', 'Three boys ', 'The terribl', 'Dolly will ', 'Our? Why ou', "I shall, I'", 'But I will ', 'And I shall', 'What a lot ', 'Perhaps he ', 'Would I hav', 'What a nast']

pt =  ['I shall lose e', 'Dress-making a', 'These horses, ', 'Love, probably', 'How proud and ', "No, I'll go in", "I'm unhappy, I", 'crypto{k3y57r3', 'As if I had an', "It can't be to", 'Why do they go', 'Three boys run', 'The terrible t', 'Dolly will thi', 'Our? Why our?', "I shall, I'll ", 'But I will sho', 'And I shall ig', 'What a lot of ', 'Perhaps he has', 'Would I have b', 'What a nasty s']

pt =  ['I shall lose every', 'Dress-making and M', 'These horses, this', 'Love, probably? Th', 'How proud and happ', "No, I'll go in to ", "I'm unhappy, I des", 'crypto{k3y57r34m_r', 'As if I had any wi', "It can't be torn o", 'Why do they go on ', 'Three boys running', 'The terrible thing', 'Dolly will think t', 'Our? Why our?', "I shall, I'll lose", 'But I will show hi', 'And I shall ignore', 'What a lot of thin', 'Perhaps he has mis', 'Would I have belie', 'What a nasty smell']

pt =  ['I shall lose everyth', 'Dress-making and Mil', 'These horses, this c', 'Love, probably? They', 'How proud and happy ', "No, I'll go in to Do", "I'm unhappy, I deser", 'crypto{k3y57r34m_r3u', 'As if I had any wish', "It can't be torn out", 'Why do they go on pa', 'Three boys running, ', 'The terrible thing i', 'Dolly will think tha', 'Our? Why our?', "I shall, I'll lose e", 'But I will show him.', 'And I shall ignore i', 'What a lot of things', 'Perhaps he has misse', 'Would I have believe', 'What a nasty smell t']

pt =  ['I shall lose everything', 'Dress-making and Millin', 'These horses, this carr', 'Love, probably? They do', "How proud and happy he'", "No, I'll go in to Dolly", "I'm unhappy, I deserve ", 'crypto{k3y57r34m_r3u53_', 'As if I had any wish to', "It can't be torn out, b", 'Why do they go on paint', 'Three boys running, pla', 'The terrible thing is t', 'Dolly will think that I', 'Our? Why our?', "I shall, I'll lose ever", 'But I will show him.', 'And I shall ignore it.', 'What a lot of things th', 'Perhaps he has missed t', 'Would I have believed t', 'What a nasty smell this']

pt =  ['I shall lose everything and n', 'Dress-making and Millinery', 'These horses, this carriage -', "Love, probably? They don't kn", "How proud and happy he'll be ", "No, I'll go in to Dolly and t", "I'm unhappy, I deserve it, th", 'crypto{k3y57r34m_r3u53_15_f47', 'As if I had any wish to be in', "It can't be torn out, but it ", 'Why do they go on painting an', 'Three boys running, playing a', 'The terrible thing is that th', "Dolly will think that I'm lea", 'Our? Why our?', "I shall, I'll lose everything", 'But I will show him.', 'And I shall ignore it.', 'What a lot of things that the', 'Perhaps he has missed the tra', 'Would I have believed then th', 'What a nasty smell this paint']

pt =  ['I shall lose everything and not', 'Dress-making and Millinery', 'These horses, this carriage - h', "Love, probably? They don't know", "How proud and happy he'll be wh", "No, I'll go in to Dolly and tel", "I'm unhappy, I deserve it, the ", 'crypto{k3y57r34m_r3u53_15_f474l', 'As if I had any wish to be in t', "It can't be torn out, but it ca", 'Why do they go on painting and ', 'Three boys running, playing at ', 'The terrible thing is that the ', "Dolly will think that I'm leavi", 'Our? Why our?', "I shall, I'll lose everything i", 'But I will show him.', 'And I shall ignore it.', 'What a lot of things that then ', 'Perhaps he has missed the train', 'Would I have believed then that', 'What a nasty smell this paint h']
'''
pt =  ['I shall lose everything and not g', 'Dress-making and Millinery', 'These horses, this carriage - how', "Love, probably? They don't know h", "How proud and happy he'll be when", "No, I'll go in to Dolly and tell ", "I'm unhappy, I deserve it, the fa", 'crypto{k3y57r34m_r3u53_15_f474l}', 'As if I had any wish to be in the', "It can't be torn out, but it can ", 'Why do they go on painting and bu', 'Three boys running, playing at ho', 'The terrible thing is that the pa', "Dolly will think that I'm leaving", 'Our? Why our?', "I shall, I'll lose everything if ", 'But I will show him.', 'And I shall ignore it.', 'What a lot of things that then se', 'Perhaps he has missed the train a', 'Would I have believed then that I', 'What a nasty smell this paint had']
pt[9] += "t"
key = xor( pt[9].encode(), long_to_bytes( int( ct[9] ) ) )
pt[4] += "d"
key = xor( pt[4].encode(), long_to_bytes( int( ct[4] ) ) )
pt[6] += "py"
key = xor( pt[6].encode(), long_to_bytes( int( ct[6] ) ) )
pt[3] += "bly"
key = xor( pt[3].encode(), long_to_bytes( int( ct[3] ) ) )
pt[17] += "nore"
key = xor( pt[17].encode(), long_to_bytes( int( ct[17] ) ) )
pt[8] += "sh"
key = xor( pt[8].encode(), long_to_bytes( int( ct[8] ) ) )
pt[0] += "ing"
key = xor( pt[0].encode(), long_to_bytes( int( ct[0] ) ) )
pt[15] += "ything"
key = xor( pt[15].encode(), long_to_bytes( int( ct[15] ) ) )
pt[19] += "in"
key = xor( pt[19].encode(), long_to_bytes( int( ct[19] ) ) )
pt[13] += "ng"
key = xor( pt[13].encode(), long_to_bytes( int( ct[13] ) ) )
for i in range ( len( ct ) ): 
    pt[i] = xor( key, long_to_bytes( int( ct[i] ))).decode()
print( "pt = ", pt )
```

##    Dancing Queen
###    Đề:
```python=
#!/usr/bin/env python3

from os import urandom


FLAG = b'crypto{?????????????????????????????}'


def bytes_to_words(b):
    return [int.from_bytes(b[i:i+4], 'little') for i in range(0, len(b), 4)]

def rotate(x, n):
    return ((x << n) & 0xffffffff) | ((x >> (32 - n)) & 0xffffffff)

def word(x):
    return x % (2 ** 32)

def words_to_bytes(w):
    return b''.join([i.to_bytes(4, 'little') for i in w])

def xor(a, b):
    return b''.join([bytes([x ^ y]) for x, y in zip(a, b)])


class ChaCha20:
    def __init__(self):
        self._state = []

    def _inner_block(self, state):
        self._quarter_round(state, 0, 4, 8, 12)
        self._quarter_round(state, 1, 5, 9, 13)
        self._quarter_round(state, 2, 6, 10, 14)
        self._quarter_round(state, 3, 7, 11, 15)
        self._quarter_round(state, 0, 5, 10, 15)
        self._quarter_round(state, 1, 6, 11, 12)
        self._quarter_round(state, 2, 7, 8, 13)
        self._quarter_round(state, 3, 4, 9, 14)

    def _quarter_round(self, x, a, b, c, d):
        x[a] = word(x[a] + x[b]); x[d] ^= x[a]; x[d] = rotate(x[d], 16)
        x[c] = word(x[c] + x[d]); x[b] ^= x[c]; x[b] = rotate(x[b], 12)
        x[a] = word(x[a] + x[b]); x[d] ^= x[a]; x[d] = rotate(x[d], 8)
        x[c] = word(x[c] + x[d]); x[b] ^= x[c]; x[b] = rotate(x[b], 7)
    
    def _setup_state(self, key, iv):
        self._state = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]
        self._state.extend(bytes_to_words(key))
        self._state.append(self._counter)
        self._state.extend(bytes_to_words(iv))

    def decrypt(self, c, key, iv):
        return self.encrypt(c, key, iv)

    def encrypt(self, m, key, iv):
        c = b''
        self._counter = 1

        for i in range(0, len(m), 64):
            self._setup_state(key, iv)
            for j in range(10):
                self._inner_block(self._state)
            c += xor(m[i:i+64], words_to_bytes(self._state))

            self._counter += 1
        
        return c
    

if __name__ == '__main__':
    msg = b'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula.'
    key = urandom(32)
    iv1 = urandom(12)
    iv2 = urandom(12)

    c = ChaCha20()
    msg_enc = c.encrypt(msg, key, iv1)
    flag_enc = c.encrypt(FLAG, key, iv2)

    print(f"iv1 = '{iv1.hex()}'")
    print(f"iv2 = '{iv2.hex()}'")
    print(f"msg_enc = '{msg_enc.hex()}'")
    print(f"flag_enc = '{flag_enc.hex()}'")
```
**Output.txt**
```txt!
iv1 = 'e42758d6d218013ea63e3c49'
iv2 = 'a99f9a7d097daabd2aa2a235'
msg_enc = 'f3afbada8237af6e94c7d2065ee0e221a1748b8c7b11105a8cc8a1c74253611c94fe7ea6fa8a9133505772ef619f04b05d2e2b0732cc483df72ccebb09a92c211ef5a52628094f09a30fc692cb25647f'
flag_enc = 'b6327e9a2253034096344ad5694a2040b114753e24ea9c1af17c10263281fb0fe622b32732'
```

Việc cần làm là tìm được key , key được giấu trong state. 
```python=
def decrypt(self, c, key, iv):
        return self.encrypt(c, key, iv)

def encrypt(self, m, key, iv):
        c = b''
        self._counter = 1

        for i in range(0, len(m), 64):
            self._setup_state(key, iv)
            for j in range(10):
                self._inner_block(self._state)
            c += xor(m[i:i+64], words_to_bytes(self._state))

            self._counter += 1
        
        return c
```
Ta thấy hàm decrypt và encrypt là một, và c ở hàm encrypt được tạo từ việc xor từng block 64 bytes m với state. Và đề đã cho một đoạn plaintext cùng với ciphertext và IV của nó, có thể tìm lại được state của block đầu bằng việc xor block đầu của plaintext với ciphertext msg.
Sau khi tìm được state thì thực hiện đảo ngược quá trình để tìm lại state ban đầu.
```python=
def rev_rotate( x, n ):
    return (( x >> n ) & 0xffffffff ) | (( x << ( 32 - n ) ) & 0xffffffff )
def rev_inner_block( state ):
    rev_quarter_round(state, 3, 4, 9, 14)
    rev_quarter_round(state, 2, 7, 8, 13)
    rev_quarter_round(state, 1, 6, 11, 12)
    rev_quarter_round(state, 0, 5, 10, 15)
    rev_quarter_round(state, 3, 7, 11, 15)
    rev_quarter_round(state, 2, 6, 10, 14)
    rev_quarter_round(state, 1, 5, 9, 13)
    rev_quarter_round(state, 0, 4, 8, 12)
    return state

def rev_quarter_round( x, a, b, c, d):
    x[b] = rev_rotate(x[b], 7); x[b] ^= x[c]; x[c] = word(x[c] - x[d])
    x[d] = rev_rotate(x[d], 8); x[d] ^= x[a]; x[a] = word(x[a] - x[b])
    x[b] = rev_rotate(x[b], 12); x[b] ^= x[c]; x[c] = word(x[c] - x[d])
    x[d] = rev_rotate(x[d], 16); x[d] ^= x[a]; x[a] = word(x[a] - x[b])
```
Sau khi tìm được state thì có thể tìm được key vì ta biết sau key là counter = 1, trước key có 4 phần tử cố định.
Chuyển đoạn key đó sang bytes và decrypt sẽ được flag.
###    Script giải:
```python=
def rotate(x, n):
    return ((x << n) & 0xffffffff) | ((x >> (32 - n)) & 0xffffffff)
class ChaCha20:
    def __init__(self):
        self._state = []

    def _inner_block(self, state):
        self._quarter_round(state, 0, 4, 8, 12)
        self._quarter_round(state, 1, 5, 9, 13)
        self._quarter_round(state, 2, 6, 10, 14)
        self._quarter_round(state, 3, 7, 11, 15)
        self._quarter_round(state, 0, 5, 10, 15)
        self._quarter_round(state, 1, 6, 11, 12)
        self._quarter_round(state, 2, 7, 8, 13)
        self._quarter_round(state, 3, 4, 9, 14)

    def _quarter_round(self, x, a, b, c, d):
        x[a] = word(x[a] + x[b]); x[d] ^= x[a]; x[d] = rotate(x[d], 16)
        x[c] = word(x[c] + x[d]); x[b] ^= x[c]; x[b] = rotate(x[b], 12)
        x[a] = word(x[a] + x[b]); x[d] ^= x[a]; x[d] = rotate(x[d], 8)
        x[c] = word(x[c] + x[d]); x[b] ^= x[c]; x[b] = rotate(x[b], 7)
    
    def _setup_state(self, key, iv):
        self._state = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]
        self._state.extend(bytes_to_words(key))
        self._state.append(self._counter)
        self._state.extend(bytes_to_words(iv))

    def decrypt(self, c, key, iv):
        return self.encrypt(c, key, iv)

    def encrypt(self, m, key, iv):
        c = b''
        self._counter = 1

        for i in range(0, len(m), 64):
            self._setup_state(key, iv)
            for j in range(10):
                self._inner_block(self._state)
            c += xor(m[i:i+64], words_to_bytes(self._state))

            self._counter += 1
        
        return c


def rev_rotate( x, n ):
    return (( x >> n ) & 0xffffffff ) | (( x << ( 32 - n ) ) & 0xffffffff )

def word(x):
    return x % (2 ** 32)

def words_to_bytes(w):
    return b''.join([i.to_bytes(4, 'little') for i in w])

def xor(a, b):
    return b''.join([bytes([x ^ y]) for x, y in zip(a, b)])

def bytes_to_words(b):
    return [int.from_bytes(b[i:i+4], 'little') for i in range(0, len(b), 4)]

def rev_inner_block( state ):
    rev_quarter_round(state, 3, 4, 9, 14)
    rev_quarter_round(state, 2, 7, 8, 13)
    rev_quarter_round(state, 1, 6, 11, 12)
    rev_quarter_round(state, 0, 5, 10, 15)
    rev_quarter_round(state, 3, 7, 11, 15)
    rev_quarter_round(state, 2, 6, 10, 14)
    rev_quarter_round(state, 1, 5, 9, 13)
    rev_quarter_round(state, 0, 4, 8, 12)
    return state

def rev_quarter_round( x, a, b, c, d):
    x[b] = rev_rotate(x[b], 7); x[b] ^= x[c]; x[c] = word(x[c] - x[d])
    x[d] = rev_rotate(x[d], 8); x[d] ^= x[a]; x[a] = word(x[a] - x[b])
    x[b] = rev_rotate(x[b], 12); x[b] ^= x[c]; x[c] = word(x[c] - x[d])
    x[d] = rev_rotate(x[d], 16); x[d] ^= x[a]; x[a] = word(x[a] - x[b])

iv1 = b"\xe4'X\xd6\xd2\x18\x01>\xa6><I"
iv2 = b'\xa9\x9f\x9a}\t}\xaa\xbd*\xa2\xa25'
msg_enc = b'\xf3\xaf\xba\xda\x827\xafn\x94\xc7\xd2\x06^\xe0\xe2!\xa1t\x8b\x8c{\x11\x10Z\x8c\xc8\xa1\xc7BSa\x1c\x94\xfe~\xa6\xfa\x8a\x913PWr\xefa\x9f\x04\xb0].+\x072\xccH=\xf7,\xce\xbb\t\xa9,!\x1e\xf5\xa5&(\tO\t\xa3\x0f\xc6\x92\xcb%d\x7f'
flag_enc = b'\xb62~\x9a"S\x03@\x964J\xd5iJ @\xb1\x14u>$\xea\x9c\x1a\xf1|\x10&2\x81\xfb\x0f\xe6"\xb3\'2'
msg = b'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula.'
xor1 = xor( msg[ : 64 ], msg_enc[ : 64 ] )
xor2 = xor( msg[ 64: ], msg_enc[ 64: ] )
state1 = bytes_to_words( xor1 )
state2 = bytes_to_words( xor2 )

for j in range ( 10 ):
   state1 = rev_inner_block( state1 )

key = []
for i in range( 4, len( state1 ) ):
    if( state1[i] == 1 ):
        break
    key.append( state1[i] )
key = words_to_bytes( key )

c = ChaCha20()
flag = c.decrypt( flag_enc, key, iv2 )
print( flag.decode() )
```
##    Pad Thai
###    Đề:
```python=
#!/usr/bin/env python3

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)
```
Đây là bài padding oracle attack vì đề bảo rứa :)))), và khi search tài liệu thì em thấy bài viết của anh L1ttl3 
https://hackmd.io/@L1ttl3NoPro/HJrtwWBdJx
cùng với vidieo
https://www.youtube.com/watch?v=4EgD4PEatA8&t=693s&ab_channel=BrianYen
Vì thấy bài viết và video giải thích rất kĩ và rất dễ hiểu nên em không biết viết gì về bài này, chỉ cần thực hiện padding oracle attack là sẽ ra :)))
###    Script giải:
```python=
from pwn import *
import json
import re

HOST = "socket.cryptohack.org"
PORT = 13421
r = remote( HOST, PORT )


def recv():
    line = r.recvline().decode().strip()
    match = re.search( r"(\{.*\})", line )
    if not match:
        return None
    return json.loads( match.group(1) )

def send( data ):
    r.sendline( json.dumps(data).encode() )
encrypt = { 
    "option" : "encrypt"
} 

r.recvline()
send( encrypt )
ciphertext = recv()["ct"]

def xor( bytes1, bytes2 ):
    return bytes(a ^ b for a, b in zip(bytes1, bytes2))

size = 16
def attack_block( ct, idx ):
    block = ct[ idx * 16 : ( idx + 1 ) * 16 ]
    prev = ct[ ( idx - 1 ) * 16 : idx * 16 ]
    I = b""
    xor_text = b""
    for i in range( size - 1, -1, -1 ):
        xor_text = xor( I, bytes([ size - i ]) * ( size - i - 1 ) )
        for byte in range ( 256 ):
            data = prev[ : i ] + bytes( [ byte ] ) + xor_text + block
            unpad = {
                "option" : "unpad",
                "ct" : data.hex()
            }
            send( unpad )
            result = recv()["result"]
            if( result ):
                I =  xor( bytes([ byte ]), bytes([ size - i ])) + I
                break
    return xor( I, prev )

def attack( ciphertext ):
    ciphertext = bytes.fromhex( ciphertext )

    pt = b""
    for i in range( ( len( ciphertext ) // size )  - 1, 0, -1 ):
        pt =  attack_block( ciphertext, i ) + pt
    return pt


msg = attack( ciphertext )
check = {
    "option" : "check",
    "message" : msg.decode()
}
send( check )
flag = r.recv()
print( flag )
```

##    Ngoài các mode CBC, ECB, OFB, CTR còn có GCM, CCM. Còn ChaCha20, RC4
Phần này em chưa tìm hiểu nên sẽ note đây và bổ sung sau. 
https://hackmd.io/@nomorecaffeine/SkzPhT2R3#RC4-v%C3%A0-FMS-attack
https://hackmd.io/@Thangcoithongminh/ry3owiLX3
https://protonvpn.com/blog/chacha20?srsltid=AfmBOorJ0ZSu_Enw25LMu0lhxbr1SSSEoDEbOJH4weq8ybWUOKaao0Xw
## Vài kiểu tấn công khác
https://wrth.medium.com/cracking-aes-without-any-one-of-its-operations-c42cdfc0452f