# CryptoHack-SYMMETRIC CIPHERS # 1. Keyed Permutations What is the mathematical term for a one-to-one correspondence? → phép chiếu (bijection) ``` crypto{bijection} ``` # 2. Resisting Bruteforce What is the name for the best single-key attack against AES? ``` crypto{biclique} ``` # 3. Structure of AES Included is a `bytes2matrix` function for converting our initial plaintext block into a state matrix. Write a `matrix2bytes` function to turn that matrix back into bytes, and submit the resulting plaintext as the flag. ```python 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. """ ???? matrix = [ [99, 114, 121, 112], [116, 111, 123, 105], [110, 109, 97, 116], [114, 105, 120, 125], ] print(matrix2bytes(matrix)) ``` ## Solution Để giải quyết bài toán ta chỉ cần chuyển lần lượt từng phần tử của matrix từ số về dạng kí tự ascii của chúng. ```python 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. """ text = '' for i in range(len(matrix)): for j in range(4): text += chr(matrix[i][j]) return text matrix = [ [99, 114, 121, 112], [116, 111, 123, 105], [110, 109, 97, 116], [114, 105, 120, 125], ] print(matrix2bytes(matrix)) ``` flag: crypto{inmatrix} # 4. Round Keys Complete the add_round_key function, then use the matrix2bytes function to get your next flag. ## Solution Ta chỉ cần xor từng phần tử của state với từng phần tử của rounf_key sau đó in ra dạng mã ascii của kết quả. ![https://cryptohack.org/static/img/aes/AddRoundKey.png](https://cryptohack.org/static/img/aes/AddRoundKey.png) ```python 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): print(chr(s[i][j]^k[i][j]), end="") print(add_round_key(state, round_key)) ``` flag: crypto{r0undk3y} # 5. Confusion through Substitution Implement sub_bytes, send the state matrix through the inverse S-box and then convert it to bytes to get the flag. ## Solution Ở challenge này chúng ta sẽ lấy giá trị `i` của từng phần tử trong ma trận state và lấy giá trị tại vị trí `i` của inv_s_box. ```python 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 sub_bytes(s, sbox=s_box): for i in range(4): for j in range(4): print(chr(sbox[s[i][j]]), end="") print(sub_bytes(state, sbox=inv_s_box)) ``` flag: crypto{l1n34rly} # 6. Diffusion through Permutation We've provided code to perform MixColumns and the forward ShiftRows operation. After implementing inv_shift_rows, take the state, run inv_mix_columns on it, then inv_shift_rows, convert to bytes and you will have your flag. ## Solution ở bài này chúng ta sẽ tìm hiểu 2 bước tiếp theo đó là shif rows và mix columns, theo thứ tự đúng của AES cipher sẽ là: 1. SubBytes 2. Shift Rows ![https://cryptohack.org/static/img/aes/ShiftRows.png](https://cryptohack.org/static/img/aes/ShiftRows.png) 3. Mix Columns (vòng cuối sẽ không có) ![https://cryptohack.org/static/img/aes/MixColumns.png](https://cryptohack.org/static/img/aes/MixColumns.png) 4. Add Round Key đề cung cấp cho ta 1 ma trận đã được `shift rows` sau đó `Mix Columns` việc cần làm là giải mã theo thứ tự ngược lại. ```python 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) for i in range(4): for j in range(4): print(chr(state[i][j]), end="") ``` flag: crypto{d1ffUs3R} # 7. Bringing It All Together We've provided the key expansion code, and ciphertext that's been properly encrypted by AES-128. Copy in all the building blocks you've coded so far, and complete the decrypt function that implements the steps shown in the diagram. The decrypted plaintext is the flag. ## Solution Chúng ta đã viết cách giải mã từng bước của mật mã AES qua các challenge trước, bây giờ chúng ta chỉ cần kết hợp chúng lại và sắp xếp theo một thứ tự hợp lý kết hợp với vòng lăp thì sẽ hoàn toàn giải mã được AES và get flag. ![https://cryptohack.org/static/img/aes/Structure2.png](https://cryptohack.org/static/img/aes/Structure2.png) ```python sdfN_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)) ``` flag: crypto{MYAES128} # 8. Modes of Operation Starter All modes have serious weaknesses when used incorrectly. The challenges in this category take you to a different section of the website where you can interact with APIs and exploit those weaknesses. Get yourself acquainted with the interface and use it to take your next flag! ```python 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()} ``` ## Solution Ta lấy được flag đã bị mã hóa như sau: 1707269807a559fe0a9320d79ea344afa464ea125c2fc17e2630c8dce3fa1d65 encrypt_flag() → decrypt(ciphertext) → bytes.fromhex() flag: crypto{bl0ck_c1ph3r5_4r3_f457_!} # 9. Passwords as Keys It is essential that keys in symmetric-key algorithms are random bytes, instead of passwords or other predictable data. The random bytes should be generated using a cryptographically-secure pseudorandom number generator (CSPRNG). If the keys are predictable in any way, then the security level of the cipher is reduced and it may be possible for an attacker who gets access to the ciphertext to decrypt it. Just because a key looks like it is formed of random bytes, does not mean that it necessarily is. In this case the key has been derived from a simple password using a hashing function, which makes the ciphertext crackable. ```python 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()} ``` ## Solution Đọc qua source ta thấy flag được mã hóa bằng AES cipher với key là 1 từ random trong file word. Việc đoán được key là bất khả thi nên cách duy nhất là brute force từng từ khóa trong file word cho đến khi tìm được flag. ```python from Crypto.Cipher import AES import hashlib import random def decrypt(ciphertext, password_hash): ciphertext = bytes.fromhex(ciphertext) key = password_hash cipher = AES.new(key, AES.MODE_ECB) try: decrypted = cipher.decrypt(ciphertext) except ValueError as e: return {"error": str(e)} return decrypted with open("words.txt") as f: words = [w.strip() for w in f.readlines()] keyword = random.choice(words) KEY = hashlib.md5(keyword.encode()).digest() l = len(words) for i in range(l): KEY = hashlib.md5(words[i].encode()).digest() decrypted = decrypt("c92b7734070205bdf6c0087a751466ec13ae15e6f1bcdd3f3a535ec0f4bbae66", KEY) if b'crypto' in decrypted: print(decrypted) ``` flag: crypto{k3y5__r__n07__p455w0rdz?} # 10. **Block Ciphers - ECB CBC WTF** Here you can encrypt in CBC but only decrypt in ECB. That shouldn't be a weakness because they're different modes... right? ```python 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} ``` ## Solution ![https://aes.cryptohack.org/static/img/CBC_encryption.svg](https://aes.cryptohack.org/static/img/CBC_encryption.svg) Flag đã được mã hóa theo CBC mode (hình trên). Đoạn code cần chú ý: ```python ciphertext = iv.hex() + encrypted.hex() ``` vậy nên 16 bytes đầu của ciphertext sẽ là iv sau khi tim được iv thì ta chỉ cần truyền vào hàm giải mã theo từng block mỗi block 16 bytes, sau đó giải mã lại theo mô hình của CBC thì sẽ thu được flag ```python enc = encrypt_flag() iv = enc[:16] block1 = enc[16:32] block2 = enc[32:] decrypt_block1 = xor(response(block1), iv) decrypt_block2 = xor(response(block2), block1) print(decrypt_block1 + decrypt_block2) ``` Full code: ```python import requests from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from Crypto.Util.number import long_to_bytes, bytes_to_long def response(byte_string): url = "http://aes.cryptohack.org/ecbcbcwtf/decrypt/" url += byte_string.hex() url += "/" r = requests.get(url) js = r.json() return bytes.fromhex(js["plaintext"]) def encrypt_flag(): url = "http://aes.cryptohack.org/ecbcbcwtf/encrypt_flag/" r = requests.get(url) js = r.json() return bytes.fromhex(js["ciphertext"]) def xor(a, b): return long_to_bytes(bytes_to_long(a) ^ bytes_to_long(b)) enc = encrypt_flag() iv = enc[:16] block1 = enc[16:32] block2 = enc[32:] decrypt_block1 = xor(response(block1), iv) decrypt_block2 = xor(response(block2), block1) print(decrypt_block1 + decrypt_block2) ``` flag: crypto{3cb_5uck5_4v01d_17_!!!!!} # 11. ****ECB ORACLE**** ECB is the most simple mode, with each plaintext block encrypted entirely independently. In this case, your input is prepended to the secret flag and encrypted and that's it. We don't even provide a decrypt function. Perhaps you don't need a padding oracle when you have an "ECB oracle"? ```python from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad KEY = ? FLAG = ? @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()} ``` ## Solution ![https://aes.cryptohack.org/static/img/ECB_encryption.svg](https://aes.cryptohack.org/static/img/ECB_encryption.svg) chú ý: ```python padded = pad(plaintext + FLAG.encode(), 16) ``` đầu vào sẽ được cộng với flag ở phía sau cho ra out put là bản mã hóa của (input + flag) ECB sẽ mã hóa mỗi khối 16 bytes. Chúng ta sẽ bắt đầu bằng 1 khối có 31 bytes kết thúc bằng “ crypto{” ⇒ `b’xxxxxxxcrypto{’ + 1 bytes` vậy chúng ta sẽ brute force để tìm ra bytes cuối cùng bằng cách so sánh bản encrypted sau khi truyền phần `‘…xxxxxxxcrypto{’ + 1bytes` vào bảng mã hóa sau khi tìm được 1 kí tự ta sẽ dùng cách tương tự để tìm kí tự tiếp theo cho đến khi thu được flag hoàn chỉnh ```python import requests from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad ciphertext = b"177d36396d029f19708cef8d1c861bfd1f227170511203bbb211a703905f9e5128ae38bc1312435b814108836328262a" flag = b'crypto{' def response(byte_string): url = "http://aes.cryptohack.org/ecb_oracle/encrypt/" url += byte_string.hex() url += '/' r = requests.get(url) js = r.json() return bytes.fromhex(js["ciphertext"]) for i in range(7, 26): byte_string = b"" byte_string += b"\x00" * (31-i) print(byte_string) res = response(byte_string)[:32] byte_string += flag for j in range(33, 128): byte_string = byte_string[:31] byte_string += j.to_bytes(1, byteorder="big") print(j) res2 = response(byte_string)[:32] *if r*es == res2: flag += j.to_bytes(1, byteorder="big") print(flag) break ``` flag: crypto{p3n6u1n5_h473_3cb} # 12. ****FLIPPING COOKIE**** You can get a cookie for my website, but it won't help you read the flag... I think. ```python 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} ``` ## Solution ![http://aes.cryptohack.org/static/img/CBC_decryption.svg](http://aes.cryptohack.org/static/img/CBC_decryption.svg) - Đầu tiên cookie sẽ có dạng: admin=false + something - Ta chỉ cần quan tâm đến “admin=false” - Ta chỉ cần làm sao cho “admin=true” khi truyền vào hàm check_admin() thì sẽ có được flag - Hệ thống sẽ tạo 1 intialization Vector (iv) với 16 kí tự ngẫu nhiên - sau cùng hàm sẽ trả về cookie dưới dạng đã mã hóa của chuỗi: iv + cookie ban đầu Ta sẽ tận dụng phép xor để biến “admin=false” thành “admin=true” ```python cookie = get_cookie() origin = b'admin=False;expi' goal = b'admin=True;\x05\x05\x05\x05\x05' iv = cookie[:16] block1 = cookie[16:32] block2 = cookie[32:] send_iv = xor(xor(origin, goal), iv) ``` block1 sẽ có dạng `b’admin=Flase’ ^ iv` send_iv sẽ có dạng: `b’admin=False…’ ^ b’admin=True…’ ^ iv` khi ta gửi vào hàm `check_admin(cookie, iv)` với format: `(cookie = block1, iv = send_iv)` Thì 16 bytes đầu tiên của ciphertext sẽ có dạng: `b’admin=False…’ ^ b’admin=True…’ ^ iv ^ iv ^ b’admin=False’` = `b’admin=True…’` ⇒ lấy flag ```python def get_cookie(): url = "http://aes.cryptohack.org/flipping_cookie/get_cookie/" r = requests.get(url) js = r.json() return bytes.fromhex(js["cookie"]) def response(cookie, iv): url = "http://aes.cryptohack.org/flipping_cookie/check_admin/" url += cookie.hex() url += "/" url += iv.hex() url += "/" r = requests.get(url) js = r.json() print(js) def xor(a, b): return long_to_bytes(bytes_to_long(a) ^ bytes_to_long(b)) cookie = get_cookie() origin = b'admin=False;expi' goal = b'admin=True;\x05\x05\x05\x05\x05' iv = cookie[:16] block1 = cookie[16:32] block2 = cookie[32:] send_iv = xor(xor(origin, goal), iv) response(block1, send_iv) ``` # 13. ****LAZY_CBC**** I'm just a lazy dev and want my CBC encryption to work. What's all this talk about initialisations vectors? Doesn't sound important. ```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"} ``` ## solution ![https://aes.cryptohack.org/static/img/CBC_decryption.svg](https://aes.cryptohack.org/static/img/CBC_decryption.svg) Ta thấy chương trình chỉ chấp nhận những chuỗi nhập vào có độ dài là bội của 16 Theo sơ đồ thì Intialization Vector chỉ cộng vào phần đầu (16 kí tự đầu) của chuỗi `KEY = iv` Để lấy được key ta sẽ gửi 1 đoạn ciphertext chứa 32 bytes có thể dễ dàng tính toán. ```python ciphertext = b"\x00" * 32 ``` sau đó tách ra thành 2 phần, mỗi phần 16 bytes. ```python CD = response(ciphertext) C = CD[:16] D = CD[16:] ``` phần C sẽ có dạng: `key ^ plaintext` phần D sẽ có dạng: `ciphertext[:16] ^ plaintext` Khi xor 2 đoạn này với nhau ra sẽ được: `key ^ plaintext ^ ciphertext[:16] ^ plaintext = key ^ b'\x00\x00\x00...' = key` ⇒ done ```python import requests from Crypto.Util.Padding import pad, unpad from Crypto.Util.number import long_to_bytes, bytes_to_long def get_flag(key): url = "http://aes.cryptohack.org/lazy_cbc/get_flag/" url += key.hex() url += "/" r = requests.get(url) js = r.json() return bytes.fromhex(js["plaintext"]) def response(ciphertext): url = "http://aes.cryptohack.org/lazy_cbc/receive/" url += ciphertext.hex() url += "/" r = requests.get(url) js = r.json() return bytes.fromhex(js["error"][len("Invalid plaintext: "):]) def xor(a, b): return long_to_bytes(bytes_to_long(a) ^ bytes_to_long(b)) ciphertext = b"\x00" * 32 CD = response(ciphertext) C = CD[:16] D = CD[16:] print(get_flag(xor(C, D))) ``` # 14. Triple DES Data Encryption Standard was the forerunner to AES, and is still widely used in some slow-moving areas like the Payment Card Industry. This challenge demonstrates a strange weakness of DES which a secure block cipher should not have. ```python 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()) ``` ## Solution ![https://aes.cryptohack.org/static/img/ECB_encryption.svg](https://aes.cryptohack.org/static/img/ECB_encryption.svg) Nhìn thì có vẻ không thể giải được nhưng khi tìm hiểu về Triple DES thì có 1 khái niệm khá hay là [Weak Key](https://en.wikipedia.org/wiki/Weak_key). Nếu plain text được mã hóa 2 lần bằng khóa yếu thì sẽ trở về như cũ ```python key = b'\x00'*8 + b'\xff'*8 ``` Full code: ```python import requests from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from Crypto.Util.number import long_to_bytes, bytes_to_long def encrypt(key, plain): url = "http://aes.cryptohack.org/triple_des/encrypt/" url += key url += "/" url += plain.hex() url += "/" r = requests.get(url).json() return bytes.fromhex(r["ciphertext"]) def encrypt_flag(key): url = "http://aes.cryptohack.org/triple_des/encrypt_flag/" r = requests.get(url + key + '/').json() return bytes.fromhex(r["ciphertext"]) key = b'\x00'*8 + b'\xff'*8 flag = encrypt_flag(key.hex()) cipher = encrypt(key.hex(), flag) print(cipher)