# AES CIPHER ### AES Cipher Knowledge - AES là viết tắt của Advanced Encryption Standard (tiêu chuẩn mã hóa nâng cao), bằng cách sử dụng cùng một khóa để mã hóa và giải mã dữ liệu. Thuật toán này được xây dựng trên các phép toán logic và số học như thay thế, hoán vị và cộng trên các khối dữ liệu có kích thước cố định. - AES được đặc trưng bởi kích thước khóa, có thể là 128-bit, 192-bit hoặc 256-bit. Trong quá trình mã hóa, dữ liệu được chia thành các khối có kích thước 128-bit và sau đó được xử lý thông qua các vòng lặp liên tiếp, mỗi vòng lặp bao gồm các phép toán khác nhau như thay thế, hoán vị, trộn và thay đổi khóa. Mỗi khối dữ liệu sẽ được mã hóa bằng cùng một khóa và phép toán này được thực hiện đối với từng khối cho đến khi toàn bộ dữ liệu được mã hóa. - Trước hết, bạn nên xem video này để tìm hiểu qua về AES cipher [click here](https://www.youtube.com/watch?v=gP4PqVGudtg) #### Cơ chế mã hóa của AES sẽ như sau - Trước hết, AES sẽ chia plaintext (thường là 128 bits (16 bytes)) thành 4 phần, mỗi phần rồi xếp thành 1 ma trận 4x4, mỗi cột là 4 bytes như sau: ![](https://i.imgur.com/ZHszJXS.png) - Gồm 4 round để mã hóa: - Sub Bytes - Shift Rows - Mix Colums - Add Round Key - Round cuối sẽ không có Mix Colums ![](https://i.imgur.com/zC1OdSX.png) - Round cuối cùng không có bước MixColumn - Bước SubBytes dùng để thay thế (substitution), bước ShiftRows và MixColumn dùng để hoán vị thuật toán (permutation) trong thuật toán. #### Add Round Key ___ - Khi ta có 1 Round Key, ta xor từng phần tử của state (trạng thái hiện tại) với Round Key. ![](https://i.imgur.com/cvm9jIJ.png) - Đoạn code để xor từng phần tử của state với Round Key ``` def add_round_key(state, key): """ Perform the AddRoundKey operation of AES by XORing each byte of the state with the corresponding byte of the key. """ assert len(state) == len(key), "State and key sizes do not match" return bytes([s ^ k for s, k in zip(state, key)]) ``` #### Sub Bytes ___ - Sub Bytes là bước mà thay thế 1 bytes trong Plaintext_Block thành 1 bytes khác trong bảng S-box (bảng thay thế) ![](https://i.imgur.com/BFYyjIA.png) - Ví dụ như sau: ``` #Ma trận cần Sub Bytes state = [ [251, 64, 182, 81], [146, 168, 33, 80], [199, 159, 195, 24], [64, 80, 182, 255], ] #Ma trận Sbox (lúc chạy thì xóa các tung và hoành độ đi, mình để thế cho dễ nhìn lúc đọc thui nhaaa) s_box = ( | 0 1 2 3 4 5 6 7 8 9 a b c d e f ---| -- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | 00 |0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB, 10 |0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, 20 |0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E, 30 |0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25, 40 |0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92, 50 |0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84, 60 |0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06, 70 |0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B, 80 |0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73, 90 |0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E, a0 |0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, b0 |0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4, c0 |0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F, d0 |0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, e0 |0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, f0 |0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D, )#Cách Sub Bytes def sub_bytes(s,s_box): for i in range(4): for j in range(4): s[i][j] = s_box[s[i][j]] return s#Output: [[99, 114, 121, 112], [116, 111, 123, 108], [49, 110, 51, 52], [114, 108, 121, 125]] ``` - Với số đầu tiên là 251, Hexa là fb, sau khi Sub Bytes, 251 --> 99 ví s_box(f,b) = 63 (sang decimal là 99), tương tự với các phần tử khác của ma trận state #### Shift Rows ___ - Shift Rows là bước để dịch chuyển những hàng của ma trận, hàng đầu tiên sẽ dịch chuyển 0 bytes(có thể nói là không dịch chuyển), hàng thứ 2 sẽ dịch chuyển 1 bytes, hàng thứ 3 sẽ dịch chuyển 2 bytes, hàng thứ 4 sẽ dịch chuyển 3 bytes - Nếu bạn không hiểu thì có thể tham khảo hình sau: ![](https://i.imgur.com/iFKOHpA.png) - Ví dụ như sau: ``` #Hàm Shift_row ma trận def shift_rows(s): s[1][0], s[1][1], s[1][2], s[1][3] = s[1][1], s[1][2], s[1][3], s[1][0] s[2][0], s[2][1], s[2][2], s[2][3] = s[2][2], s[2][3], s[2][0], s[2][1] s[3][0], s[3][1], s[3][2], s[3][3] = s[3][3], s[3][0], s[3][1], s[3][2] return s #input state = [ [251, 64, 182, 81], [146, 168, 33, 80], [199, 159, 195, 24], [64, 80, 182, 255], ] shift_rows_state = shift_rows(state) for i in range(4): print(shift_rows_state[i]) #Output [251, 64, 182, 81] [168, 33, 80, 146] [195, 24, 199, 159] [255, 64, 80, 182] ``` #### Mix Columns ___ - Bước này, ta cần nhân từng cột của ma trận cần mã hóa với 1 ma trận cụ thể để ra 1 ma trận mới do các bytes trong cột bị thay đổi - Ví dụ, ta có 1 ma trận mẫu ![](https://i.imgur.com/E5Wsmy9.png) - Sau đó, ma trận trạng thái sẽ được xử lý như sau ![](https://i.imgur.com/SQIsHZW.png) - Lấy 1 ví dụ khác và code bằng Python ``` def mix_columns(state, form): for i in range(4): a = state[i][0] b = state[i][1] c = state[i][2] d = state[i][3] state[i][0] = (form[0][0] * a + form[0][1] * b + form[0][2] * c + form[0][3] * d) % 256 state[i][1] = (form[1][0] * a + form[1][1] * b + form[1][2] * c + form[1][3] * d) % 256 state[i][2] = (form[2][0] * a + form[2][1] * b + form[2][2] * c + form[2][3] * d) % 256 state[i][3] = (form[3][0] * a + form[3][1] * b + form[3][2] * c + form[3][3] * d) % 256 return state state = [ [251, 64, 182, 81], [146, 168, 33, 80], [199, 159, 195, 24], [64, 80, 182, 255], ] form = [ [1,2,3,4], [5,6,7,8], [9,10,11,12], [13,14,15,16], ] x=(mix_columns(state,form)) for i in x: print(i) #Output #[225, 233, 241, 249] #[133, 49, 221, 137] #[174, 178, 182, 186] #[254, 18, 38, 58] ``` #### AES Key schedule ___ - Nếu như gặp bài có tổng cộng 10 round thì ta phải kiếm 11 cái subkey đúng khum nà, nhưng mà tự nhập bằng tay có mà chớt mấc, nên là phương pháp này sẽ cung cấp 10 round key và chỉ cần nhập 1 key ban đầu - Bắt đầu với 1 khóa bí mật ``de6916a49c65d8d260ea5da05cf257ea`` thành 4 cột: ``` #w0 w1 w2 w3 de 69 16 a4 9c 65 d8 d2 60 ea 5d a0 5c f2 57 ea ``` - Mục tiêu là tìm được w4, w5, w6, w7 là khóa con thứ 1 - Sau đó, ta thực hiện RotWord với cột thứ 4 ``` #Cái này tự code đi nhaaa, giống Shift_rows á :v d2 a0 ea a4 ``` - Tiếp theo, ta sử dụng thao tác SubWord, mỗi byte được thay thế bằng cách sử dụng hộp S mà chúng ta đã xem xét trong bước SubBytes. Giả sử sau khi SubWord, ta thu được cột như sau: ``` #Này cũng tự code nha x0 x1 x2 x3 ``` - Cuối cùng, trong thao tác Rcon (hằng số vòng), cột được thêm vào một cột không đổi được xác định trước tương ứng với vòng hiện tại. Việc bổ sung ở đây tất nhiên là hoạt động xor. Đối với khóa 128 bit, các vectơ cột không đổi này là: ![](https://i.imgur.com/301FAWk.png) - Có nghĩa là ta sẽ làm như sau: ``` #Đang ở round 1 nên ta sẽ dùng 01 00 00 00 y0 = x0 ⊕ 01 y1 = x1 ⊕ 00 y2 = x2 ⊕ 00 y3 = x3 ⊕ 00 ``` - Và các giá trị y kia gọi đặt tên là W_processed, các w4, w5, w6, w7 tiếp theo sẽ làm như sau: ``` w4 = w0 ⊕ W_processed w5 = w1 ⊕ w4 w6 = w2 ⊕ w5 w7 = w3 ⊕ w6 ``` - Đó chính là round 1, với các round tiếp theo thì vẫn làm như thế nhưng mà sẽ đổi cột Rcon - Đoạn function tính rcon thứ i ``` def Rcon(i): """ Calculate the value of the Rcon constant at position i. """ Rcon_value = 1 if i == 0: return 0 # If i is not zero, use a loop to calculate the Rcon value for j in range(1, i): # Perform a left circular shift on Rcon_value Rcon_value = ((Rcon_value << 1) % 0x100) # Check if the leftmost bit of Rcon_value is set if Rcon_value & 0x80 == 0x80: # If so, perform a bitwise XOR with the value 0x1b Rcon_value = (Rcon_value ^ 0x1b) % 0x100 return Rcon_value ``` #### Tóm tắt thuật toán - Trước hết, quá trình mở rộng khóa diễn ra, sử dụng khóa bí mật 128 bit do người dùng cung cấp. Sau đó, đối với bất kỳ khối dữ liệu văn bản gốc 128 bit đã cho nào, phép biến đổi sau đây được áp dụng: - Addition of the first round key 9 Rounds: - Substitute Bytes - Shift Rows - Mix Columns - Adding the Round Key - The final round - Substitute Bytes - Shift Rows - Adding the Round Key # Block cipher modes of operation #### 1. Electronic Code Book (ECB) ___ - Electronic Code Book (ECB) là một trong những chế độ hoạt động của AES (Advanced Encryption Standard) - một thuật toán mã hóa đối xứng tiêu chuẩn được sử dụng rộng rãi trong các ứng dụng bảo mật. - Trong chế độ ECB, dữ liệu được chia thành các khối có kích thước cố định và mỗi khối được mã hóa độc lập với các khối khác bằng cách sử dụng cùng một khóa mã hóa. ![](https://i.imgur.com/Fmy4D2q.png) - Trong quá trình mã hóa, mỗi khối dữ liệu được truyền qua hàm mã hóa để tạo ra một chuỗi mã hóa tương ứng. Điều này có nghĩa là các khối dữ liệu giống nhau sẽ được mã hóa thành một chuỗi mã hóa giống nhau. Khi khối dữ liệu mã hóa được kết hợp lại, ta sẽ có được chuỗi mã hóa hoàn chỉnh. - Tuy nhiên, ECB có nhược điểm là không đảm bảo tính bảo mật cao nếu các khối mã hóa có cùng nội dung. Các kẻ tấn công có thể dễ dàng phát hiện ra sự trùng lặp giữa các khối dữ liệu và dễ dàng tấn công bằng các phương pháp tấn công theo phương pháp "chosen-plaintext" hoặc "ciphertext-only". Do đó, ECB không được sử dụng rộng rãi trong các ứng dụng bảo mật. - Đoạn code minh họa về cách mã hóa và giải mã ECB: ``` import base64 from Crypto.Cipher import AES from Crypto.Util.Padding import pad,unpad from binascii import* data = 'Tran Anh Nhat Viet dep trai' # Đây là dữ liệu cần mã hóa key = b'1122334455667788' # Đây là key (phải là 16 ký tự nếu là AES 128) def encrypt(raw): raw = pad(raw.encode(),16) # Thêm các byte không có giá trị để đảm bảo độ dài là bội số của 16 print('raw after pad:', raw) cipher = AES.new(key, AES.MODE_ECB) return (cipher.encrypt(raw)).hex() def decrypt(enc): enc = unhexlify(enc) cipher = AES.new(key, AES.MODE_ECB) print('cipher:', cipher.decrypt(enc)) return unpad(cipher.decrypt(enc),16) # Bỏ bớt các byte không có giá trị encrypted = encrypt(data) print('encrypted ECB Hexa:',encrypted) decrypted = decrypt(encrypted) print('data: ',decrypted) ``` #### 2. Cipher block chaining (CBC ___ - CBC (Cipher Block Chaining) là một trong những chế độ mã hóa trong thuật toán AES (Advanced Encryption Standard). Chế độ CBC được sử dụng để tăng tính an toàn của dữ liệu so với chế độ ECB. - CBC hoạt động bằng cách chia các khối plaintext thành các khối có cùng độ dài, sau đó thực hiện mã hóa các khối này bằng thuật toán AES. Tuy nhiên, trước khi mã hóa, mỗi khối plaintext sẽ được XOR với khối ciphertext của khối trước đó. Điều này có nghĩa là, đầu ra của khối trước đó sẽ được dùng để làm "vector khởi đầu" cho khối tiếp theo trước khi thực hiện mã hóa. ![](https://i.imgur.com/ZriVxxG.png) - Để bắt đầu quá trình mã hóa, một vector khởi đầu (initialization vector hay IV) được sử dụng cho khối đầu tiên của plaintext. IV có thể được coi như là một khối ciphertext "giả", được tạo ra một cách ngẫu nhiên và được cung cấp cùng với ciphertext đến bên giải mã. - Cụ thể, quá trình mã hóa trong chế độ CBC diễn ra như sau: - Khối dữ liệu đầu tiên của plaintext được XOR với IV (Initialization Vector) để tạo khối dữ liệu được mã hóa đầu tiên. - Khối dữ liệu đầu tiên được mã hóa bằng thuật toán AES. - Khối dữ liệu đầu tiên được mã hóa sau đó được XOR với khối dữ liệu tiếp theo của plaintext để tạo khối dữ liệu được mã hóa thứ hai. - Quá trình mã hóa tiếp tục cho đến khi hết các khối dữ liệu của plaintext. - Khi giải mã, quá trình giải mã được thực hiện bằng cách giải mã khối ciphertext và sau đó XOR với khối ciphertext của khối trước đó để khôi phục lại khối plaintext gốc. Đối với khối đầu tiên, khối ciphertext của nó sẽ được giải mã bằng thuật toán AES và sau đó XOR với IV để khôi phục lại khối plaintext gốc. - Đoạn code minh họa về cách mã hóa và giải mã CBC: ``` from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad import os key = b'this_is_a_keycbc'# Đây là key (phải là 16 ký tự nếu là AES 128) iv = b'1234567812345678'#Đây là Initialization Vector (phải là 16 ký tự nếu là AES 128) # Chuỗi cần được mã hóa plaintext = b'This is a secret message' def encrypt_CBC(key, iv, plaintext): cipher = AES.new(key, AES.MODE_CBC, iv) # Khởi tạo 1 đối tượng AES với key và iv plaintext = pad(plaintext,16) ciphertext = cipher.encrypt(plaintext) return ciphertext def decrypt_CBC(key, iv, ciphertext): cipher = AES.new(key, AES.MODE_CBC, iv) # Khởi tạo 1 đối tượng AES với key và iv plaintext = unpad(cipher.decrypt(ciphertext), 16) return plaintext # Mã hóa ciphertext = encrypt_CBC(key, iv, plaintext) print('Ciphertext:', ciphertext) # Giải mã decrypted_text = decrypt_CBC(key, iv, ciphertext) print('Plaintext:', decrypted_text) ``` #### 3. Propagating cipher block chaining (PCBC) ___ - Propagating cipher block chaining (PCBC) là một phương pháp mã hóa dữ liệu, trong đó, mỗi khối được mã hóa bằng cách kết hợp với khối trước đó thông qua phép XOR trước khi được truyền qua thuật toán mã hóa. Sau đó, khối mã hóa được sử dụng để mã hóa khối tiếp theo. Quá trình này được tiếp tục cho đến khi tất cả các khối dữ liệu được mã hóa. ![](https://i.imgur.com/VjRITwM.png) ![](https://i.imgur.com/VtrYJj0.png) - Quá trình mã hóa bằng PCBC: ``cj = enc(ci ^ pi ^ pj)`` với j > i. - Quá trình giải mã bằng PCBC: ``dj = ci ^ pi ^ dec(cj)`` với j > i. - Hiện tại, thuật toán AES không hỗ trợ chế độ hoạt động PCBC nên mục này sẽ không có code nha :v #### 4. Cipher Feedback (CFB) ___ - Cipher Feedback (CFB) là một mode của thuật toán mã hóa block (block cipher) trong mật mã học. CFB hoạt động bằng cách sử dụng output của block cipher như một số liệu vào cho việc mã hóa dữ liệu tiếp theo, thay vì sử dụng trực tiếp khóa để mã hóa dữ liệu đó. - Trong chế độ CFB, các khối plaintext sẽ được chia nhỏ thành các khối con và mỗi khối con này sẽ được mã hóa riêng bằng block cipher. Để thực hiện mã hóa, CFB sử dụng một vector khởi tạo (initialization vector - IV) để bắt đầu quá trình mã hóa. Kết quả của quá trình mã hóa sẽ được XOR với khối plaintext để tạo ra khối ciphertext tương ứng. Sau đó, khối ciphertext này sẽ được sử dụng làm đầu vào cho việc mã hóa khối tiếp theo. ![](https://i.imgur.com/YknN5Ik.png) - Cụ thể quá trình mã hóa CFB như sau: - Tạo một vector khởi tạo (IV) ngẫu nhiên có độ dài bằng với kích thước của block cipher. - Chia các khối plaintext thành các khối con có độ dài bằng với kích thước của block cipher. - Mã hóa vector khởi tạo bằng block cipher để tạo ra một khối ciphertext. - XOR khối ciphertext này với khối plaintext đầu tiên để tạo ra khối ciphertext đầu tiên. - Sử dụng khối ciphertext đầu tiên để mã hóa khối plaintext thứ hai. - Lặp lại 2 bước cuối - Đoạn code minh họa về cách mã hóa và giải mã CFB: ``` from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad key = b'1111111111111111' iv = b'1234567812345678' def encrypt(plaintext): cipher = AES.new(key, AES.MODE_CFB, iv) ciphertext = cipher.encrypt(pad(plaintext, AES.block_size)) return ciphertext def decrypt(ciphertext): cipher = AES.new(key, AES.MODE_CFB, iv) plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size) return plaintext # Mã hóa plaintext plaintext = b'This is a secret message' ciphertext = encrypt(plaintext) print('Ciphertext:', ciphertext.hex()) # Giải mã ciphertext decrypted_plaintext = decrypt(ciphertext) print('Decrypted plaintext:', decrypted_plaintext) ``` #### 5. Output feedback (OFB) ___ - Chế độ Output Feedback (OFB) trong mật mã học là một phương pháp sử dụng block cipher để mã hóa dữ liệu theo các khối có kích thước cố định. Giống như Cipher Feedback (CFB), OFB sử dụng một vector khởi tạo (IV) để khởi đầu quá trình mã hóa và sử dụng output của block cipher như một số liệu vào cho việc mã hóa các khối dữ liệu tiếp theo. - Để thực hiện mã hóa, OFB sử dụng một vector khởi tạo (initialization vector - IV) để bắt đầu quá trình mã hóa. Kết quả của quá trình mã hóa sẽ được XOR với khối plaintext để tạo ra khối ciphertext tương ứng. Sau đó, khối ciphertext này sẽ được sử dụng làm đầu vào cho việc mã hóa khối tiếp theo. ![](https://i.imgur.com/IVaYnPJ.png) - Quá trình mã hóa CFB như sau: - Tạo một vector khởi tạo (IV) ngẫu nhiên có độ dài bằng với kích thước của block cipher. - Chia các khối plaintext thành các khối con có độ dài bằng với kích thước của block cipher. - Mã hóa vector khởi tạo bằng block cipher để tạo ra một khối output. - Sử dụng khối output để mã hóa khối plaintext đầu tiên. - Lặp lại 2 bước cuối với các plaintext còn lại. - Đoạn code minh họa về cách mã hóa và giải mã OFB: ``` from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad key = b'1111111111111111' iv = b'1234567812345678' def encrypt(plaintext): cipher = AES.new(key, AES.MODE_OFB, iv) ciphertext = cipher.encrypt(pad(plaintext, AES.block_size)) return ciphertext def decrypt(ciphertext): cipher = AES.new(key, AES.MODE_OFB, iv) plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size) return plaintext # Mã hóa plaintext plaintext = b'This is a secret message' ciphertext = encrypt(plaintext) print('Ciphertext:', ciphertext.hex()) # Giải mã ciphertext decrypted_plaintext = decrypt(ciphertext) print('Decrypted plaintext:', decrypted_plaintext) ``` #### 6. Counter (CTR) ___ - Chế độ Counter (CTR) trong mật mã học là một phương pháp sử dụng block cipher để mã hóa dữ liệu theo các khối có kích thước cố định, tương tự như Cipher Block Chaining (CBC) và Cipher Feedback (CFB). Tuy nhiên, CTR sử dụng một chuỗi giá trị đếm (counter) để tạo ra các key stream (khối mã hóa) để mã hóa các khối dữ liệu, thay vì sử dụng kết quả của các khối dữ liệu trước đó như CFB hoặc CBC. ![](https://i.imgur.com/DN01HSS.png) - Quá trình mã hóa CTR như sau: - Tạo một chuỗi giá trị đếm ngẫu nhiên có độ dài bằng với kích thước của block cipher. - Chia các khối plaintext thành các khối con có độ dài bằng với kích thước của block cipher. - Mã hóa chuỗi giá trị đếm bằng block cipher để tạo ra một khối key stream. - XOR khối key stream với khối plaintext để tạo ra khối ciphertext. - Tăng giá trị đếm lên 1 và lặp lại 2 với tất cả các khối plaintext còn lại. - Đoạn code minh họa quá trình mã hóa và giải mã của CTR ``` from Crypto.Cipher import AES from Crypto.Util import Counter from Crypto.Util.Padding import pad,unpad import binascii plaintext = b'This is a secret message.' key = b'1111111111111111' iv = b'1234567812345678' def encrypt(plaintext): data_bytes = bytes(plaintext) cipher = AES.new(key,AES.MODE_CTR) ciphertext = cipher.encrypt(data_bytes) return ciphertext, cipher.nonce ciphertext,nonce = encrypt(plaintext) print(ciphertext) print(binascii.hexlify(ciphertext)) def decrypt(ciphertext,nonce): cipher= AES.new(key,AES.MODE_CTR,nonce=nonce) raw_bytes = cipher.decrypt(ciphertext) return raw_bytes plaintext = decrypt(ciphertext,nonce) print(plaintext) ``` # DES 2DES 3DES ### DES - Data Encryption Standard (DES) là một thuật toán mã hóa đối xứng được sử dụng để mã hóa dữ liệu điện tử. Đây là một trong những thuật toán mã hóa đầu tiên và phổ biến nhất được sử dụng trong những năm 1980 và đầu những năm 1990. - DES là một thuật toán mã hóa đối xứng, có nghĩa là nó sử dụng cùng một khóa để mã hóa và giải mã dữ liệu. Trong thuật toán DES, khóa được sử dụng để biến đổi dữ liệu theo các chu kỳ của hoán vị và thay thế, tạo ra một bản mã của dữ liệu đó. Khóa của DES có độ dài là 56 bit, có thể được mã hóa bằng nhiều phương pháp khác nhau. ![](https://i.imgur.com/LeOQgBD.png) - Thuật toán DES hoạt động dựa trên một chu kỳ rất lớn của các hoán vị và thay thế. Khi mã hóa, DES chia dữ liệu thành các khối 64 bit và sử dụng một khóa 56 bit để tạo ra một bản mã của dữ liệu đó. Khi giải mã, cùng một khóa được sử dụng để giải mã dữ liệu đã được mã hóa trước đó. #### Thuật toán DES ___ - Nếu bạn chán đọc chữ thì bạn có thể xem video này [Click here](https://www.youtube.com/watch?v=B7PSAeb7jL0) - Sơ đồ khái quát sẽ như sau: ![](https://i.imgur.com/d7cJYdr.png) - Trước hết, ta cần phải hiểu thuật toán sinh khóa của nó **1. Thuật toán sinh khóa** - Sơ đồ tổng quát sẽ như sau: ![](https://i.imgur.com/ArAhFn2.png) - Ban đầu, ta đưa 1 khóa chính KEY 64 bit, sau đó KEY sẽ bước vào PC-1 để hoán vị - PC-1 là 1 thuật toán hoán vị, sắp xếp lại KEY như sau: ![](https://i.imgur.com/bYGo3pH.png) - Lưu ý, sẽ không có các số [8,16,24,32,40,48,56,64] --> đưa ra 1 key_56 chỉ có 56 bit - Sau khi thu được key_56, ta chia đôi ra thành 2 nửa là C0 và D0, mỗi bên 28 bit - Đến bước LS, đây là 1 phép chuyển dịch vòng sang trái: - Chuyển dịch 1 vị trí nếu i = 1,2,9,16 - Chuyển dịch 2 vị trí với các i còn lại - Ví dụ, ta có chuỗi bit (theo lẽ là binary, nhưng mình ghi thế này cho dễ hiểu): 12345678 --> 1 vị trí --> 23456781 --> 1 vị trí 34567812 - Sau khi đi qua LS, ta thu được Ci, Di (i=1,2,...,16), thì sẽ đi PC-2 (cũng là 1 thuật toán hoán vị) như sau: ![](https://i.imgur.com/22yx3aW.png) - Sau khi qua PC-2, ta sẽ thu được Keyi tương ứng nhaaaa <3 **2. Quá trình mã hóa** ___ ![](https://i.imgur.com/DQrDnHQ.png) - Plaintext (64 bit) sẽ được đưa vào IP, IP cũng là 1 thuật toán hoán vị và sẽ sắp xếp lại như sau: ![](https://i.imgur.com/lklXVRy.png) - Sau khi đi qua IP, ta chia đôi ra thành 2 phần L0, R0 (mỗi bên 32 bit) - Ta lấy R0, ta cho đi qua Function F như sau: - Giá trị đầu vào gồm: - Ri: 32 bit - Keyi: 48 bit - Ban đầu, Ri 32 bit, sẽ đi qua phép hoán vị E như sau: ![](https://i.imgur.com/5QA2hlI.png) - Khi ra thì sẽ thu được 1 chuỗi 48 bit, rồi ta lấy XOR với Keyi, ta thu được 1 chuỗi After_XOR 48 bit - Chia After_XOR thành 8 khối (tương đương với 8 Security box), mỗi khối 6 bit (gọi là After_Split[i] ). Các S-box sẽ như sau: ![](https://i.imgur.com/Z8T0r5y.png) - Ví dụ ta có 1 đoạn After_Split[1] = 101110, ta sẽ làm như sau: - Lấy kí tự thứ 1 và thứ 6 của After_Split[1], ta được 10 đổi sang Decimal là 2 --> lấy hàng thứ 2 (có 4 hàng là 0, 1, 2, 3) - Lấy kí tự thứ 2, 3, 4, 5 của After_Split[1], ta thu được 0111 đổi sang Decimal là 7 --> lấy cột thứ 7 - ==> 101110 = 11 (Decimal) = 1011 (Binary) - Ví dụ ta có 1 đoạn After_Split[2] = 011000 thì sau khi đi qua S-box[2], ta thu được 12 (Decimal) = 1100 (Binary) - Sau khi thu được 8 phần, ta sẽ được 1 đoạn F(Ri, Keyi) là 32 bit - Ta lấy F(Ri, Keyi) ⊕ Li --> R tiếp theo - Còn L tiếp theo chính là R trước - Sau 16 lần như thế, ta lại đưa vào FP (Final permutation), cũng là 1 thuật toán hoán vị và sắp xếp lại như sau: ![](https://i.imgur.com/X0LJH5B.png) - Sau khi qua FP, ta sẽ thu được ciphertext lll **3. Quá trình giải mã** ___ - Quá trình giải mã DES cũng tương tự quá trình mã hóa. Chỉ khác là sẽ khác các khóa. Thay vì là từ 1 --> 16 thì giờ sẽ ngược lại và là 16 --> 1 ![](https://i.imgur.com/56GelpZ.png) ### 2DES and Attack in the Middle #### Double Des - DoubleDES (hay còn gọi là 2DES) là một thuật toán mã hóa đối xứng (symmetric encryption) sử dụng khối (block cipher) được xây dựng trên cơ sở của DES (Data Encryption Standard). DoubleDES sử dụng hai khóa ``Key1`` và ``Key2`` để thực hiện mã hóa và giải mã dữ liệu, mỗi khóa có độ dài 56 bit. ![](https://i.imgur.com/YuxXhWI.png) - Thuật toán DoubleDES thực hiện việc mã hóa bằng cách sử dụng hai giai đoạn mã hóa DES theo thứ tự liên tiếp. Vì vậy, DoubleDES có thể được biểu diễn bằng công thức: - ``Ciphertext = E(Key2, E(Key1, Plaintext))`` - ``Plaintext = D(Key1 ,D(Key2, Ciphertext))`` - Trong đó, Plaintext là dữ liệu cần được mã hóa, E(K1, Plaintext) là kết quả sau khi dữ liệu được mã hóa với khóa K1, và E(K2, E(K1, Plaintext)) là kết quả cuối cùng sau khi dữ liệu đã được mã hóa hai lần với hai khóa K1 và K2. - Thời gian BruteForce của 2DES là 2^56 x 2^56 = 2^112 --> Rất chi là lâu lunnn #### Attack in the Middle with 2DES - Giả sử, Hacker có được 1 cặp Plaintext_1 và Ciphertext_1 tương ứng, Hacker có thể tìm ra được 2 khóa bí mật nhờ kỹ thuật Attack in the Middle để có thể biết được nhiều thông tin hơn sau này - Cụ thể như sau, ta thấy công thức mã hóa và giải mã của 2DES là: - ``Ciphertext = E(Key2, E(Key1, Plaintext))`` - ``Plaintext = D(Key1 ,D(Key2, Ciphertext))`` - ==> ``D(Key2, Ciphertext) = E(Key1, Plaintext)`` - Giờ ta có 2 nửa là D(Key2, Ciphertext_1) và E(Key1, Plaintext_1), ta cần BruteForce sao cho kết quả 2 bên là như nhau, với tổng thời gian là 2^56 + 2^56 = 2^57, nhỏ hơn rất nhiều so với ban đầu. Như vậy meet in the middle attack đã làm cho 2DES không còn đáng tin cậy để áp dụng cho bảo mật thông tin. Mà thay vào đó là 3DES, hay AES… - Ta thử làm challenge sau đây: ``` from Crypto.Cipher import AES from Crypto.Util.Padding import pad from hashlib import md5 from os import urandom FLAG = b"KCSC{???????????????????????????}" assert len(FLAG) % 16 == 1 # hint key1 = md5(urandom(3)).digest() key2 = md5(urandom(3)).digest() cipher1 = AES.new(key1, AES.MODE_ECB) cipher2 = AES.new(key2,AES.MODE_ECB) print(pad(FLAG,16)[-16:]) enc = cipher1.encrypt(pad(FLAG,16)) enc = cipher2.encrypt(enc) print(enc.hex()) # 21477fac54cb5a246cb1434a1e39d7b34b91e5c135cd555d678f5c01b2357adc0c6205c3a4e3a8e6fb37c927de0eec95 ``` - Phân tích: - Ta thấy rằng có cái hint là ``len(FLAG) % 16 == 1`` --> Block 3 sẽ bị lẻ mất 1 ký tự ``}`` và sẽ padding 15 bytes ``\x0f`` vào sau để cho tròn 48 bytes. - Thế nên ta sẽ có được 1 block_plain là ``b'}\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f'``. - Giờ ta sẽ lấy last_block của flag_encrypted là ``last_block = flag_encrypted[-16:]``. Sau đó sẽ bruteforce các key sao cho ``Encrypt(block_plain, key1) == Decrypt(last_block, key2)``. ``` from Crypto.Util.number import long_to_bytes from Crypto.Util.Padding import unpad from hashlib import md5 from Crypto.Cipher import AES known = b'}\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f' flag_encrypted = bytes.fromhex("21477fac54cb5a246cb1434a1e39d7b34b91e5c135cd555d678f5c01b2357adc0c6205c3a4e3a8e6fb37c927de0eec95") last_block = flag_encrypted[-16:] def encrypt(plaintext, key): cipher = AES.new(key, AES.MODE_ECB) ciphertext = cipher.encrypt(plaintext) return ciphertext def decrypt(ciphertext, key): cipher = AES.new(key, AES.MODE_ECB) plaintext = cipher.decrypt(ciphertext) return plaintext middle = {} for i in range(256): for j in range(256): for k in range(256): key1 = bytes([i, j, k]) key1 = md5(key1).digest() middle[encrypt(known,key1)] = key1 print("done") for i in range(256): for j in range(256): for k in range(256): key2 = bytes([i, j, k]) key2 = md5(key2).digest() try: if decrypt(last_block, key2) in middle: key1 = middle[decrypt(last_block,key2)] print(f"key1 = {key1.hex()}") print(f"key2 = {key2.hex()}") print(unpad(decrypt(decrypt(flag_encrypted,key2),key1),16).decode()) exit(0) except: continue ``` > Flag: KCSC{MeEt_In_tHe_mIdDLe_AttaCk__} #### PKCS#7 Padding - PKCS#7 Padding là một phương thức đệm dữ liệu (padding) trong mật mã học để đảm bảo độ dài của dữ liệu được mã hóa phù hợp với kích thước khối của thuật toán mã hóa khối. - Trong PKCS#7 Padding, các byte đệm được sử dụng để điền vào các byte trống trong khối cuối cùng của dữ liệu cần mã hóa. Giá trị của các byte đệm sẽ bằng với số byte đệm cần thêm vào. Ví dụ, nếu chỉ còn thiếu 2 byte để đạt độ dài khối, thì 2 byte đệm có giá trị là 0x02 sẽ được thêm vào cuối khối. Nếu cần thêm 5 byte đệm, thì 5 byte đệm có giá trị là 0x05 sẽ được thêm vào. - Có các cách để sử dụng PKCS#7 Padding trong python, bạn có thể tham khảo ``` length = 16 def PKCS7(s): pad = 16 - (len(s) % length) if pad != 0: s += bytes([pad]*(16 - (len(s) % length))) return s print(len(b'Tran_Anh_Nhat_Viet_dep_trai_qua_<3')) print(PKCS7(b'Tran_Anh_Nhat_Viet_dep_trai_qua_<3')) print(len(PKCS7(b'Tran_Anh_Nhat_Viet_dep_trai_qua_<3'))) # 34 # b'Tran_Anh_Nhat_Viet_dep_trai_qua_<3\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e' # 48 ``` Hoặc có thể đệm dữ liệu để có thể đủ kích thước để mã hóa AES ``` from Crypto.Cipher import AES # Dữ liệu cần mã hóa data = b'nhatziet' # Đệm dữ liệu trước khi mã hóa block_size = AES.block_size padding_length = block_size - (len(data) % block_size) padding = bytes([padding_length] * padding_length) padded_data = data + padding print(padded_data) #b'nhatziet\x08\x08\x08\x08\x08\x08\x08\x08' ``` Hoặc có thể dùng hàm pad của Python ``` from Crypto.Cipher import AES from Crypto.Util.Padding import pad # Dữ liệu cần mã hóa data = b'nhatziet' # Đệm dữ liệu trước khi mã hóa padded_data = pad(data, AES.block_size, style='pkcs7') print(padded_data) #b'nhatziet\x08\x08\x08\x08\x08\x08\x08\x08' ``` - Khi giải mã, quá trình giải mã được thực hiện bằng cách xóa các byte đệm thêm vào cuối cùng của khối cuối cùng. Giá trị của byte cuối cùng được sử dụng để xác định số lượng byte đệm cần phải xóa. - PKCS#7 Padding được sử dụng trong nhiều thuật toán mã hóa khối như AES, DES, Triple DES, và Blowfish. Tuy nhiên, nó cũng có một số nhược điểm như là nó chỉ có thể được sử dụng khi độ dài của dữ liệu được mã hóa chính xác bằng một số nguyên bội của kích thước khối của thuật toán mã hóa. ### 3DES - TripleDES (hay còn gọi là 3DES) là một thuật toán mã hóa đối xứng có khóa độ dài 168-bit, được sử dụng rộng rãi trong các ứng dụng bảo mật thông tin. Nó được xây dựng dựa trên thuật toán DES (Data Encryption Standard), một thuật toán mã hóa đối xứng có khóa độ dài 56-bit. TripleDES sử dụng 3 lần mã hóa DES để tăng độ bảo mật. - Thuật toán TripleDES có thể được sử dụng trong hai chế độ mã hóa khác nhau: chế độ EDE (Encrypt-Decrypt-Encrypt) và chế độ EEE (Encrypt-Encrypt-Encrypt). Trong chế độ EDE, plaintext được mã hóa bằng thuật toán DES, sau đó được giải mã bằng thuật toán DES và cuối cùng được mã hóa bằng thuật toán DES lần nữa. Trong chế độ EEE, plaintext được mã hóa bằng thuật toán DES ba lần liên tiếp. - Encrypt-Decrypt-Encrypt Mode: ![](https://i.imgur.com/wb22xEF.png) - Các bước trong quá trình mã hóa TripleDES chế độ EDE như sau: - Chọn một khóa bí mật có độ dài 192-bit. - Chia khóa thành 3 phần bằng nhau, mỗi phần có độ dài 64-bit. - Sử dụng phần đầu tiên của khóa để mã hóa plaintext bằng thuật toán DES. - Sử dụng phần thứ hai của khóa để giải mã kết quả từ bước trên bằng thuật toán DES. - Sử dụng phần cuối cùng của khóa để mã hóa kết quả từ bước trên bằng thuật toán DES. - Kết quả sau khi mã hóa sẽ là ciphertext. - Các bước trong quá trình giải mã TripleDES chế độ EDE như sau: - Sử dụng phần cuối cùng của khóa để giải mã ciphertext bằng thuật toán DES. - Sử dụng phần thứ hai của khóa để mã hóa kết quả từ bước trên bằng thuật toán DES. - Sử dụng phần đầu tiên của khóa để giải mã kết quả từ bước trên bằng thuật toán DES. - Kết quả sau khi giải mã sẽ là plaintext. - Encrypt-Encrypt-Encrypt Mode: ![](https://i.imgur.com/ut9MqJP.png) - Các bước trong quá trình mã hóa TripleDES chế độ EEE như sau: - Chọn một khóa bí mật có độ dài 192-bit. - Chia khóa thành 3 phần bằng nhau, mỗi phần có độ dài 64-bit. - Sử dụng phần đầu tiên của khóa để mã hóa plaintext bằng thuật toán DES. - Sử dụng phần thứ hai của khóa để mã hóa kết quả từ bước trên bằng thuật toán DES. - Sử dụng phần cuối cùng của khóa để mã hóa kết quả từ bước trên bằng thuật toán DES. - Kết quả sau khi mã hóa sẽ là ciphertext. - Các bước trong quá trình giải mã TripleDES chế độ EEE như sau: - Sử dụng phần thứ ba của khóa để giải mã ciphertext bằng thuật toán DES. - Sử dụng phần thứ hai của khóa để giải mã kết quả từ bước trên bằng thuật toán DES. - Sử dụng phần thứ nhất của khóa để giải mã kết quả từ bước trên bằng thuật toán DES. - Kết quả sau khi giải mã sẽ là plaintext. - Lưu ý rằng TripleDES là một thuật toán khá chậm vì nó phải thực hiện 3 lần mã hóa/giải mã trên cùng một khóa. Tuy nhiên, TripleDES vẫn được sử dụng rộng rãi trong các ứng dụng yêu cầu độ bảo mật cao nhưng không cần tốc độ mã hóa/giải mã nhanh như các ứng dụng tài chính, bảo mật thông tin, hệ thống thanh toán điện tử, lưu trữ dữ liệu, v.v. - Tuy nhiên, TripleDES cũng có nhược điểm, đó là độ dài khóa chỉ có 112-bit hiệu quả, do đó có thể dễ dàng bị tấn công brute-force nếu attacker có đủ thời gian và tài nguyên. Vì vậy, TripleDES hiện đã được thay thế bởi các thuật toán mã hóa đối xứng hiện đại hơn như AES (Advanced Encryption Standard) với độ dài khóa lên tới 256-bit và tốc độ mã hóa/giải mã nhanh hơn. # Padding Oracle Attack Nếu bạn chưa hiểu cách tấn công này là gì thì bạn nên xem [video](https://www.youtube.com/watch?v=uDHo-UAM6_4) này. Padding Oracle Attack, ban đầu mình cũng tưởng là dành cho ECB thế nhưng nó thường được sử dụng trong các block mode có IV để xor với ouput. Những bài thế này, thường thì sẽ cho ta 1 ciphertext của flag và iv mã hóa. Sau đó, ta cần phải gửi iv và ciphertext vào, nếu có thể unpad theo PKCS#7 thì sẽ trả về True, còn không sẽ trả về False. Nhìn thì có thể không cách nào tấn công được, thế nhưng, ta sẽ dựa vào cách padding của PKCS#7 để tấn công dạng này. Giờ ta có ví dụ sau đây ![image](https://hackmd.io/_uploads/rJLL_T4FT.png) Giờ ta muốn attack block thứ 2 kia, ta sẽ phải làm như sau: - Trước hết, ta cần phải lấy 1 ``ciphertext1' == ciphertext1[:15]`` và thêm byte cuối cùng (gọi là ``add_byte`` đi ha) của ciphertext1 và ta có ``ciphertext1' ^ decryption2 = plaintext2'``. Cụ thể ở đây ``ciphertext1' == lmaolmaolmaolma + add_byte``. - Giờ ta sẽ tìm add_byte sao cho``ciphertext1'[15] ^ decryption2[15] == \x01``. plaintext2'[15] phải bắt buộc bằng ``\x01`` vì chỉ có thế mới có thể unpad theo PKCS#7 được. Cụ thể là tìm sao cho ``add_byte ^ d == \x01``. - Giờ ta có thể thu được ``decryption2[15] == \x01 ^ add_byte``. Ta sẽ tìm lại được giá trị ``d``. - Tiếp theo, ta sẽ lấy ``decryption2[15] ^ ciphertext1[15]`` thì sẽ thu được plaintext2[15]. Cụ thể là ``d ^ o`` á. - Thế giờ muốn tấn công plaintext2[14] thì sao ??? - Ta lại lấy thêm 1 ciphertext1' nhưng mà giờ bị mất đi 2 bytes cuối nha. - Vì đã có decryption2[15] rồi và để thỏa mãn PKCS#7 thì ``decryption2[15] ^ ciphertext1'[15] = \x02`` thế nên``ciphertext1' = ciphertext1[:14] + add_byte + (decryption2[15] ^ \x02)``. Tức là ``ciphertext = lmaolmaolmaolm + add_byte + (decryption2[15] ^ \x02)``. - Giờ ta sẽ tìm add_byte sao cho ``add_byte ^ decryption2[14] == \x02``. Từ đó, ta sẽ tìm được ``decryption[14]`` và thu lại được plaintext2[14] như cách trên. ### Chall_1 Chall này để ví dụ đây nhaa, nhìn có vẻ lú cái đầu :cry: ``` from Crypto.Cipher import AES from os import urandom import string chars = string.ascii_lowercase + string.ascii_uppercase + string.digits + '!_{}' FLAG = b'KCSC{CBC_p4dd1ng_0racle_attack}' assert all(i in chars for i in FLAG.decode()) def pad(msg, block_size): pad_len = 16 - len(msg) % block_size return msg + bytes([pad_len])*pad_len def encrypt(key): iv = 'vietvietvietviet' cipher = AES.new(key, AES.MODE_CBC, iv) return (iv + cipher.encrypt(pad(FLAG,16))).hex() def decrypt(enc,key): enc = bytes.fromhex(enc) iv = enc[:16] ciphertext = enc[16:] cipher = AES.new(key, AES.MODE_CBC, iv) decrypted = cipher.decrypt(ciphertext) pad_len = decrypted[-1] print(pad_len) print(decrypted.hex()) if all(i == pad_len for i in decrypted[-pad_len:]): return 'Decrypted successfully.' else: return 'Incorrect padding.' if __name__ == '__main__': key = b'hello_hello_hell' while True: choice = input() if choice == 'encrypt': print(encrypt(key)) elif choice == 'decrypt': c = input('Ciphertext: ') try: print(decrypt(c,key)) except: continue # lấy iv và key như thế cho dễ ví dụ nha. ``` Ta sẽ được iv và encrypt_flag từ output của bài cho. Sau đó, ta cần phải gửi iv mới và ciphertext mới vào server. Nếu có thể unpad được thì sẽ trả về True, còn ngược lại là False. Giờ cách tấn công sẽ như thế nào. Trước hết, mình tấn công block 2 trước nha. ``` from pwn import* from Crypto.Util.number import* io = remote("localhost", 1337) # lấy iv và encrypt_flag thuiii io.sendline(b'encrypt') encrypt = io.recvuntil(b'\n') encrypt = encrypt[:-1].decode() encrypt = bytes.fromhex(encrypt) iv = encrypt[:16] ciphertext = encrypt[16:] ciphertext1 = ciphertext[:16] ciphertext2 = ciphertext[16:32] print(ciphertext1.hex()) print(ciphertext2.hex()) # e3f32290b03eeb08780c171c500a12a0 # 11a5bf175e9967272efbde9400fd73c1 ``` Giờ ta có 2 block, kệ lun block2 nhá. Giờ ta quan tâm tới block1 thui. Giờ ta cũng sẽ làm như trên, ``ciphertext1' = "e3f32290b03eeb08780c171c500a12" + add_byte``. Brute force add_byte sao cho trả về True. Mình sẽ tấn công byte cuối cùng của plaintext2 cho bạn coi nha. ``` from pwn import* from Crypto.Util.number import* io = remote("localhost", 1337) # lấy iv và encrypt_flag thuiii io.sendline(b'encrypt') encrypt = io.recvuntil(b'\n') encrypt = encrypt[:-1].decode() encrypt = bytes.fromhex(encrypt) iv = encrypt[:16] ciphertext = encrypt[16:] ciphertext1 = ciphertext[:16] ciphertext2 = ciphertext[16:32] print(ciphertext1.hex()) print(ciphertext2.hex()) plaintext2 = b'' for i in range(1,2): temp = ciphertext1[:16-i] for add_byte in range(256): send = temp + long_to_bytes(add_byte) + ciphertext2 # print(long_to_bytes(b).hex(),len(send)) io.sendline(b'decrypt') io.recvuntil(b'Ciphertext: ') io.sendline(send.hex().encode()) receive = io.recvuntil(b'\n',drop=True) # print(receive) receive = io.recvuntil(b'\n',drop=True).decode() # print(receive) receive = io.recvuntil(b'\n',drop=True) if receive == b'Decrypted successfully.': # print(receive, long_to_bytes(i)) plaintext2 = xor(xor(long_to_bytes(i),(add_byte)), (ciphertext[16-i:17-i])) print(plaintext2) break ``` ![image](https://hackmd.io/_uploads/B1l7x04KT.png) Vì len(flag) % 16 = 15 nên là byte cuối cùng sẽ thu được là b'\x01'. Giờ mình sẽ tấn công byte nữa nha. Cần sửa code vì phải sửa để ``decryption2[15] ^ ciphertext1'[15] = \x02``. ``` plaintext2 = b'' new_xor = b"" for i in range(1,3): temp = ciphertext1[:16-i] for b in range(256): if len(plaintext2) > 0: pad = long_to_bytes(i)*(i-1) send = temp + long_to_bytes(b) + xor(pad, new_xor) + ciphertext[16:] else: send = temp + long_to_bytes(b) + ciphertext[16:] # print(long_to_bytes(b).hex(),len(send)) io.sendline(b'decrypt') io.recvuntil(b'Ciphertext: ') io.sendline(send.hex().encode()) receive = io.recvuntil(b'\n',drop=True) # print(receive) receive = io.recvuntil(b'\n',drop=True).decode() # print(receive) receive = io.recvuntil(b'\n',drop=True) if receive == b'Decrypted successfully.': # print(receive, long_to_bytes(i)) new_xor = xor(long_to_bytes(i),(b)) + new_xor plaintext2 = xor(xor(long_to_bytes(i),(b)), (ciphertext[16-i:17-i])) + plaintext2 print(plaintext2) break ``` ![image](https://hackmd.io/_uploads/B11kb0NK6.png) Đó thu được dấu "}" rồi kìa, giờ tăng giới hạn vòng for ra là được thui. ``` from pwn import* from Crypto.Util.number import* io = remote("localhost", 1337) # lấy iv và encrypt_flag thuiii io.sendline(b'encrypt') encrypt = io.recvuntil(b'\n') encrypt = encrypt[:-1].decode() encrypt = bytes.fromhex(encrypt) iv = encrypt[:16] ciphertext = encrypt[16:] ciphertext1 = ciphertext[:16] ciphertext2 = ciphertext[16:32] print(ciphertext1.hex()) print(ciphertext2.hex()) plaintext2 = b'' new_xor = b"" for i in range(1,17): temp = ciphertext1[:16-i] for b in range(256): if len(plaintext2) > 0: pad = long_to_bytes(i)*(i-1) send = temp + long_to_bytes(b) + xor(pad, new_xor) + ciphertext[16:] else: send = temp + long_to_bytes(b) + ciphertext[16:] # print(long_to_bytes(b).hex(),len(send)) io.sendline(b'decrypt') io.recvuntil(b'Ciphertext: ') io.sendline(send.hex().encode()) receive = io.recvuntil(b'\n',drop=True) # print(receive) receive = io.recvuntil(b'\n',drop=True).decode() # print(receive) receive = io.recvuntil(b'\n',drop=True) if receive == b'Decrypted successfully.': # print(receive, long_to_bytes(i)) new_xor = xor(long_to_bytes(i),(b)) + new_xor plaintext2 = xor(xor(long_to_bytes(i),(b)), (ciphertext[16-i:17-i])) + plaintext2 print(plaintext2) break ``` ![image](https://hackmd.io/_uploads/H1BXbCNKT.png) Giờ sẽ tấn công plaintext1, vấn đề không phải là sửa ciphertext1 nữa mà là sửa iv để tấn công. Cũng tương tự thế thui, mình phang code lun nhaa. ``` plaintext1 = b"" new_xor = b"" for i in range(1, 17): temp = iv[:16-i] for b in range(256): if len(plaintext1) > 0: pad = long_to_bytes(i)*(i-1) send = temp + long_to_bytes(b) + xor(pad, new_xor) + ciphertext[:16] else: send = temp + long_to_bytes(b) + ciphertext[:16] # print(long_to_bytes(b).hex(),(send)) io.sendline(b'decrypt') io.recvuntil(b'Ciphertext: ') io.sendline(send.hex().encode()) receive = io.recvuntil(b'\n',drop=True) # print(receive) receive = io.recvuntil(b'\n',drop=True).decode() # print(receive) receive = io.recvuntil(b'\n',drop=True) if receive == b'Decrypted successfully.': # print(receive, long_to_bytes(i)) new_xor = xor(long_to_bytes(i),(b)) + new_xor plaintext1 = xor(xor(long_to_bytes(i),(b)), (iv[16-i:17-i])) + plaintext1 print(plaintext1) break ``` ![image](https://hackmd.io/_uploads/Hyr3-0VYp.png) Từ đó ta thu được flag thuiii. > KCSC{CBC_p4dd1ng_0racle_attack} ### Chall_2: PAPER PLANE CRYPTOHACK ``` from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad import os KEY = ? FLAG = ? class AesIge: def __init__(self, key): self.cipher = AES.new(key, AES.MODE_ECB) def encrypt(self, data, m0=os.urandom(16), c0=os.urandom(16)): data = pad(data, 16, 'pkcs7') last_block_plaintext = m0 last_block_ciphertext = c0 result = b'' for i in range(0, len(data), 16): block = data[i: i + 16] x = AesIge._xor_blocks(block, last_block_ciphertext) x = self.cipher.encrypt(x) x = AesIge._xor_blocks(x, last_block_plaintext) result += x last_block_plaintext = block last_block_ciphertext = x return result, m0, c0 def decrypt(self, data, m0, c0): last_block_plaintext = m0 last_block_ciphertext = c0 result = b'' for i in range(0, len(data), 16): block = data[i: i + 16] x = AesIge._xor_blocks(block, last_block_plaintext) x = self.cipher.decrypt(x) x = AesIge._xor_blocks(x, last_block_ciphertext) result += x last_block_ciphertext = block last_block_plaintext = x if AesIge._is_pkcs7_padded(result): return unpad(result, 16, 'pkcs7') else: return None @staticmethod def _is_pkcs7_padded(message): padding = message[-message[-1]:] return all(padding[i] == len(padding) for i in range(0, len(padding))) @staticmethod def _xor_blocks(a, b): return bytes([x ^ y for x, y in zip(a, b)]) @chal.route('/paper_plane/encrypt_flag/') def encrypt_flag(): ciphertext, m0, c0 = AesIge(KEY).encrypt(FLAG.encode()) return {"ciphertext": ciphertext.hex(), "m0": m0.hex(), "c0": c0.hex()} @chal.route('/paper_plane/send_msg/<ciphertext>/<m0>/<c0>/') def send_msg(ciphertext, m0, c0): ciphertext = bytes.fromhex(ciphertext) m0 = bytes.fromhex(m0) c0 = bytes.fromhex(c0) if len(ciphertext) % 16 != 0: return {"error": "Data length must be a multiple of the blocksize!"} if len(c0) != 16 or len(m0) != 16: return {"error": "m0 and c0 must be 16 bytes long!"} plaintext = AesIge(KEY).decrypt(ciphertext, m0, c0) if plaintext is not None: return {"msg": "Message received"} else: return {"error": "Can't decrypt the message."} ``` Vì request lên server thì sẽ bị lâu nên mình build localhost cho tiện nhaaa. ``` from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad import os KEY = b'1234567812345678' FLAG = '' class AesIge: def __init__(self, key): self.cipher = AES.new(key, AES.MODE_ECB) def encrypt(self, data, m0=b'helohelohelohelo', c0=b'1234123412341234'): data = pad(data, 16, 'pkcs7') last_block_plaintext = m0 last_block_ciphertext = c0 result = b'' for i in range(0, len(data), 16): block = data[i: i + 16] x = AesIge._xor_blocks(block, last_block_ciphertext) x = self.cipher.encrypt(x) x = AesIge._xor_blocks(x, last_block_plaintext) result += x last_block_plaintext = block last_block_ciphertext = x return result, m0, c0 def decrypt(self, data, m0, c0): last_block_plaintext = m0 last_block_ciphertext = c0 result = b'' for i in range(0, len(data), 16): block = data[i: i + 16] x = AesIge._xor_blocks(block, last_block_plaintext) x = self.cipher.decrypt(x) x = AesIge._xor_blocks(x, last_block_ciphertext) result += x last_block_ciphertext = block last_block_plaintext = x if AesIge._is_pkcs7_padded(result): return unpad(result, 16, 'pkcs7') else: return None def _is_pkcs7_padded(message): padding = message[-message[-1]:] return all(padding[i] == len(padding) for i in range(0, len(padding))) def _xor_blocks(a, b): return bytes([x ^ y for x, y in zip(a, b)]) def encrypt_flag(): ciphertext, m0, c0 = AesIge(KEY).encrypt(FLAG.encode()) return {"ciphertext": ciphertext.hex(), "m0": m0.hex(), "c0": c0.hex()} def send_msg(ciphertext, m0, c0): ciphertext = bytes.fromhex(ciphertext) m0 = bytes.fromhex(m0) c0 = bytes.fromhex(c0) if len(ciphertext) % 16 != 0: return {"error": "Data length must be a multiple of the blocksize!"} if len(c0) != 16 or len(m0) != 16: return {"error": "m0 and c0 must be 16 bytes long!"} plaintext = AesIge(KEY).decrypt(ciphertext, m0, c0) if plaintext is not None: return {"msg": "Message received"} else: return {"error": "Can't decrypt the message."} while True: choice = input() if choice == "encrypt": print(encrypt_flag()) elif choice == "decrypt": c = input("Ciphertext: ") m = input("m0: ") d = input("d0: ") try: print(send_msg(c,m,d)) except: continue ``` Ta có sơ đồ của loại mã hóa này như sau ![image](https://hackmd.io/_uploads/Skl13zrt6.png) Bài này vì dùng 2 cái iv nên dễ bị lộn á, thế nên mình sẽ tấn công từng block. Giờ mình sẽ tìm plaintext1 trước để làm tiền đề cho giải mã block sau. ![image](https://hackmd.io/_uploads/HJMdnzrKa.png) Giờ đơn giản thôi, mình sẽ liên tục gửi Ciphertext1, m0 và c0' là giá trị để có thể tấn công. ``` import json from pwn import * from Crypto.Util.number import * io = remote("localhost", 1337) io.sendline(b'encrypt') encrypt = io.recvuntil(b'\n', drop=True).decode() encrypt = (encrypt.split(',')) encrypt_flag = encrypt[0].split(" ")[1] encrypt_flag = bytes.fromhex(encrypt_flag[1:len(encrypt_flag)-1]) encrypt_flag_1 = encrypt_flag[:16] encrypt_flag_2 = encrypt_flag[16:] m0 = encrypt[1].split(" ")[2] m0 = bytes.fromhex(m0[1:len(m0)-1]) c0 = encrypt[2].split(" ")[2] c0 = bytes.fromhex(c0[1:len(c0)-2]) plaintext1 = b'' new_xor = b"" for i in range(1,17): temp = c0[:16-i] for add_byte in range(256): if len(plaintext1) > 0: pad = long_to_bytes(i)*(i-1) send = temp + long_to_bytes(add_byte) + xor(pad, new_xor) else: send = temp + long_to_bytes(add_byte) io.sendline(b'decrypt') io.recvuntil(b'Ciphertext: ') io.sendline(encrypt_flag_1.hex().encode()) io.recvuntil(b'm0: ') io.sendline(m0.hex().encode()) io.recvuntil(b'd0: ') io.sendline(send.hex().encode()) text = io.recvuntil(b'\n', drop=True).decode() text = io.recvuntil(b'\n', drop=True).decode() if 'Message received' in text: new_xor = xor(long_to_bytes(i),(add_byte)) + new_xor plaintext1 = xor(xor(long_to_bytes(i),(add_byte)), (c0[16-i:17-i])) + plaintext1 print(plaintext1) break ``` ![image](https://hackmd.io/_uploads/Skh0y_Btp.png) Hurrayyy!!! ``Plaintext1 = b'crypto{h3ll0_t3l'``. Giờ mình sẽ tiếp tục tấn công bằng cách gửi Ciphertext2, Plaintext1 và Ciphertext1' nhaaa. ``` plaintext2 = b"" new_xor = b"" for i in range(1, 17): temp = encrypt_flag_1[:16-i] for add_byte in range(256): if len(plaintext2) > 0: pad = long_to_bytes(i)*(i-1) send = temp + long_to_bytes(add_byte) + xor(pad, new_xor) else: send = temp + long_to_bytes(add_byte) io.sendline(b'decrypt') io.recvuntil(b'Ciphertext: ') io.sendline(encrypt_flag_2.hex().encode()) io.recvuntil(b'm0: ') io.sendline(plaintext1.hex().encode()) io.recvuntil(b'd0: ') io.sendline(send.hex().encode()) text = io.recvuntil(b'\n', drop=True).decode() text = io.recvuntil(b'\n', drop=True).decode() if 'Message received' in text: new_xor = xor(long_to_bytes(i),(add_byte)) + new_xor plaintext2 = xor(xor(long_to_bytes(i),(add_byte)), (encrypt_flag[16-i:17-i])) + plaintext2 print(plaintext2) break ``` ![image](https://hackmd.io/_uploads/rJuSMdHKT.png) Taddaaaa~~~ Thu được flag rùi. Cách tấn công sẽ là như thế, nhưng mà bạn phải sửa để request chứ không phải giao tiếp qua docker như mình nha. Source để solve chall trên cryptohack sau đây: Bài này bạn nên chia code tìm 2 block riêng cho nó nhanh nha. ``` import json import requests from pwn import * from Crypto.Util.number import * r = requests.get('https://aes.cryptohack.org/paper_plane/encrypt_flag') output = json.loads(r.text) encrypt_flag = bytes.fromhex(output["ciphertext"]) encrypt_flag_1 = encrypt_flag[:16] encrypt_flag_2 = encrypt_flag[16:] m0 = bytes.fromhex(output["m0"]) c0 = bytes.fromhex(output["c0"]) plaintext1 = b'' new_xor = b"" for i in range(1,17): temp = c0[:16-i] for add_byte in range(256): if len(plaintext1) > 0: pad = long_to_bytes(i)*(i-1) send = temp + long_to_bytes(add_byte) + xor(pad, new_xor) else: send = temp + long_to_bytes(add_byte) print(send.hex()) url = 'https://aes.cryptohack.org/paper_plane/send_msg/' r = requests.get(url + encrypt_flag_1.hex() + "/" + m0.hex() + "/" + send.hex()) if 'Message received' in r.text: new_xor = xor(long_to_bytes(i),(add_byte)) + new_xor plaintext1 = xor(xor(long_to_bytes(i),(add_byte)), (c0[16-i:17-i])) + plaintext1 print(plaintext1) break print(plaintext1) # b'crypto{h3ll0_t3l' ``` Block 2 mình hay bị die nên là mình chia thành 10 threads cho nó cháy. ``` import threading import json import requests from pwn import * from Crypto.Util.number import * def run(): r = requests.get('https://aes.cryptohack.org/paper_plane/encrypt_flag') output = json.loads(r.text) encrypt_flag = bytes.fromhex(output["ciphertext"]) encrypt_flag_1 = encrypt_flag[:16] encrypt_flag_2 = encrypt_flag[16:] m0 = bytes.fromhex(output["m0"]) c0 = bytes.fromhex(output["c0"]) plaintext1 = b'crypto{h3ll0_t3l' plaintext2 = b"" new_xor = b"" for i in range(1,17): temp = encrypt_flag_1[:16-i] for add_byte in range(255,-1,-1): if len(plaintext2) > 0: pad = long_to_bytes(i)*(i-1) send = temp + long_to_bytes(add_byte) + xor(pad, new_xor) else: send = temp + long_to_bytes(add_byte) print(send) if i==1: if send.hex() != "7e05f59cf77ce0bb0f764e3f94f4a445": url = 'https://aes.cryptohack.org/paper_plane/send_msg/' r = requests.get(url + encrypt_flag_2.hex() + "/" + plaintext1.hex() + "/" + send.hex()) if 'Message received' in r.text: new_xor = xor(long_to_bytes(i),(add_byte)) + new_xor plaintext2 = xor(xor(long_to_bytes(i),(add_byte)), (encrypt_flag_1[16-i:17-i])) + plaintext2 print(plaintext2) break else: url = 'https://aes.cryptohack.org/paper_plane/send_msg/' r = requests.get(url + encrypt_flag_2.hex() + "/" + plaintext1.hex() + "/" + send.hex()) if 'Message received' in r.text: new_xor = xor(long_to_bytes(i),(add_byte)) + new_xor plaintext2 = xor(xor(long_to_bytes(i),(add_byte)), (encrypt_flag_1[16-i:17-i])) + plaintext2 print(plaintext2) break threads = [] for i in range(10): thread = threading.Thread(target=run) threads.append(thread) thread.start() for thread in threads: thread.join() ``` # Key-Recovery Attacks on GCM with Repeated Nonces ### The Galois/Counter Mode of Operation (GCM) Giờ ta có 1 challenge như GCM_AES như sau: ```python3 import sys from Crypto.Cipher import AES from Crypto.Random import get_random_bytes flag = b"utflag{6cm_f0rb1dd3n_4774ck_777}" KEY = b'1234567812345678' NONCE = b'lmaolmaolmaolmao' def aes_gcm_encrypt(plaintext): cipher = AES.new(KEY, AES.MODE_GCM, nonce=NONCE) ciphertext, tag = cipher.encrypt_and_digest(plaintext) return ciphertext.hex(), tag.hex() def aes_gcm_decrypt(ciphertext, tag): cipher = AES.new(KEY, AES.MODE_GCM, nonce=NONCE) plaintext = cipher.decrypt_and_verify(ciphertext, tag) return plaintext if __name__ == '__main__': options = '''Welcome to the AES GCM encryption and decryption tool! 1. Encrypt message 2. Decrypt message 3. Quit ''' def encrypt_msg(): print("Input a string to encrypt (must be at least 32 characters):") user_input = input() if len(user_input) < 32: sys.exit() output = aes_gcm_encrypt(user_input.encode()) print("Here is your encrypted string & tag, have a nice day :)") print(output) def decrypt_msg(): print("Input a hex string and its tag to decrypt:") user_input = bytearray.fromhex(input()) tag = bytearray.fromhex(input()) try: output = aes_gcm_decrypt(user_input, tag) except ValueError: print("Decryption failed :(") return print("Here is your decrypted string, have a nice day :)") print(output) def quit(): sys.exit() menu = { '1' : encrypt_msg, '2' : decrypt_msg, '3' : quit } i = 0 print('flag', aes_gcm_encrypt(flag)[0]) while i < 10: print(options) print('Select option: ') choice = input() if choice not in menu.keys(): print("Not a valid choice...") sys.exit() menu[choice]() i += 1 ``` Dù sao thì đây vẫn là 1 dạng stream cipher, ta chỉ cần ``encrypt_flag ^ chosen_ciphertext ^ chosen_plaintext`` là sẽ thu được flag đúng không nào ~~ ``` from pwn import remote, log def xor(s1, s2): if(len(s1) == 1 and len(s1) == 1): return bytes([ord(s1) ^ ord(s2)]) else: return bytes(x ^ y for x, y in zip(s1, s2)) def main(): payload = b'a' * 32 conn = remote('localhost', 1337) conn.recv(5) flag_enc = bytes.fromhex(conn.recv(64).decode()) conn.recv() conn.sendline(b'1') conn.recvline() conn.sendline(payload) conn.recvline() ciphertext = bytes.fromhex(conn.recv(66).decode()[2:]) # flag_enc xor ciphertext xor payload = # e(k, iv) xor flag xor e(k, iv) xor payload xor payload = # flag flag = xor(xor(flag_enc, ciphertext), payload) log.info("flag: " + str(flag)) if __name__ == '__main__': main() ``` Thế nhưng, giờ ta phải xác nhận là admin thì sao ??? Sửa code 1 tí ta lại có chall như sau: ```python3 import sys from Crypto.Cipher import AES from Crypto.Random import get_random_bytes flag = b"utflag{6cm_f0rb1dd3n_4774ck_777}" KEY = b'1234567812345678' NONCE = b'lmaolmaolmaolmao' admin = b'iamadministrator' def aes_gcm_encrypt_admin(plaintext): cipher = AES.new(KEY, AES.MODE_GCM, nonce=NONCE) ciphertext, tag = cipher.encrypt_and_digest(plaintext) return ciphertext.hex() def aes_gcm_encrypt(plaintext): cipher = AES.new(KEY, AES.MODE_GCM, nonce=NONCE) ciphertext, tag = cipher.encrypt_and_digest(plaintext) return ciphertext.hex(), tag.hex() def aes_gcm_decrypt(ciphertext, tag): cipher = AES.new(KEY, AES.MODE_GCM, nonce=NONCE) plaintext = cipher.decrypt_and_verify(ciphertext, tag) return plaintext if __name__ == '__main__': options = '''Welcome to the AES GCM encryption and decryption tool! 1. Encrypt message 2. Decrypt message 3. Quit ''' def encrypt_msg(): print("Input a plaintext (hex)") user_input = bytes.fromhex(input()) if b"admin" in user_input: print(aes_gcm_encrypt_admin(user_input)) else: output = aes_gcm_encrypt(user_input) print("Here is your encrypted string & tag, have a nice day :)") print(output) def decrypt_msg(): print("Input a hex string and its tag to decrypt:") user_input = bytearray.fromhex(input()) tag = bytearray.fromhex(input()) try: output = aes_gcm_decrypt(user_input, tag) if admin in output: print(flag) return except ValueError: print("Decryption failed :(") return print("Here is your decrypted string, have a nice day :)") print(output) def quit(): sys.exit() menu = { '1' : encrypt_msg, '2' : decrypt_msg, '3' : quit } i = 0 while i < 10: print(options) print('Select option: ') choice = input() if choice not in menu.keys(): print("Not a valid choice...") sys.exit() menu[choice]() i += 1 ``` Giờ ta sẽ được nhập 1 plaintext, và ta sẽ thu được tag tương ứng của nó. Giờ muốn có được flag thì phải có tag của admin. Giờ làm sao để có tag của admin đây nhỉiii. Ta tìm hiểu GMAC là như nào nha. ![image](https://hackmd.io/_uploads/S1Y7mh_t6.png) Chú thích: - Ek là hàm encryption AES 256 với key - PT là plaintext - AD là Additional Data, cái này không quan trọng lắm nhưng mà nó cũng sẽ ảnh hưởng tới tag. (Chall trên không dùng tới cái này). Ta có thuật toán gmac như sau: (hoặc là tham khảo ở [**ĐÂY**](https://viblo.asia/p/cryptopals-set-8-abstract-algebra-challenge-63-66-m68Z0Wb9KkG)) ```python def gmac(key, msg, aad, nonce): ''' Input: @key: key to be encrypted/GMAC @msg: message to be encrypted @aad: additional associated data @nonce: 96-bit of nonce to XOR at the end ''' authkey = AES_encrypt(key, b'\x00' * 16) authkey = GF2p128(int.from_bytes(authkey, 'big')) if msg is None: iv = encrypted = b'' else: iv = generate_key(8) encrypted = iv + AES_encrypt(key, msg, 'ctr', iv) content = aad + b'\x00' * (-len(aad) % 16) + \ encrypted + b'\x00' * (-len(encrypted) % 16) + \ pack('>2Q', len(aad), len(encrypted)) g = GF2p128(0) for i in range(0, len(content), 16): b = GF2p128(int.from_bytes(content[i : i + 16], 'big')) g += b g *= authkey s = AES_encrypt(key, nonce + b'\x00\x00\x00\x01') s = GF2p128(int.from_bytes(s, 'big')) g += s mac = int.to_bytes(g.val, 16, 'big') if msg is None: return mac else: return encrypted, mac ``` Dựa vào đường [**LINK**](https://toadstyle.org/cryptopals/63.txt) này, ta thấy được rằng cộng trừ trong trường hữu hạn GF(2^128) thì sẽ là phép toán xor thông thường, nhưng nhân và chia thì sẽ khó khăn hơn 1 chút: ``` function mul(a, b): p := 0 while a > 0: if a & 1: p := p ^ b a := a >> 1 b := b << 1 return p function divmod(a, b): q, r := 0, a while deg(r) >= deg(b): d := deg(r) - deg(b) q := q ^ (1 << d) r := r ^ (b << d) return q, r ``` Trước hết, ta có 1 giá trị ``h = E(k, b'\x00' * 16)``. Đệm các byte 0 cho AD và Ciphertext sao cho chia hết cho chiều dài khối thì ta sẽ thu được 1 đoạn như thế này ``AD0 || AD1 || C0 || C1 || C2``. Ngoài ra thì ta sẽ thêm phải có cả ``len(AD)||len(PT)`` nữa, thế nên tổng dữ liệu của ta sẽ như sau: ``AD0 || AD1 || C0 || C1 || C2 || len(AD)||len(PT)``. Giờ ta sẽ lấy từng block của data theo thuật toán sau đây ``` g := 0 for b in bs: g := g + b g := g * h ``` Cuối cùng, ta sẽ có 1 giá trị ``s := E(K, nonce || 1)`` và ta sẽ thu được TAG ``t = g + s``. Nhìn thì có vẻ khó hiểu, thế nhưng, ta có thể viết được thành như sau: $$\text{TAG} = (((((((((((h \cdot \text{AD0}) + \text{AD1}) \cdot h + \text{c0}) \cdot h + \text{c1}) \cdot h + \text{c2}) \cdot h + \text{len}) \cdot h + \text{s}$$ Hay còn được viết thành: $$\text{TAG} = \text{AD0} \cdot h^6 + \text{AD1} \cdot h^5 + \text{c0} \cdot h^4 + \text{c1} \cdot h^3 + \text{c2} \cdot h^2 + \text{len} \cdot h + \text{s}$$ Đó sẽ là công thức tính tag, thế giờ quay trở lại với bài, ta sẽ nhập plaintext 2 block và ta sẽ thu được như sau: $$\text{TAG} = ((((h \cdot \text{c0}) + \text{c1}) \cdot h + \text{len}) \cdot h + \text{s})$$ Ta chuyển thành được $$\text{TAG} = \text{c0} \cdot h^3 + \text{c1} \cdot h^2 + \text{len} \cdot h + \text{s}$$ Thế giờ ta gửi 2 ciphertext C_1 và C_2 thì sao nhỉiii $$\text{TAG1} = C_{10}h^3 + C_{11}h^2 + \text{len}h + s$$ $$\text{TAG2} = C_{20}h^3 + C_{21}h^2 + \text{len}h + s$$ $$\text{TAG1} + \text{TAG2} = (C_{10} + C_{20})h^3 + (C_{11} + C_{21})h^2$$ Giải phương trình bậc 3 này, ta sẽ thu được h. $$\text{Out} = ((((h \cdot C0) + C1) \cdot h) + \text{len}) \cdot h$$ Ta gửi admin vào, ta sẽ thu được ciphertext C tương ứng. Ta sẽ tính được $$\text{Out} = ((((h \cdot C0) + C1) \cdot h) + \text{len}) \cdot h$$ Ngoài ra, ta có $$\text{G1} = ((((h \cdot C0) + C1) \cdot h + \text{len}) \cdot h + \text{TAG1})$$ Qua đó, $\text{TAG3} = \text{Out} + \text{G1}$. **HÃY NHỚ `+` TRONG TRƯỜNG HỮU HẠN LÀ XOR** **HÃY NHỚ `+` TRONG TRƯỜNG HỮU HẠN LÀ XOR** **HÃY NHỚ `+` TRONG TRƯỜNG HỮU HẠN LÀ XOR** Quay trở lại challenge trên, ta sẽ gửi 1 block có độ dài 32 bit là ``b"iamadministratoriamadministrator"`` dạng hex (69616d61646d696e6973747261746f7269616d61646d696e6973747261746f72) để ta thu được ciphertext ``2dd7de7c5a763954b8b5d9c9027ae12b8ba405652f995ed357accbc32e8354ac``. ![image](https://hackmd.io/_uploads/HyW_cg5ta.png) Giờ ta sẽ gửi 2 plaintext có cùng độ dài để lấy ciphertext và tag. ![image](https://hackmd.io/_uploads/ryIWoe9t6.png) ``` ciphertext1 = "69617362646d696e6973747261746f72cf12a87b11820ee9866a66784d8ddaf5" tag1 = "f955c7263f01b6e54254cb2a4880a2b4" ciphertext2 = "2dd7c07f5a763954b8b5d9c9027aef3b8ba41b662f995ed357accbc32e835abc" tag2 = "97ae07bef5aee38c748c76d7dcff3a6e" ``` Giờ ta sẽ sử dụng cách tấn công như trên, ta có đoạn code như sau ```sage= from sage.all import * # import sage library from Crypto.Util.number import long_to_bytes as lb from Crypto.Util.number import bytes_to_long as bl from binascii import unhexlify, hexlify from sage.all import * import struct def bytes_to_polynomial(block, a): poly = 0 # pad to 128 bin_block = bin(bl(block))[2 :].zfill(128) # reverse it to count correctly, wrong don't reverse it lol # bin_block = bin_block[::-1] for i in range(len(bin_block)): poly += a^i * int(bin_block[i]) return poly def polynomial_to_bytes(poly): return lb(int(bin(poly.integer_representation())[2:].zfill(128)[::-1], 2)) def convert_to_blocks(ciphertext): return [ciphertext[i:i + 16] for i in range(0 , len(ciphertext), 16)] def xor(s1, s2): if(len(s1) == 1 and len(s1) == 1): return bytes([ord(s1) ^^ ord(s2)]) else: return bytes(x ^^ y for x, y in zip(s1, s2)) F, a = GF(2^128, name="a", modulus=x^128 + x^7 + x^2 + x + 1).objgen() R, x = PolynomialRing(F, name="x").objgen() # Set correct values C1 = convert_to_blocks(bytes.fromhex("69617362646d696e6973747261746f72cf12a87b11820ee9866a66784d8ddaf5")) T1 = bytes.fromhex("f955c7263f01b6e54254cb2a4880a2b4") C2 = convert_to_blocks(bytes.fromhex("2dd7c07f5a763954b8b5d9c9027aef3b8ba41b662f995ed357accbc32e835abc")) T2 = bytes.fromhex("97ae07bef5aee38c748c76d7dcff3a6e") C = convert_to_blocks(bytes.fromhex("2dd7de7c5a763954b8b5d9c9027ae12b8ba405652f995ed357accbc32e8354ac")) L = struct.pack(">QQ", 0 * 8, len(C1) * 8) C1_p = [bytes_to_polynomial(C1[i], a) for i in range(len(C1))] C2_p = [bytes_to_polynomial(C2[i], a) for i in range(len(C2))] C_p = [bytes_to_polynomial(C[i], a) for i in range(len(C))] T1_p = bytes_to_polynomial(T1, a) T2_p = bytes_to_polynomial(T2, a) L_p = bytes_to_polynomial(L, a) # Here G_1 is already modified to include the tag G_1 = (C1_p[0] * x^3) + (C1_p[1] * x^2) + (L_p * x) + T1_p G_2 = (C2_p[0] * x^3) + (C2_p[1] * x^2) + (L_p * x) + T2_p G_3 = (C_p[0] * x^3) + (C_p[1] * x^2) + (L_p * x) P = G_1 + G_2 auth_keys = [r for r, _ in P.roots()] for H, _ in P.roots(): EJ = G_1(H) T3 = G_3(H) + EJ print("H: " + str(H) + "\tT3: " + str(polynomial_to_bytes(T3).hex())) ``` Sau khi run thì mình thu được 1 tag mới ![image](https://hackmd.io/_uploads/Hk4Z6xcYT.png) Giờ mình chỉ cần nhập lại ciphertext của admin và tag vào là sẽ được flag thuiii ![image](https://hackmd.io/_uploads/Hy5GaeqYT.png) Hoặc bạn cũng có thể thu được flag khi nhập độ dài 16 bit, chỉ cần sửa lại đoạn tính nghiệm phương trình bậc 3 thành bậc 2 thôi $$G_1 = (C1_p[0] \cdot x^2) + (L_p \cdot x) + (T1\_p)$$ $$G_2 = (C2_p[0] \cdot x^2) + (L_p \cdot x) + (T2\_p)$$ $$G_3 = (C_p[0] \cdot x^2) + (L_p \cdot x)$$ ![image](https://hackmd.io/_uploads/HyGNCgqK6.png) ### FORBIDDEN FRUIT CRYPTOHACK ``` from Crypto.Cipher import AES import os IV = ? KEY = ? FLAG = ? @chal.route('/forbidden_fruit/decrypt/<nonce>/<ciphertext>/<tag>/<associated_data>/') def decrypt(nonce, ciphertext, tag, associated_data): ciphertext = bytes.fromhex(ciphertext) tag = bytes.fromhex(tag) header = bytes.fromhex(associated_data) nonce = bytes.fromhex(nonce) if header != b'CryptoHack': return {"error": "Don't understand this message type"} cipher = AES.new(KEY, AES.MODE_GCM, nonce=nonce) encrypted = cipher.update(header) try: decrypted = cipher.decrypt_and_verify(ciphertext, tag) except ValueError as e: return {"error": "Invalid authentication tag"} if b'give me the flag' in decrypted: return {"plaintext": FLAG.encode().hex()} return {"plaintext": decrypted.hex()} @chal.route('/forbidden_fruit/encrypt/<plaintext>/') def encrypt(plaintext): plaintext = bytes.fromhex(plaintext) header = b"CryptoHack" cipher = AES.new(KEY, AES.MODE_GCM, nonce=IV) encrypted = cipher.update(header) ciphertext, tag = cipher.encrypt_and_digest(plaintext) if b'flag' in plaintext: return { "error": "Invalid plaintext, not authenticating", "ciphertext": ciphertext.hex(), } return { "nonce": IV.hex(), "ciphertext": ciphertext.hex(), "tag": tag.hex(), "associated_data": header.hex(), } ``` Giờ thì dễ rồi, giống hệt bài trên kia, giờ chỉ cần code để kết nối với server thôi, không thì làm tay cũng được ahihihi :smiling_face_with_smiling_eyes_and_hand_covering_mouth: ```sage= from sage.all import * # import sage library from Crypto.Util.number import long_to_bytes as lb from Crypto.Util.number import bytes_to_long as bl from binascii import unhexlify, hexlify from sage.all import * import struct import requests import json def bytes_to_polynomial(block, a): poly = 0 # pad to 128 bin_block = bin(bl(block))[2 :].zfill(128) # reverse it to count correctly, wrong don't reverse it lol # bin_block = bin_block[::-1] for i in range(len(bin_block)): poly += a^i * int(bin_block[i]) return poly def polynomial_to_bytes(poly): return lb(int(bin(poly.integer_representation())[2:].zfill(128)[::-1], 2)) def convert_to_blocks(ciphertext): return [ciphertext[i:i + 16] for i in range(0 , len(ciphertext), 16)] def xor(s1, s2): if(len(s1) == 1 and len(s1) == 1): return bytes([ord(s1) ^^ ord(s2)]) else: return bytes(x ^^ y for x, y in zip(s1, s2)) F, a = GF(2^128, name="a", modulus=x^128 + x^7 + x^2 + x + 1).objgen() R, x = PolynomialRing(F, name="x").objgen() def encrypt(plaintext): plaintext = (plaintext).hex() r = requests.get('http://aes.cryptohack.org/forbidden_fruit/encrypt/'+plaintext) data = json.loads(r.text) return data["ciphertext"], (data["tag"]), data["nonce"] def encrypt_flag(plaintext): plaintext = (plaintext).hex() r = requests.get('http://aes.cryptohack.org/forbidden_fruit/encrypt/'+plaintext) data = json.loads(r.text) return data["ciphertext"] def get_flag(nonce, ciphertext, tag, asso_data): flag = requests.get('http://aes.cryptohack.org/forbidden_fruit/decrypt/' + nonce + '/' + ciphertext + '/' + tag + '/' + asso_data).json()['plaintext'] return bytes.fromhex(flag).decode() admin = b'give me the flag' plaintext1 = b'give me the gift' plaintext2 = b'give me the girl' ciphertext = (encrypt_flag(admin)) C = convert_to_blocks(bytes.fromhex(ciphertext)) C1, T1, nonce = encrypt(plaintext1) C1 = convert_to_blocks(bytes.fromhex(C1)) T1 = bytes.fromhex(T1) C2, T2, nonce = encrypt(plaintext2) C2 = convert_to_blocks(bytes.fromhex(C2)) T2 = bytes.fromhex(T2) L = struct.pack(">QQ", 0 * 8, len(C1) * 8) C1_p = [bytes_to_polynomial(C1[i], a) for i in range(len(C1))] C2_p = [bytes_to_polynomial(C2[i], a) for i in range(len(C2))] C_p = [bytes_to_polynomial(C[i], a) for i in range(len(C))] T1_p = bytes_to_polynomial(T1, a) T2_p = bytes_to_polynomial(T2, a) L_p = bytes_to_polynomial(L, a) # Here G_1 is already modified to include the tag # G_1 = (C1_p[0] * x^3) + (C1_p[1] * x^2) + (L_p * x) + T1_p # G_2 = (C2_p[0] * x^3) + (C2_p[1] * x^2) + (L_p * x) + T2_p # G_3 = (C_p[0] * x^3) + (C_p[1] * x^2) + (L_p * x) G_1 = (C1_p[0] * x^2) + (L_p * x) + T1_p G_2 = (C2_p[0] * x^2) + (L_p * x) + T2_p G_3 = (C_p[0] * x^2) + (L_p * x) P = G_1 + G_2 auth_keys = [r for r, _ in P.roots()] for H, _ in P.roots(): EJ = G_1(H) T3 = G_3(H) + EJ T3 = str(polynomial_to_bytes(T3).hex()) associated_data = "43727970746f4861636b" print(get_flag(nonce, ciphertext, T3, associated_data)) ``` > **Flag: crypto{https://github.com/attr-encrypted/encryptor/pull/22}** Tham khảo: > https://toadstyle.org/cryptopals/63.txt > > https://viblo.asia/p/cryptopals-set-8-abstract-algebra-challenge-63-66-m68Z0Wb9KkG > > https://math.stackexchange.com/questions/758985/multiplying-in-gf128#:~:text=You%20get%20the%20field%20GF%28128%29%20G%20F%20%28128%29,1%20when%20I%20need%20GF%28128%29%20G%20F%20%28128%29. > > https://meowmeowxw.gitlab.io/ctf/utctf-2020-crypto/