# Task 2: Symmetric Ciphers
## Lý thuyết
### 1.DES
#### KHÁI NIỆM
DES (Data Encryption Standard) là một thuật toán mã hóa đối xứng được phát triển vào những năm 1970 bởi Viện Tiêu chuẩn và Công nghệ Quốc gia Hoa Kỳ (NIST). DES đã được sử dụng rộng rãi trong nhiều năm và trở thành một trong những thuật toán mã hóa quan trọng nhất trong lịch sử.

* Đầu vào và đầu ra đều là 64 bit. Khóa mẹ có 56 bit. Thực chất khóa mẹ có 64 bit nhưng 8 bit ( là những bit ở những vị trí là bội của 8 ) đã được chọn để sử dụng để kiểm soát chẵn lẻ , đảm bảo rằng mỗi byte (8 bit) của khóa có số lượng bit chẵn. Nhưng trong quá trình mã hóa DES, những bit kiểm soát chẵn lẻ này không được sử dụng, do đó chỉ có 56 bit thực sự được sử dụng để tạo ra khóa.

#### Mô tả thuật toán
* DES là thuật toán mã hóa khối. Nên nó sẽ chia thông tin cần mã hóa thành các khối 64 bit.Nếu có khối nào chưa đủ 64 bit thì sẽ được padding cho đủ.Mỗi khối đó sẽ được mã hóa độc lập theo DES.


#### Các bước:
* Đây là hình ảnh mô tả DES:

* Mình nói phần hoán vị IP và hoán vị khóa PC1 trước
(còn cụ thể 16 vòng lặp như nào thì ở ngay dưới mình nói tiếp):
* Với Hoán vị IP:
* Đây là bảng IP:

* Cụ thể là với mỗi 8 bit tăng dần được xếp thành cột từ phải qua trái, mỗi cột sẽ là 4 bit chẵn tăng dần sau đó là 4 bit lẻ tăng dần.
* Input sẽ được thay thế qua bảng IP này. Rồi chia thành 2 phần $L_0$ là 32 bit đầu và $R_0$ là 32 bit cuối.
* Với Hoán vị PC1:
* Đây là bảng PC1:

* Như đã nói ở trên, khóa mẹ sẽ có 64 bit nhưng khi qua hoán vị PC1 sẽ loại bỏ các bit ở vị trí chia hết cho 8 : 8,16,24,32,40,48,56,64.
* Khóa 56 bit thu được thì chia ra thành $C_0$ là 28 bit đầu và $D_0$ là 28 bit cuối
* Đây là giai đoạn gọi là chuẩn bị các dữ liệu cần thiết ($L_0,R_0,C_0,D_0$) để bước tiếp theo là thực hiện 16 vòng lặp.
* Còn đây là mô tả về từng vòng lặp cụ thể:

* Ở đây mình nên giải thích phần tạo khóa $K_1$ trước.
* Với 2 giá trị $C_0,D_0$ thì mình giải thích ở trên rồi.
* Dịch vòng trái $C_0,D_0$.Ở mỗi vòng 1,2,9,16 thì dịch trái 1 bit, còn lại dịch 2 bit.

* Đem $C_0,D_0$ cho vào hoán vị PC2:

* PC2 hoạt động y như PC1 chỉ khác các giá trị đã được hoán vị, các bit 9,18,22,25,35,38,43,54 thì bị xóa đi.
* Vậy $K_1$ thu được thông qua hoán vị PC2 sẽ là khóa 48 bit.
* Giá trị $C_0,D_0$ vừa được dịch vòng trái sẽ được gán cho $C_1,D_1$ để thực hiện tiếp ở bước 2.
* Đó là thao tác tạo khóa $K_1$. Tiếp theo là vòng lặp mã hóa:
* 2 giá trị $L_0,R_0$ được khởi tạo như nào thì mình giải thích ở trên rồi.
* $R_0$ được đưa vào bảng mở rộng E:

* bảng E chỉ là lặp lại 2 bit cuối của hàng trước và tăng tiếp giá trị bit ở các vị trí tiếp theo để tạo hàng mới.
* Sau khi qua bảng mở rộng E thì $R_0$ từ 32 bit trở thành 48 bit. Mục đích là để $R_0$ được **xor** với $K_1$(48 bit).
* Giá trị sau **xor** đó được đi qua S_Box ( 8 box ) .Mục đích là từ 48 bit trở lại thành 32 bit.

* Sau đó tiếp tục qua hoán vị P:

* Gọi kết quả vừa tìm được là Res.
* Chuẩn bị cho vòng lặp tiếp theo thì $L_1$ = $R_0$, $R_1$ = $L_0 (XOR)$ Res
* Tương tự vậy khi đủ 16 vòng lặp thì ghép $L_{16},R_{16}$ lại rồi cho qua $IP^{-1}$:

là thu được output 64 bit.
* Mình giải thích nếu vẫn khó hiểu thì mọi người tham khảo ở
https://www.youtube.com/watch?v=YAITYYw_Cds&t=1s
và
https://www.youtube.com/watch?v=3mH6zaSjeGU&list=PL1MYBVskDVJXW6D9mwvaX37nNbeGVfAbP&index=5
### 2.TRIPLE DES
#### KHÁI NIỆM
* 3DES (Triple Data Encryption Standard) là một biến thể của DES được sử dụng để tăng cường độ bảo mật của mã hóa DES bằng cách áp dụng ba vòng lặp DES.
#### CÁC BƯỚC
* Thuật toán 3DES sử dụng một nhóm khóa bao gồm 03 khóa DES K1, K2 và K3, mỗi khóa có giá trị 56 bít.
* Ciphertext = EK3(DK2(EK1(Plaintext)))
Trong đó:
* * EK1 là quá trình mã hóa DES với khóa K1.
* DK2 là quá trình giải mã DES với khóa K2.
* EK3 là quá trình mã hóa DES cuối cùng với khóa K3.
Quá trình này bắt đầu bằng việc mã hóa plaintext với khóa K1, sau đó giải mã kết quả với khóa K2 và cuối cùng mã hóa lại kết quả giải mã với khóa K3. Kết quả của quá trình mã hóa cuối cùng là ciphertext (dữ liệu đã được mã hóa).
* Giải mã:Plaintext = DK1(EK2(DK3(Ciphertext))
Quá trình giải mã với việc giải mã với khóa K3, sau đó mã hóa với khóa K2, và cuối cùng giải mã với khóa K1…
### 3.AES
#### Khái niệm:
AES (Advanced Encryption Standard) là một thuật toán mã hóa đối xứng (symmetric encryption) hiện đại và được sử dụng phổ biến trong lĩnh vực bảo mật thông tin. Nó đã được chọn làm tiêu chuẩn mã hóa bởi Viện Tiêu chuẩn và Công nghệ Quốc gia Hoa Kỳ (NIST) vào năm 2001 để thay thế DES.
#### Các bước quá trình mã hóa
* AES sử dụng khóa có độ dài 128-bit, 192-bit hoặc 256-bit lần lượt tương ứng với AES-128,AES-192,AES-256.
* Tùy thuộc vào độ dài của khóa khi sử dụng 128 bit, 192 bit hay 256 bit mà thuật toán được thực hiện với số lần lặp khác nhau.

* Sơ đồ:

* Tổng quan:
* Vòng lặp chính (từ 1 -> N-1) của AES thực hiện các hàm sau :
* SubBytes – thay thế các byte dữ liệu của state.
* ShiftRows – dịch vòng dữ liệu của state.
* MixColumns – trộn cột dữ liệu của state.
* AddRoundKey – chèn khoá vòng.
* Riêng vòng cuối (N) không thực hiện MixColums (trộn dữ liệu).

* Mình nói về những hàm chính:
* * **AddRoundKey**:

* 128 bit của state được XOR tương ứng với khóa 128 bit ( Key Expansion ). Còn cách tạo khóa con để thực hiện XOR mình xin phép trình bày ở dưới.

* * **SubBytes**:

* Thay thế từng bytes của state thành bytes khác theo quy định của AES. Bảng thay thế đó gọi là S_box:

* Ví dụ: Với giá trị **08** thì chiếu vô S_box và thay thế nó bằng giá trị ở hàng 0, cột 8.

* * **ShiftRows**:

* Hàng 1 giữ nguyên, hàng 2 dịch 1 byte, hàng 3 dịch 2 byte,hàng 4 dịch 3 byte. (Túm lại là hàng i dịch i-1 bit)

* * **MixColumns**:

* Làm việc trên các cột của state.

* Đây là ma trận dùng để MixColumns.

* Sau mỗi vòng lặp thứ i như vậy thì state đầu ra tạm thời của mỗi vòng lặp sẽ được XOR với $K_i$.
* Quá trình sinh khóa con cho vòng lặp(KeyExpansion):

* Làm việc với từng cột của ma trận khóa ở vòng lặp trước (Gọi là word).
* Giải thích hàm G:
* w đi qua G đầu tiên sẽ dịch vòng trái 1 bytes (Rot Word) , sau đó SubBytes bằng S_Box,cuối cùng là XOR với Rcon(i).

* Quy tắc tính các word:
* Nếu i là word vị trí bội của 4 (ví dụ: i = 4, 8, 12):
* SubBytes: Thay thế mỗi byte của word thứ i bằng giá trị tương ứng trong S-box.
* RotaWord: Dịch vòng trái 1 byte (Rot Word).
* XOR với Rcon(i)
* Nếu i là word ở các vị trí còn lại:
* SubBytes: Thay thế mỗi byte của word thứ i bằng giá trị tương ứng trong S-box.
* XOR với word thứ i - 4.
* Ví dụ:
* Ở vòng lặp thứ 8 mình có khóa vòng là 
* Tương ứng là
$w_{i-4}$ = EA D2 73 21
$w_{i-3}$ = B5 8D BA D2
$w_{i-2}$ = 31 2B F5 60
$w_{i-1}$ = 7F 8D 29 2F
* $w_{i-1}$ sau khi đi qua hàm G:

* Lúc này $w_{i-1}$ = 46 A5 15 D2
* Tính khóa cho vòng lặp thứ 9: $w_i$ = $w_{i-1}$ XOR $w_{i-4}$
* Tức là với ma trận khóa của vòng trước, chỉ có cột cuối cùng vòng đó là đi qua hàm G. Sau đó để tạo các cột mới của ma trận sử dụng cho vòng tiếp theo thì cột đầu tiên ma trận mới sẽ bằng cột đầu tiên ma trận cũ XOR với cột cuối của ma trận cũ đã đi qua hàm G. Các cột tiếp theo thì cột thứ i của ma trận mới bằng cột thứ i của ma trận cũ XOR với cột mới thứ i-1 của ma trận mới.

* Đây là ví dụ cụ thể tổng quát quá trình mã hóa AES:


* Nếu giải thích như vậy vẫn chưa dễ hiểu thì mọi người có thể tham khảo ở https://www.youtube.com/watch?v=gP4PqVGudtg&t=109s
và
https://www.youtube.com/watch?v=o-Y3IKVv-sM&list=PL1MYBVskDVJXW6D9mwvaX37nNbeGVfAbP&index=6
#### Giải mã

* Làm ngược lại từng bước của quá trình mã hóa.Round key ban đầu được thêm vào cuối để tạo ra bản rõ ban đầu.
### 4.PKCS7 Padding
* PKCS7 Padding là một phương pháp đệm dữ liệu được sử dụng trong các thuật toán mã hóa khối như AES, DES, RSA và Blowfish để đảm bảo kích thước của dữ liệu đầu vào phù hợp với kích thước của khối mã hóa.
* PKCS7 Padding hoạt động bằng cách thêm vào các byte có giá trị bằng số lượng byte còn thiếu trong khối để đạt được kích thước mong muốn. Ví dụ, nếu kích thước khối mã hóa là 8 byte và dữ liệu cần mã hóa chỉ có 5 byte, thì 3 byte giá trị 03 sẽ được thêm vào cuối dữ liệu để đủ 8 byte. Trường hợp dữ liệu cần mã hóa đã có đúng kích thước của khối mã hóa, không có byte đệm nào được thêm vào.
* PKCS7 Padding được sử dụng rộng rãi trong các ứng dụng bảo mật, đặc biệt khi sử dụng các thuật toán mã hóa khối có kích thước khối như AES và DES. Nó đảm bảo rằng dữ liệu có thể được mã hóa và giải mã đúng và đồng thời giúp đảm bảo tính chính xác và thống nhất trong quá trình mã hóa và giải mã.
### 5.Block cipher modes of operation
* Block cipher modes of operation là các phương thức sử dụng để mở rộng khả năng của một thuật toán mã hóa khối, như AES hoặc DES, để có thể xử lý dữ liệu đầu vào có kích thước lớn hơn kích thước khối của thuật toán. Các phương thức này giúp chúng ta mã hóa và giải mã dữ liệu có kích thước khác nhau như dữ liệu văn bản, hình ảnh, video, v.v.
* Dưới đây là một số phương thức chính của block cipher modes of operation:
* * Electronic Codebook (ECB): ECB chia dữ liệu thành các khối cùng kích thước và mã hóa từng khối riêng lẻ với cùng một khóa.Tuy nhiên, ECB không bảo mật nếu có sự lặp lại trong dữ liệu, vì cùng một khối đầu vào sẽ tạo ra cùng một khối mã hóa.


* * Cipher Block Chaining (CBC):CBC sử dụng việc kết hợp giữa khối dữ liệu hiện tại và khối mã hóa trước đó để mã hóa khối dữ liệu hiện tại.
Trước khi mã hóa, mỗi khối dữ liệu được XOR với khối mã hóa trước đó để tạo ra đầu vào cho quá trình mã hóa.
CBC cung cấp bảo mật hơn ECB và không cho phép phân tích mật mã thông qua việc lặp lại các khối dữ liệu giống nhau.


* Propagating cipher block chaining (PCBC):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.


* Cipher Feedback (CFB):CFB chuyển đổi một thuật toán mã hóa khối thành một thuật toán mã hóa dòng.
CFB mã hóa dữ liệu theo từng byte hoặc bit và sử dụng kết quả mã hóa trước đó để tạo ra đầu vào cho quá trình mã hóa tiếp theo.
CFB thích hợp cho việc mã hóa dữ liệu liên tục và cho phép mã hóa theo cấp độ byte hoặc bit.


* Output Feedback (OFB):OFB hoạt động tương tự như CFB, nhưng nó sử dụng đầu ra của quá trình mã hóa trước đó để tạo ra một dãy khóa tiếp theo.
OFB chuyển đổi một thuật toán mã hóa khối thành một thuật toán mã hóa dòng, tạo ra các khóa để XOR với dữ liệu để tạo ra đầu ra mã hóa.
OFB cho phép mã hóa và giải mã các khối dữ liệu mà không cần phải chờ đến khi có đủ dữ liệu để tạo thành một khối.


* Counter (CTR):CTR chuyển đổi một thuật toán mã hóa khối thành một thuật toán mã hóa dòng, sử dụng một bộ đếm để tạo ra các khóa tiếp theo.
Mỗi khóa được tạo ra bằng cách tăng giá trị của bộ đếm và sử dụng khóa đó để mã hóa dữ liệu.
CTR cho phép mã hóa và giải mã các khối dữ liệu theo thứ tự bất kỳ, cung cấp hiệu suất cao và khả năng xử lý song song.


### 6.“Meet-in-the-middle" với 2DES
* “Meet-in-the-middle” là một kỹ thuật tấn công được sử dụng để giảm đáng kể số lượng khóa có thể phải thử khi tấn công một hệ mã hóa. Đây là một phương pháp tấn công hiệu quả đối với các hệ mã hóa sử dụng khối mã hóa với khóa ngắn.
* 2DES sử dụng hai lần quá trình mã hóa DES với hai khóa riêng biệt.
* * Ciphertext = EK2(DK1(Plaintext))
* Trong đó:
DK1 là quá trình giải mã DES với khóa K1.
EK2 là quá trình mã hóa DES với khóa K2.
* Giai đoạn tiền tính toán:
Kẻ tấn công tạo ra một bảng tra cứu (lookup table) chứa tất cả các khóa K1 có thể có và đầu ra của quá trình giải mã DES với khóa K1 (DK1).
Bằng cách mã hóa một số plaintext với tất cả các khóa K1 có thể có, kẻ tấn công tạo ra các cặp (K1, DK1) và lưu trữ chúng trong bảng tra cứu.
* Giai đoạn tấn công:
Kẻ tấn công mã hóa cùng một plaintext với tất cả các khóa K2 có thể có.
Kẻ tấn công so sánh ciphertext kết quả với các giá trị đã được lưu trữ trong bảng tra cứu.
Khi kết quả trùng khớp giữa ciphertext và giá trị trong bảng tra cứu, kẻ tấn công đã tìm thấy cặp khóa chính xác (K1, K2) tương ứng với plaintext và ciphertext.
Với phương pháp này, kẻ tấn công giảm không gian tìm kiếm của các khóa từ 2^112 xuống 2^57, làm tăng khả năng tấn công lên đáng kể so với việc thử tất cả các khóa có thể (brute force). Tuy nhiên, độ phức tạp của cuộc tấn công vẫn là lớn, đòi hỏi lượng tính toán và lưu trữ đáng kể để xây dựng bảng tra cứu và thực hiện giai đoạn tấn công.
Để ngăn chặn cuộc tấn công MITM trên 2DES, các biến thể mã hóa mạnh hơn như 3DES hoặc AES nên được sử dụng.

### 7. Padding oracle attack
* Padding Oracle là một lỗ hổng bảo mật trong các hệ thống mã hóa thông tin sử dụng chế độ hoạt động CBC (Cipher Block Chaining).
https://en.wikipedia.org/wiki/Padding_oracle_attack
* Công thức toán học để giải mã CBC là
* * $P_i = D_K(C_i)$ xor $C_{i-1}$
* * $C_0 = IV$
* Chọn hai khối ciphertext liên tiếp từ dữ liệu được mã hóa. Gọi chúng là $C_1$ và $C_2$ và muốn giải mã khối thứ hai để lấy bản rõ $P_2$
* Thay đổi byte cuối cùng của $C_1$ thành một giá trị tùy ý (ví dụ: $C_1'$) rồi gửi $IV,C_1',C_2$ tới máy chủ để giải mã.
* Máy chủ giải mã và kiểm tra phần đệm (padding) của $C_2$.Nếu padding hợp lệ, máy chủ sẽ trả về một thông báo đúng, ngược lại, nếu padding không hợp lệ, máy chủ sẽ trả về một thông báo lỗi.
* Nếu padding là hợp lệ, attacker có thể suy luận thông tin về giá trị của byte cuối cùng của $P_2$.Điều này xảy ra do quy tắc padding PKCS#7, trong đó giá trị của mỗi byte trong padding là giống nhau và bằng với số byte được thêm vào cuối.
* Sau khi tìm được byte cuối của $P_2$ **(brute_force nhiều nhất 256 byte)**, làm tương tự như vậy thì thu được các byte còn lại của $P_2$.
* Nếu một khối bao gồm 128 bit ( ví dụ AES ), tức là 16 byte, kẻ tấn công sẽ có được bản rõ $P_2$ không quá $256*16 = 4096$ lần thử. Điều này nhanh hơn đáng kể so với
$2^{128}$ lần để brute_force khóa 128 bit.
## How AES Works
### Challenge 1:Keyed Permutations
* 
* **Flag:crypto{bijection}**
### Challenge 2:Resisting Bruteforce
* 
* **Flag:crypto{biclique}**
### Challenge 3:Structure of AES
* 
* File **matrix.py**:
```py
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))
```
* Bài này họ đã hướng dẫn chúng ta tương đối là đầy đủ về cách mà AES hoạt động. Với source code họ đã cho thì chúng ta chỉ cần viết lại hàm **matrix2bytes** là xong :v:
```py
from Crypto.Util.number import *
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. """
flag = ""
for row in matrix:
for i in row:
flag += chr(i)
return flag
matrix = [
[99, 114, 121, 112],
[116, 111, 123, 105],
[110, 109, 97, 116],
[114, 105, 120, 125],
]
print(matrix2bytes(matrix))
from Crypto.Util.number import *
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. """
flag = ""
for row in matrix:
for i in row:
flag += chr(i)
return flag
matrix = [
[99, 114, 121, 112],
[116, 111, 123, 105],
[110, 109, 97, 116],
[114, 105, 120, 125],
]
print(matrix2bytes(matrix))
```
* **Flag:crypto{inmatrix}**
### Challenge 4: Round Keys
* 
* File **add_round_key.py**:
```py
state = [
[206, 243, 61, 34],
[171, 11, 93, 31],
[16, 200, 91, 108],
[150, 3, 194, 51],
]
round_key = [
[173, 129, 68, 82],
[223, 100, 38, 109],
[32, 189, 53, 8],
[253, 48, 187, 78],
]
def add_round_key(s, k):
???
print(add_round_key(state, round_key))
```
* 1 bước quan trọng của **AES** là **Round Keys**. Chúng ta chỉ cần xor 2 mảng state và key sau đó dùng lại hàm matrix2bytes của challenge trước là được :eagle:
```py
state = [
[206, 243, 61, 34],
[171, 11, 93, 31],
[16, 200, 91, 108],
[150, 3, 194, 51],
]
round_key = [
[173, 129, 68, 82],
[223, 100, 38, 109],
[32, 189, 53, 8],
[253, 48, 187, 78],
]
def add_round_key(s, k):
matrix = [[x ^ y for x, y in zip(row_1, row_2)] for row_1, row_2 in zip(s, k)]
return matrix
def matrix2bytes(matrix):
""" Converts a 4x4 matrix into a 16-byte array. """
flag = ""
for row in matrix:
for i in row:
flag += chr(i)
return flag
print(matrix2bytes(add_round_key(state, round_key)))
```
* **Flag:crypto{r0undk3y}**
### Challenge 5:Confusion through Substitution
* 
* File sbox.py:
```py
s_box = (
0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16,
)
inv_s_box = (
0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D,
)
state = [
[251, 64, 182, 81],
[146, 168, 33, 80],
[199, 159, 195, 24],
[64, 80, 182, 255],
]
def sub_bytes(s, sbox=s_box):
???
print(sub_bytes(state, sbox=inv_s_box))
```
* Tiếp theo là thao tác **Sub_bytes** của AES:
```py
s_box = (
0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16,
)
inv_s_box = (
0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D,
)
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 (0,4):
for j in range (0,4):
s[i][j]=sbox[s[i][j]]
return s
def matrix2bytes(matrix):
""" Converts a 4x4 matrix into a 16-byte array. """
flag = ""
for row in matrix:
for i in row:
flag += chr(i)
return flag
print(matrix2bytes(sub_bytes(state, sbox=inv_s_box)))
```
* **Flag:crypto{l1n34rly}**
### Challenge 6: Diffusion through Permutation
* 
* File diffusion.py:
```py
def shift_rows(s):
s[0][1], s[1][1], s[2][1], s[3][1] = s[1][1], s[2][1], s[3][1], s[0][1]
s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
s[0][3], s[1][3], s[2][3], s[3][3] = s[3][3], s[0][3], s[1][3], s[2][3]
def inv_shift_rows(s):
???
# 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],
]
```
* Còn lại là **Shift_Row** và **Mix_Column**:
```py
def shift_rows(s):
s[0][1], s[1][1], s[2][1], s[3][1] = s[1][1], s[2][1], s[3][1], s[0][1]
s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
s[0][3], s[1][3], s[2][3], s[3][3] = s[3][3], s[0][3], s[1][3], s[2][3]
def inv_shift_rows(s):
s[0][1], s[1][1], s[2][1], s[3][1] = s[3][1], s[0][1], s[1][1], s[2][1]
s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
s[0][3], s[1][3], s[2][3], s[3][3] = s[1][3], s[2][3], s[3][3], s[0][3]
# learned from http://cs.ucsb.edu/~koc/cs178/projects/JT/aes.c
xtime = lambda a: (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1)
def mix_single_column(a):
# see Sec 4.1.2 in The Design of Rijndael
t = a[0] ^ a[1] ^ a[2] ^ a[3]
u = a[0]
a[0] ^= t ^ xtime(a[0] ^ a[1])
a[1] ^= t ^ xtime(a[1] ^ a[2])
a[2] ^= t ^ xtime(a[2] ^ a[3])
a[3] ^= t ^ xtime(a[3] ^ u)
def mix_columns(s):
for i in range(4):
mix_single_column(s[i])
def inv_mix_columns(s):
# see Sec 4.1.3 in The Design of Rijndael
for i in range(4):
u = xtime(xtime(s[i][0] ^ s[i][2]))
v = xtime(xtime(s[i][1] ^ s[i][3]))
s[i][0] ^= u
s[i][1] ^= v
s[i][2] ^= u
s[i][3] ^= v
mix_columns(s)
state = [
[108, 106, 71, 86],
[96, 62, 38, 72],
[42, 184, 92, 209],
[94, 79, 8, 54],
]
def matrix2bytes(matrix):
""" Converts a 4x4 matrix into a 16-byte array. """
flag = ""
for row in matrix:
for i in row:
flag += chr(i)
return flag
inv_mix_columns(state)
inv_shift_rows(state)
flag = matrix2bytes(state)
print(flag)
```
* **Flag:crypto{d1ffUs3R}**
### Challenge 7: Bringing It All Together
* 
* File aes_decrypt.py:
```py
N_ROUNDS = 10
key = b'\xc3,\\\xa6\xb5\x80^\x0c\xdb\x8d\xa5z*\xb6\xfe\\'
ciphertext = b'\xd1O\x14j\xa4+O\xb6\xa1\xc4\x08B)\x8f\x12\xdd'
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
# Initial add round key step
for i in range(N_ROUNDS - 1, 0, -1):
pass # Do round
# Run final round (skips the InvMixColumns step)
# Convert state matrix to plaintext
return plaintext
# print(decrypt(key, ciphertext))
```
* Bài này sẽ tổng hợp tất cả các bước trước đó để tạo thành thuật toán AES hoàn chỉnh. Chúng ta chỉ cần thêm lại các hàm của các bài trước là ra:
```py
from Crypto.Util.number import *
from gmpy2 import iroot
N_ROUNDS = 10
key = b'\xc3,\\\xa6\xb5\x80^\x0c\xdb\x8d\xa5z*\xb6\xfe\\'
ciphertext = b'\xd1O\x14j\xa4+O\xb6\xa1\xc4\x08B)\x8f\x12\xdd'
s_box = (
0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16,
)
inv_s_box = (
0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D,
)
def add_round_key(s, k):
for i in range(4):
for j in range(4):
s[i][j]^=k[i][j]
def bytes2matrix(text):
""" Converts a 16-byte array into a 4x4 matrix. """
return [list(text[i:i+4]) for i in range(0, len(text), 4)]
def matrix2bytes(matrix):
""" Converts a 4x4 matrix into a 16-byte array. """
list = [i for row in matrix for i in row]
return bytes(list)
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 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 inv_sub_bytes(s):
for i in range(4):
for j in range(4):
s[i][j] = inv_s_box[s[i][j]]
def decrypt(key, ciphertext):
round_keys = expand_key(key) # Remember to start from the last round key and work backwards through them when decrypting
# Convert ciphertext to state matrix
state = bytes2matrix(ciphertext)
# Initial add round key step
add_round_key(state, round_keys[10])
for i in range(N_ROUNDS - 1, 0, -1):
inv_shift_rows(state)
inv_sub_bytes(state)
add_round_key(state, round_keys[i])
inv_mix_columns(state)
# Run final round (skips the InvMixColumns step)
inv_shift_rows(state)
inv_sub_bytes(state)
add_round_key(state, round_keys[0])
# Convert state matrix to plaintext
flag = matrix2bytes(state)
return flag
print(decrypt(key, ciphertext))
```
* **Flag:crypto{MYAES128}**
## Symmetric Starter
### Challenge 8:Modes of Operation Starter
* 
* **SOURCE**:
```py
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()}
```
* Nhìn **source** thì flag được encrypt ở : https://aes.cryptohack.org/block_cipher_starter/encrypt_flag/

* Gửi ciphertext vào mục decrypt:

* Lấy output đó chuyển sang ASCII là có flag:

* **Flag:crypto{bl0ck_c1ph3r5_4r3_f457_!}**
### Challenge 9:Passwords as Keys

* **SOURCE**:
```py
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()}
```
* Tương tự challenge trên thì vào https://aes.cryptohack.org/passwords_as_keys/encrypt_flag/ là có ciphertext:
* Để ý **source** thì **key** được giấu ở:https://gist.github.com/WChargin/8927565
* Vì **key** được random từ list_words đó nên phải brute_force key:
```py
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()
flag = decrypt("c92b7734070205bdf6c0087a751466ec13ae15e6f1bcdd3f3a535ec0f4bbae66", KEY)
if b'crypto' in flag:
print(flag)
```
* **Flag:crypto{k3y5__r__n07__p455w0rdz?}**
## BLOCK CIPHERS
### Challenge 10:ECB CBC WTF

* **SOURCE**:
```py
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}
```
* Nhìn source thì thấy challenge này đặc biệt ở chỗ mã hóa theo CBC nhưng giải mã lại theo ECB. :smile:
* Lấy ciphertext từ https://aes.cryptohack.org/ecbcbcwtf/encrypt_flag/

* Mình quan tâm 2 thứ trong **source**:

* Nôm na là $iv$ dài 16 bytes.

* Vậy từ $ciphertext$ thu được thì tìm được $iv$ và $encrypted$.
(Cụ thể là 16 bytes đầu của $ciphertext$ chính là $iv$)
* Do độ dài của $ciphertext = 48 bytes$ thì $encrypted = 48 - 16 = 32 bytes$. Theo đặc điểm mã hóa của CBC thì các plainted độ dài bytes bằng nhau và bằng iv. Vậy thì quá trình mã hóa sẽ chia thành 2 giai đoạn. Tạm gọi là $block1$và $block2$.
* Vậy túm lại thì cứ 16 bytes trong 48 bytes của ciphertext sẽ lần lượt là $iv,block1,block2$.

```py
import requests
from Crypto.Cipher import AES
from Crypto.Util.number import*
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 xor(a, b):
return long_to_bytes(bytes_to_long(a) ^ bytes_to_long(b))
cipher_text = "cdae9e688e5d0e8beab20304a11d89c19d9caa165560870519bb96fa6388cfac78d054b7a433f412c49d677b47660eca"
enc = bytes.fromhex(cipher_text)
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_!!!!!}**
### Challenge 11:ECB Oracle
* 
* **SOURCE**:
```py
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()}
```
* Điều mình quan tâm :
* $plaintext + FLAG.encode()$( Đặt là Block) sẽ được padding thêm 1 số bytes để cho $padded$ có độ dài bytes là bội của 16.
* Challenge này dùng **Padding oracle attack** miêu tả ở trên để brute_force từng kí tự của flag.
* Nôm na là đầu tiên ta thử với $Block = 31 bytes$ và $flag$ = b'crypto{'
* Khi đó $encrypted$ =
$Block + 1 bytes$ = b'$\x00$'*$(31-len(flag))$ + b'crypto{' + $1 bytes$
* Ta brute_force để tìm bytes cuối đó và lặp lại tới khi thu được flag hoàn chỉnh.
```py
import requests
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
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 res == res2:
flag += j.to_bytes(1, byteorder="big")
print(flag)
break
```
* **Flag: crypto{p3n6u1n5_h473_3cb}**
### Challenge 12:Flipping Cookie
* 
* **Source:**
```py
@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}
```

* Đầu tiên mình lấy cookie:
* Với cách giải thích như bài **ECB CBC WTF** thì ta có:
* Từ hàm giải mã thì chúng ta chỉ cần biến $admin=False;expiry=...$ thành $admin=True;...$ là có Flag.Ở đây mình sẽ tận dụng phép Xor để làm điều đó.
* block sẽ có dạng b’$admin=Flase...$’ ^ $iv$
send_iv sẽ có dạng: b’$admin=False…$’ ^ b’$admin=True…$’ ^ $iv$
Khi mình gửi vào hàm check_admin(cookie, iv) với format:
(cookie = block, iv = send_iv)
Thì 16 bytes đầu tiên của ciphertext sẽ có dạng:
(b’$admin=False…$’ ^ $iv$) ^ (b’$admin=False...$’ ^ b’$admin=True…$’ ^ $iv$)
== b’$admin=True…$’
```py
import requests
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Util.number import*
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()
return js
def xor(a, b):
return long_to_bytes(bytes_to_long(a) ^ bytes_to_long(b))
cookie = "89ba89fdb9700ad2262d95f02fa7b6f04abe309059d7bc073e89238646fe7a9c1bff9d6f95585155b36d9d706387a4af"
cookie = bytes.fromhex(cookie)
xor1 = b'admin=False;abcd'
xor2 = b'admin=True;abcde'
iv = cookie[:16]
block = cookie[16:]
send_iv = xor(xor(xor1, xor2), iv)
flag = response(block,send_iv)
print(flag)
```
* **Flag:crypto{4u7h3n71c4710n_15_3553n714l}**
### Challenge 13:Lazy CBC

* **Source**:
```py
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"}
```

* Ở đây thì $iv=KEY$. Để có flag thì mình chỉ cần tìm $KEY$ rồi gửi vô hàm **get_flag** là xong.
* Đầu vào chỉ chấp nhận chuỗi byte có độ dài là bội của 16.
Ta chọn 
Vì len = 32 thì ta chia thành 2 block để tận dụng được phép xor.

* phần block1 sẽ có dạng: $key$ ^ $plaintext$
phần block2 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$
```py
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
block = response(ciphertext)
block1 = block[:16]
block2 = block[16:]
key = xor(block1, block2)
print(get_flag(key))
```
* **Flag:crypto{50m3_p30pl3_d0n7_7h1nk_IV_15_1mp0r74n7_?}**
### Challenge 14:Triple DES

* **source**:
```py
from Crypto.Cipher import DES3
from Crypto.Util.Padding import pad
IV = os.urandom(8)
FLAG = ?
def xor(a, b):
# xor 2 bytestrings, repeating the 2nd one if necessary
return bytes(x ^ y for x,y in zip(a, b * (1 + len(a) // len(b))))
@chal.route('/triple_des/encrypt/<key>/<plaintext>/')
def encrypt(key, plaintext):
try:
key = bytes.fromhex(key)
plaintext = bytes.fromhex(plaintext)
plaintext = xor(plaintext, IV)
cipher = DES3.new(key, DES3.MODE_ECB)
ciphertext = cipher.encrypt(plaintext)
ciphertext = xor(ciphertext, IV)
return {"ciphertext": ciphertext.hex()}
except ValueError as e:
return {"error": str(e)}
@chal.route('/triple_des/encrypt_flag/<key>/')
def encrypt_flag(key):
return encrypt(key, pad(FLAG.encode(), 8).hex())
```

* Với 3DES thì có 1 khái niệm https://en.wikipedia.org/wiki/Weak_key. Tức là nếu đầu vào được mã hóa 2 lần cùng 1 khóa yếu thì sẽ trở về cũ.
```py
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())
flag = encrypt(key.hex(), flag)
print(flag)
```
* **Flag:crypto{n0t_4ll_k3ys_4r3_g00d_k3ys}**
## STREAM CIPHERS
### Challenge 15:Symmetry

* **Source**:
```py
from Crypto.Cipher import AES
KEY = ?
FLAG = ?
@chal.route('/symmetry/encrypt/<plaintext>/<iv>/')
def encrypt(plaintext, iv):
plaintext = bytes.fromhex(plaintext)
iv = bytes.fromhex(iv)
if len(iv) != 16:
return {"error": "IV length must be 16"}
cipher = AES.new(KEY, AES.MODE_OFB, iv)
encrypted = cipher.encrypt(plaintext)
ciphertext = encrypted.hex()
return {"ciphertext": ciphertext}
@chal.route('/symmetry/encrypt_flag/')
def encrypt_flag():
iv = os.urandom(16)
cipher = AES.new(KEY, AES.MODE_OFB, iv)
encrypted = cipher.encrypt(FLAG.encode())
ciphertext = iv.hex() + encrypted.hex()
return {"ciphertext": ciphertext}
```


* Mình thấy rằng quá trình mã hóa và giải mã của OFB là như nhau. :smiley:
* OFB là mật mã dòng , không phải mật mã khối nên không cần phải padding.
* Từ **source** thì ciphertext mình nhận được thì 16 bytes đầu là iv, còn lại là flag.
```py
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Hash import SHA256
from Crypto.Util.number import *
import requests
def response(plaintext,iv):
url = "https://aes.cryptohack.org/symmetry/encrypt/"
url += plaintext.hex()
url += '/'
url += iv.hex()
url += '/'
r = requests.get(url)
js = r.json()
return bytes.fromhex(js["ciphertext"])
def encrypt_flag():
url = "https://aes.cryptohack.org/symmetry/encrypt_flag/"
r = requests.get(url)
js = r.json()
return bytes.fromhex(js["ciphertext"])
encrypted = encrypt_flag()
iv = encrypted[:16]
flag_encrypted = encrypted[16:]
flag = response(flag_encrypted,iv)
print(flag)
```
* **Flag:crypto{0fb_15_5ymm37r1c4l_!!!11!}**
### Chalenge 16:Bean Counter

* **Source**:
```py
from Crypto.Cipher import AES
KEY = ?
class StepUpCounter(object):
def __init__(self, step_up=False):
self.value = os.urandom(16).hex()
self.step = 1
self.stup = step_up
def increment(self):
if self.stup:
self.newIV = hex(int(self.value, 16) + self.step)
else:
self.newIV = hex(int(self.value, 16) - self.stup)
self.value = self.newIV[2:len(self.newIV)]
return bytes.fromhex(self.value.zfill(32))
def __repr__(self):
self.increment()
return self.value
@chal.route('/bean_counter/encrypt/')
def encrypt():
cipher = AES.new(KEY, AES.MODE_ECB)
ctr = StepUpCounter()
out = []
with open("challenge_files/bean_flag.png", 'rb') as f:
block = f.read(16)
while block:
keystream = cipher.encrypt(ctr.increment())
xored = [a^b for a, b in zip(block, keystream)]
out.append(bytes(xored).hex())
block = f.read(16)
return {"encrypted": ''.join(out)}
```
* Bài này mode ECB và sử dụng 1 phương thức **StepUpCounter**
* Bài này mình chỉ hiểu nôm na đơn giản là:
* Flag là ở trong 1 tệp ảnh Bean_flag.png gì đó.
* Dữ liệu nhị phân của ảnh này là 1 chuỗi bytes đem đi mã hóa bằng AES_ECB và có thiết lập thêm bộ đếm **StepUpCounter**
* Từng 16 bytes của chuỗi bytes đó được XOR với 1 keystream được 1 chuỗi bytes mới mình gọi là encrypted.
* Sau khi đọc **source** thì mình nhận ra là keystream không thay đổi trong suốt quá trình mã hóa. Tức là từng 16 bytes được XOR với cùng 1 key (Ngon:smile: )
* Giải thích: Mình cũng chỉ sơ sơ đại khái chứ không cặn kẽ được phương thức **StepUpCounter** này. Nhưng mà do nó chỉ được gọi 1 lần và các hàm bên trong không có sự thay đổi giá trị trả về của hàm **increment** nên keystream là không đổi.
* Vậy thì ngay lập tức mình nghĩ tới việc tìm keystream.
* Vì flag XOR keystream = encrypted thì có keystream sẽ có flag. :))
* Nghe đơn giản nhưng mình chưa có hướng. Chả nhẽ bây giờ lại brute_force
```py
self.value = os.urandom(16).hex()
```
vì keystream tính theo value.
* Nhưng brute_force 1 chuỗi 16 bytes thì không khả quan rồi xD.
* Ý tưởng thì đơn giản như thế mà bế tắc. :satisfied: Nhưng lần trước mình nhớ anh Thắng có cho 1 bài cũng tương tự vậy. Cốt lõi nhất của bài đó và bài này là do file ảnh PNG thì luôn có 1 số bytes đầu cố định. (Thơm :satisfied: )
* Mình tìm thử **png header signature** thì 
* Ngon ở chỗ là có đủ 16 bytes luôn. Vậy mình lấy 16 bytes này đem XOR với 16 bytes đầu của encrypted là có được keystream thôi xD
* Có keystream rồi thì đương nhiên XOR lại với encrypted để lấy lại được dữ liệu ảnh đó. Sau đó mình có tạo ra 1 ảnh rỗng và ghi dữ liệu thu được vào là có flag.
```py
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Hash import SHA256
from Crypto.Util.number import *
import requests
import os
def encrypt():
url = "https://aes.cryptohack.org/bean_counter/encrypt/"
r = requests.get(url)
js = r.json()
return js["encrypted"]
encrypted = bytes.fromhex(encrypt())
png_header = bytes([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52])
def xor(byte1,byte2):
return bytes(a ^ b for a, b in zip(byte1, byte2))
block1 = encrypted[:16]
keystream = xor(block1,png_header)
png = []
for i in range(len(encrypted)):
png.append(encrypted[i] ^ keystream[i%len(keystream)])
with open('Bean_Counter.bmp', 'wb') as f:
f.write(bytes(png))
print(bytes(png))
```

* **Flag:crypto{hex_bytes_beans}**
### Challenge 17: CTRIME

* **Source**:
```py
from Crypto.Cipher import AES
from Crypto.Util import Counter
import zlib
KEY = ?
FLAG = ?
@chal.route('/ctrime/encrypt/<plaintext>/')
def encrypt(plaintext):
plaintext = bytes.fromhex(plaintext)
iv = int.from_bytes(os.urandom(16), 'big')
cipher = AES.new(KEY, AES.MODE_CTR, counter=Counter.new(128, initial_value=iv))
encrypted = cipher.encrypt(zlib.compress(plaintext + FLAG.encode()))
return {"ciphertext": encrypted.hex()}
```
* Bài này là mode CTR

* Nhìn **source** có vẻ ngắn ( Ngon :satisfied: )
* Mình có lẽ không cần phải giải thích gì nhiều ở mode CTR. Tuy nhiên mình quan tâm cái này:
```py
encrypted = cipher.encrypt(zlib.compress(plaintext + FLAG.encode()))
```
* Mình hiểu **source** như sau:
* hàm encrypt sẽ mã hóa plaintext.
* Đặc biệt ở chỗ là hàm đó trả về chuỗi mã hóa của $plaintext + Flag$
* Phương thức **zlib.compress()** hiểu là nó sẽ nén chuỗi đó lại bằng cách loại bỏ các **chuỗi con** giống nhau.
* Vậy ngay lập tức mình nghĩ rằng là gửi **plaintext = Flag** thì sẽ thấy được vài thứ hay ho. (Chắc vậy :satisfied: )
* Nhưng then chốt vấn đề không phải là quan tâm chuỗi bytes hàm encrypt trả về (mình gọi tạm là encrypted ) mà chỉ là cái độ dài bytes chuỗi đó thôi.
* Giải thích: Nếu mình gửi **plaintext = Flag** thì chắc chắn độ dài của encrypted trước và sau không thay đổi.Vì **zlib.compress()** xóa bỏ chuỗi con thì plaintext chả ảnh hưởng gì cả.
* Sơ sơ như vậy thì mình thử brute_force từng kí tự tiếp theo của **Flag=b'crypto{'**. Nếu kí tự đó không làm thay đổi len(encrypted) thì sẽ ghép vào flag rồi tìm tiếp.
```py
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Hash import SHA256
from Crypto.Util.number import *
import requests
import os
def response(plaintext):
url = "https://aes.cryptohack.org/ctrime/encrypt/"
url += plaintext.hex()
url += '/'
r = requests.get(url)
js = r.json()
return bytes.fromhex(js["ciphertext"])
alphabet = '0123456789' + 'abcdefghijklmnopqrstuvwxyz' + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + '!{}_?'
alphabet = alphabet.encode()
flag = b'crypto{'
len_flag_real = len(response(flag))
while True:
if flag[-1:] == b'}':
break
else:
for char in alphabet:
print(chr(char))
if len(response((flag.decode()+chr(char)).encode())) == len_flag_real:
flag = (flag.decode()+chr(char)).encode()
print(flag)
break
print(flag)
print("*")
```
* Mình code chay 1 đoạn như này rồi để đó làm trận game. Nhưng khi nó brute_force ra được **Flag=b'crypto{CRIM'** thì mình thấy nó không thêm được kí tự nào nữa. (Code chưa tối ưu chăng :satisfied: )
* Nhưng thôi mình mạnh dạn đoán luôn kí tự tiếp theo là E :smile:
* Vậy mình chỉ sửa lại mỗi **Flag=b'crypto{CRIME'** rồi chạy tiếp là có Flag.
```py
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Hash import SHA256
from Crypto.Util.number import *
import requests
import os
def response(plaintext):
url = "https://aes.cryptohack.org/ctrime/encrypt/"
url += plaintext.hex()
url += '/'
r = requests.get(url)
js = r.json()
return bytes.fromhex(js["ciphertext"])
alphabet = '0123456789' + 'abcdefghijklmnopqrstuvwxyz' + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + '!{}_?'
alphabet = alphabet.encode()
flag = b'crypto{CRIME'
len_flag_real = len(response(flag))
while True:
if flag[-1:] == b'}':
break
else:
for char in alphabet:
print(chr(char))
if len(response((flag.decode()+chr(char)).encode())) == len_flag_real:
flag = (flag.decode()+chr(char)).encode()
print(flag)
break
print(flag)
print("*")
```
* **Flag:crypto{CRIME_571ll_p4y5}**
### Challenge 18: Logon Zero

* 13399.py:
```py
#!/usr/bin/env python3
from Crypto.Cipher import AES
from Crypto.Util.number import bytes_to_long
from os import urandom
from utils import listener
FLAG = "crypto{???????????????????????????????}"
class CFB8:
def __init__(self, key):
self.key = key
def encrypt(self, plaintext):
IV = urandom(16)
cipher = AES.new(self.key, AES.MODE_ECB)
ct = b''
state = IV
for i in range(len(plaintext)):
b = cipher.encrypt(state)[0]
c = b ^ plaintext[i]
ct += bytes([c])
state = state[1:] + bytes([c])
return IV + ct
def decrypt(self, ciphertext):
IV = ciphertext[:16]
ct = ciphertext[16:]
cipher = AES.new(self.key, AES.MODE_ECB)
pt = b''
state = IV
for i in range(len(ct)):
b = cipher.encrypt(state)[0]
c = b ^ ct[i]
pt += bytes([c])
state = state[1:] + bytes([ct[i]])
return pt
class Challenge():
def __init__(self):
self.before_input = "Please authenticate to this Domain Controller to proceed\n"
self.password = urandom(20)
self.password_length = len(self.password)
self.cipher = CFB8(urandom(16))
def challenge(self, your_input):
if your_input['option'] == 'authenticate':
if 'password' not in your_input:
return {'msg': 'No password provided.'}
your_password = your_input['password']
if your_password.encode() == self.password:
self.exit = True
return {'msg': 'Welcome admin, flag: ' + FLAG}
else:
return {'msg': 'Wrong password.'}
if your_input['option'] == 'reset_connection':
self.cipher = CFB8(urandom(16))
return {'msg': 'Connection has been reset.'}
if your_input['option'] == 'reset_password':
if 'token' not in your_input:
return {'msg': 'No token provided.'}
token_ct = bytes.fromhex(your_input['token'])
if len(token_ct) < 28:
return {'msg': 'New password should be at least 8-characters long.'}
token = self.cipher.decrypt(token_ct)
new_password = token[:-4]
self.password_length = bytes_to_long(token[-4:])
self.password = new_password[:self.password_length]
return {'msg': 'Password has been correctly reset.'}
listener.start_server(port=13399)
```
* Lúc đầu nhìn source mình thấy dài nên hơi nản :))
* Mình hiểu và giải thích những phần chính để solve được chall này như sau:
* Nôm na bài này chúng ta cần nhập đúng mật khẩu để có flag:
* 
* Tuy nhiên password được khởi tạo như này thì brute_force hay thao tác gì khác là việc bất khả thi :
* 
* Mình phân tích tiếp xem.
* 
* Đây chỉ là hàm reset lại kết nối. Chưa có gì đáng nghi ngờ để khai thác.
* 
* Đọc đến đây thì mình thấy rằng nếu ta gọi tới reset_password thì server sẽ ghi đè password mới thay cho password cũ.Lúc này mình có nghĩ tới việc đổi pass theo ý mình sau đó tính toán pass mới thì là xong. Nhưng mọi chuyện không đơn giản như vậy xD.
* Mình chỉ biết là nên gửi 1 pass mới có độ dài là lớn hơn 28 thì pass mới được thay đổi thôi.
* Hơi bế tắc thì mình có để ý tới cái hint to tướng: ! 
* (Chính là tên của chall) :satisfied: - Đăng nhập bằng 0 :))
* Sau khi search thì mình tìm được bài viết này:https://viblo.asia/p/tim-hieu-ve-zerologon-WAyK8rk9lxX
* Phân tích thêm hàm decrypt thì thấy khá là lạ.Với pass chúng ta gửi vào thì 16 bytes đầu mặc định là IV, còn lại là ciphertext. 1 vòng lặp chứa len(ciphertext) lần lặp , giá trị trả về là 1 chuỗi gồm các bytes được tạo ra bằng cách xor giữa giá trị đầu tiên của state sau encrypt với giá trị tương ứng trong vòng lặp của ciphertext.
* Vậy thì nếu mình gửi pass mới = $b'\x00' * 28$ thì self.password_length = 0
(Do bytes_to_long(b'x\00'...) = 0).Thì pass mới sẽ là không có pass xD.
```py
self.password = new_password[:self.password_length]
```
* Sau cùng mình thử gửi ```password = ""``` liên tục. Nếu **Wrong_password** thì mình chỉ cần reset lại kết nối (mục đích là thay đổi key để phù hợp với đúng bộ IV và ciphertext toàn bytes $b'\x00'$, lặp lại tới khi nào được thì thôi.
* Vì theo bài viết mình cung cấp ở trên nói rằng:

```py
from Crypto.Util.number import *
from Crypto.Cipher import AES
from Crypto.Util import Counter
import random
import requests
from pwn import *
import json
HOST = "socket.cryptohack.org"
PORT = 13399
r = remote(HOST,PORT)
def json_recv():
line = r.readline()
return json.loads(line.decode())
def json_send(hsh):
request = json.dumps(hsh).encode()
r.sendline(request)
print(r.readline())
while True:
token = (b'\x00'*28).hex()
request = {"option":"reset_password","token":token}
json_send(request)
json_recv()
password = ""
request = {"option":"authenticate","password":password}
json_send(request)
flag = json_recv()
print(flag)
if "crypto" in flag:
break
request = {"option": "reset_connection"}
json_send(request)
json_recv()
```
* Script của mình chưa hẳn là tối ưu. Nhưng mình vẫn có được flag: **Flag:crypto{Zerologon_Windows_CVE-2020-1472}**
### Challenge 19: Stream of Consciousness

* **Source**:
```py
from Crypto.Cipher import AES
from Crypto.Util import Counter
import random
KEY = ?
TEXT = ['???', '???', ..., FLAG]
@chal.route('/stream_consciousness/encrypt/')
def encrypt():
random_line = random.choice(TEXT)
cipher = AES.new(KEY, AES.MODE_CTR, counter=Counter.new(128))
encrypted = cipher.encrypt(random_line.encode())
return {"ciphertext": encrypted.hex()}
```

* Nhìn source ngắn (thơm :satisfied: )
* Mình hiểu source như sau:
* Mã hóa theo mode CTR
* Flag thuộc trong 1 TEXT trong đó còn có các chuỗi khác.
* Mỗi lần kết nối tới https://aes.cryptohack.org/stream_consciousness/encrypt/
thì hàm encrypt sẽ chọn ngẫu nhiên 1 chuỗi trong TEXT để đem đi mã hóa và trả về dạng hex của ciphertext.
```py
def response():
url = "https://aes.cryptohack.org/stream_consciousness/encrypt/"
r = requests.get(url)
js = r.json()
return bytes.fromhex(js["ciphertext"])
```
* Đương nhiên là hên xui cao mới trả về được chuỗi flag được mã hóa nên mình nghĩ phải tìm cách nào để biết được đại khái số lượng chuỗi của TEXT.
* Sau khi mình thử gọi 100 lần hàm **response()** ở trên :
```py
from Crypto.Util.number import *
from Crypto.Cipher import AES
from Crypto.Util import Counter
import random
import requests
from pwn import xor
def response():
url = "https://aes.cryptohack.org/stream_consciousness/encrypt/"
r = requests.get(url)
js = r.json()
return bytes.fromhex(js["ciphertext"])
TEXT_encrypted = []
TEXT_decrypted = []
for i in range(100):
ciphertext = response()
if ciphertext not in TEXT_encrypted:
TEXT_encrypted.append(ciphertext)
print("Số lượng ciphertext : ",len(TEXT_encrypted))
```
* Thì mình nhận được TEXT có khoảng 22 chuỗi.
* Tuy nhiên do response() 100 lần hơi lâu nên mình giảm xuống còn 20 lần.
* Tuy là mode CTR nhưng mỗi lần lấy ciphertext là lại gọi hàm response() nên key sẽ không bị thay đổi. (Ngon :satisfied: )
* Do key không đổi nên chuỗi đem xor với từng plaintext trong TEXT đương nhiên là như nhau. Mình cứ gọi nó là key cho tiện tính toán.
* Mình lợi dụng việc flag = b'crypto{' nên sẽ tìm được khoảng 7 bytes đầu của key bằng brute_force.
```py
TEXT_test = []
for text in TEXT_encrypted :
TEXT_test.append(text[:7])
flag = b'crypto{'
for text in TEXT_test:
key_test = xor(flag,text)
print("#")
print("7 bytes đầu của key : ",key_test)
count = 1
for cipher in TEXT_test:
plain = xor(key_test,cipher)
print("Plaintext thứ ",count," : ",plain)
count+=1
```
* Ý tưởng của hàm trên là mình sẽ lấy flag đi xor với từng 7 bytes đầu của ciphertext, Vì nếu có 1 ciphertext nào đó là mã hóa của b'crypto{' thì kết quả sau xor chính là key . :exploding_head:
* Tuy nhiên thì hên xui mới có được chuỗi ciphertext là flag nên không phải cứ thử 1 lần là ra đâu . Phải đẹp trai như mình cơ. :smile:
* Sau khi kiểm tra các plaintext .(Chính xác là 7 bytes đầu khôi phục lại được của các plaintext trong TEXT) thì mình thấy có 1 key đặc biệt:

* Đấy đã bảo phải đẹp trai như mình mới 1 phát ăn ngay xD
* Vậy là khôi phục được 7 bytes đầu của key rồi.
* Còn lại 16-7=9 bytes còn lại thì tìm tiếp kiểu gì đây. :question:
* Brute_force key thì không khả quan rồi nên mình hướng tới brute_force flag.
* hông thường flag chỉ toàn các kí tự chữ hoa,chữ thường, số và 1 vài kí tự đặc biết nữa nên mình tạo ra :
```py
alphabet1 = '0123456789'
alphabet2 = 'abcdefghijklmnopqrstuvwxyz'
alphabet3 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
alphabet4 = '~`!@#$%^&*()_-+=[]{}|\:;<,.>?/'
```
* Ý tưởng của mình như sau:
* Mình sẽ thử thêm từng kí tự của alphabet vào flag, sau đó đem xor với 8 bytes đầu của ciphertext chứa flag:
```py
cipher_flag = b'\x92\x97n\x96\xda\xfb\x1a\x93\xab\xafK\xbe\x0c\xff\x13\x1b> *X\xc9\xbb\xe6L\x01\xc5\xfb`a\xd5\x1c\x9d'
```
để tìm ra 8 bytes đầu của key. Sau đó xem xor lại với từng 8 bytes đầu của plaintext. Do các plaintext đều là các từ có nghĩa nên mình nghĩ việc dò tay thủ công này cũng không phải là nohope.
```py
cipher_flag = b'\x92\x97n\x96\xda\xfb\x1a\x93\xab\xafK\xbe\x0c\xff\x13\x1b> *X\xc9\xbb\xe6L\x01\xc5\xfb`a\xd5\x1c\x9d'
alphabet1 = '0123456789'
alphabet2 = 'abcdefghijklmnopqrstuvwxyz'
alphabet3 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
alphabet4 = '~`!@#$%^&*()_-+=[]{}|\:;<,.>?/'
def find_key():
for char in alphabet2.encode():
flag = b'crypto{'
print("# : char = ",chr(char))
flag += chr(char).encode()
key = xor(flag,cipher_flag[:8])
i = 1
for text in TEXT_encrypted:
print("Plaintext thứ ",i," : ",xor(text[:8],key))
i+=1
find_key()
```
* Sau khi mình brute_force đủ 4 alphabet thì mình thấy bytes tiếp theo của
flag = b'crypto{' là k:

* Mình chỉ nghĩ được cách thủ công này nên mình sẽ dò tay tiếp cho tới khi thu được flag.Chính vì thế nên mình không để ở dưới full script được mà chỉ có thể nói ý tưởng và từng giai đoạn giải challenge của mình ở trên.
* Sau 1 hồi vật vã tìm flag bằng cách thủ công này thì:
* **Flag: crypto{k3y57r34m_r3u53_15_f474l}**
### Challenge 20: Dancing Queen

* chacha20.py:
```py
#!/usr/bin/env python3
from os import urandom
FLAG = b'crypto{?????????????????????????????}'
def bytes_to_words(b):
return [int.from_bytes(b[i:i+4], 'little') for i in range(0, len(b), 4)]
def rotate(x, n):
return ((x << n) & 0xffffffff) | ((x >> (32 - n)) & 0xffffffff)
def word(x):
return x % (2 ** 32)
def words_to_bytes(w):
return b''.join([i.to_bytes(4, 'little') for i in w])
def xor(a, b):
return b''.join([bytes([x ^ y]) for x, y in zip(a, b)])
class ChaCha20:
def __init__(self):
self._state = []
def _inner_block(self, state):
self._quarter_round(state, 0, 4, 8, 12)
self._quarter_round(state, 1, 5, 9, 13)
self._quarter_round(state, 2, 6, 10, 14)
self._quarter_round(state, 3, 7, 11, 15)
self._quarter_round(state, 0, 5, 10, 15)
self._quarter_round(state, 1, 6, 11, 12)
self._quarter_round(state, 2, 7, 8, 13)
self._quarter_round(state, 3, 4, 9, 14)
def _quarter_round(self, x, a, b, c, d):
x[a] = word(x[a] + x[b]); x[d] ^= x[a]; x[d] = rotate(x[d], 16)
x[c] = word(x[c] + x[d]); x[b] ^= x[c]; x[b] = rotate(x[b], 12)
x[a] = word(x[a] + x[b]); x[d] ^= x[a]; x[d] = rotate(x[d], 8)
x[c] = word(x[c] + x[d]); x[b] ^= x[c]; x[b] = rotate(x[b], 7)
def _setup_state(self, key, iv):
self._state = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]
self._state.extend(bytes_to_words(key))
self._state.append(self._counter)
self._state.extend(bytes_to_words(iv))
def decrypt(self, c, key, iv):
return self.encrypt(c, key, iv)
def encrypt(self, m, key, iv):
c = b''
self._counter = 1
for i in range(0, len(m), 64):
self._setup_state(key, iv)
for j in range(10):
self._inner_block(self._state)
c += xor(m[i:i+64], words_to_bytes(self._state))
self._counter += 1
return c
if __name__ == '__main__':
msg = b'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula.'
key = urandom(32)
iv1 = urandom(12)
iv2 = urandom(12)
c = ChaCha20()
msg_enc = c.encrypt(msg, key, iv1)
flag_enc = c.encrypt(FLAG, key, iv2)
print(f"iv1 = '{iv1.hex()}'")
print(f"iv2 = '{iv2.hex()}'")
print(f"msg_enc = '{msg_enc.hex()}'")
print(f"flag_enc = '{flag_enc.hex()}'")
```
* output.txt:
```py
iv1 = 'e42758d6d218013ea63e3c49'
iv2 = 'a99f9a7d097daabd2aa2a235'
msg_enc = 'f3afbada8237af6e94c7d2065ee0e221a1748b8c7b11105a8cc8a1c74253611c94fe7ea6fa8a9133505772ef619f04b05d2e2b0732cc483df72ccebb09a92c211ef5a52628094f09a30fc692cb25647f'
flag_enc = 'b6327e9a2253034096344ad5694a2040b114753e24ea9c1af17c10263281fb0fe622b32732'
```
* Source dài quá :satisfied:
* Lúc đầu mình nhìn output mà source còn nhả thêm cả msg thì mình nghĩ ngay tới việc phải tìm key.
* Mình có search được 1 vài blog:
https://antoanthongtin.vn/mat-ma-dan-su/he-ma-dong-co-xac-thuc-chacha20---poly1305-107783
https://tailieu.antoanthongtin.gov.vn/Files/files/site-2/files/Hemadongcoxacthuc.pdf
* Phân tích qua qua thì mình thấy rằng có vẻ key và iv được dùng để tạo state ( có kết hợp thêm cả giá trị counter). Tuy nhiên do mình thấy là các hàm public và mọi thứ đều thao tác 1 chiều nên theo mình là chỉ cần **rì vợt** :satisfied: các hàm lại. XD
* Mình thấy mấy bài code dài dài thì cách làm lại khá dễ. Còn mấy bài code ngắn thì khoai lắm. :satisfied:
* Thay vì ngồi giải thích từng hàm thì mình chỉ nói tóm lược lại quá trình solve của mình.Vì thói quen của mình là đọc code từ main ra các hàm chứ không đọc từ đầu tới cuối.XD
* Như đã nói ở trên hướng đi của mình ban đầu là sẽ tìm key bằng cách reverse các hàm lại.


* 2 hàm đó chỉ là những thao tác cơ bản của state . Hoàn toàn làm ngược lại thì có thể tìm được state ban đấu.
* Tiếp theo:

* Giải thích qua 1 chút thì state là 1 chuỗi biểu diễn đầu tiên là 4 giá trị cố định, tiếp theo là 32 bytes của key,giá trị counter, và còn lại là 12 bytes của iv.
* Vậy tức là nếu tìm được state ban đầu thì chắc chắn tìm lại được key.

* Hàm decrypt thì không có gì phải quan tâm.Còn hàm encrypt thì cực kì quan trọng:
* Nôm na là có **i** lần lặp, mỗi lần sẽ **setup_state**,sau đó 10 lần inner_block, kết quả sau đó sẽ word_to_bytes state và xor với 64 bytes của msg.Cuối cùng là counter tăng lên 1 đơn vị.
* Vậy mình sẽ find key bằng cách reverse lại hàm encrypt.
* Mình nhận thấy là mỗi lần setup_state thì state chỉ thay đổi mỗi giá trị counter.Tức là key và iv không có sự thay đổi nào hết.
* Vậy mình không cần reverse lại i lần(mỗi lần 64 bytes) cho đủ msg_enc mà chỉ cần thao tác với 64 bytes đầu tiên thôi.

* Full script:
```py
from Crypto.Util.number import *
from Crypto.Cipher import AES
from Crypto.Util import Counter
import random
import requests
from pwn import *
import json
def bytes_to_words(b):
return [int.from_bytes(b[i:i+4], 'little') for i in range(0, len(b), 4)]
def rotate(x, n):
return ((x << n) & 0xffffffff) | ((x >> (32 - n)) & 0xffffffff)
def word(x):
return x % (2 ** 32)
def words_to_bytes(w):
return b''.join([i.to_bytes(4, 'little') for i in w])
def xor(a, b):
return b''.join([bytes([x ^ y]) for x, y in zip(a, b)])
class ChaCha20:
def __init__(self):
self._state = []
def _inner_block(self, state):
self._quarter_round(state, 0, 4, 8, 12)
self._quarter_round(state, 1, 5, 9, 13)
self._quarter_round(state, 2, 6, 10, 14)
self._quarter_round(state, 3, 7, 11, 15)
self._quarter_round(state, 0, 5, 10, 15)
self._quarter_round(state, 1, 6, 11, 12)
self._quarter_round(state, 2, 7, 8, 13)
self._quarter_round(state, 3, 4, 9, 14)
def reverse_inner_block(self, state):
self.reverse_quarter_round(state, 3, 4, 9, 14)
self.reverse_quarter_round(state, 2, 7, 8, 13)
self.reverse_quarter_round(state, 1, 6, 11, 12)
self.reverse_quarter_round(state, 0, 5, 10, 15)
self.reverse_quarter_round(state, 3, 7, 11, 15)
self.reverse_quarter_round(state, 2, 6, 10, 14)
self.reverse_quarter_round(state, 1, 5, 9, 13)
self.reverse_quarter_round(state, 0, 4, 8, 12)
def _quarter_round(self, x, a, b, c, d):
x[a] = word(x[a] + x[b]); x[d] ^= x[a]; x[d] = rotate(x[d], 16)
x[c] = word(x[c] + x[d]); x[b] ^= x[c]; x[b] = rotate(x[b], 12)
x[a] = word(x[a] + x[b]); x[d] ^= x[a]; x[d] = rotate(x[d], 8)
x[c] = word(x[c] + x[d]); x[b] ^= x[c]; x[b] = rotate(x[b], 7)
def reverse_quarter_round(self,x, a, b, c, d):
x[b] = rotate(x[b], 32-7)
x[b] ^= x[c]
x[c] = word(x[c] - x[d])
x[d] = rotate(x[d], 32-8)
x[d] ^= x[a]
x[a] = word(x[a] - x[b])
x[b] = rotate(x[b], 32-12)
x[b] ^= x[c]
x[c] = word(x[c] - x[d])
x[d] = rotate(x[d], 32-16)
x[d] ^= x[a]
x[a] = word(x[a] - x[b])
def _setup_state(self, key, iv):
self._state = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]
self._state.extend(bytes_to_words(key))
self._state.append(self._counter)
self._state.extend(bytes_to_words(iv))
def decrypt(self, c, key, iv):
return self.encrypt(c, key, iv)
def encrypt(self, m, key, iv):
c = b''
self._counter = 1
for i in range(0, len(m), 64):
self._setup_state(key, iv)
for j in range(10):
self._inner_block(self._state)
c += xor(m[i:i+64], words_to_bytes(self._state))
self._counter += 1
return c
def find_key(self,msg,msg_enc):
self._state = bytes_to_words(xor(msg,msg_enc)[:64])
for j in range(10):
self.reverse_inner_block(self._state)
key = words_to_bytes(self._state[4:])[:32]
return key
c = ChaCha20()
msg = b'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula.'
iv1 = 'e42758d6d218013ea63e3c49'
iv2 = 'a99f9a7d097daabd2aa2a235'
msg_enc = 'f3afbada8237af6e94c7d2065ee0e221a1748b8c7b11105a8cc8a1c74253611c94fe7ea6fa8a9133505772ef619f04b05d2e2b0732cc483df72ccebb09a92c211ef5a52628094f09a30fc692cb25647f'
flag_enc = 'b6327e9a2253034096344ad5694a2040b114753e24ea9c1af17c10263281fb0fe622b32732'
iv1 = bytes.fromhex(iv1)
iv2 = bytes.fromhex(iv2)
msg_enc = bytes.fromhex(msg_enc)
flag_enc = bytes.fromhex(flag_enc)
key = c.find_key(msg,msg_enc)
flag = c.decrypt(flag_enc,key,iv2)
print(flag)
```
* **Flag:crypto{M1x1n6_r0und5_4r3_1nv3r71bl3!}**
### Challenge 21:Oh SNAP

* **SOURCE**:
```py
from Crypto.Cipher import ARC4
FLAG = ?
@chal.route('/oh_snap/send_cmd/<ciphertext>/<nonce>/')
def send_cmd(ciphertext, nonce):
if not ciphertext:
return {"error": "You must specify a ciphertext"}
if not nonce:
return {"error": "You must specify a nonce"}
ciphertext = bytes.fromhex(ciphertext)
nonce = bytes.fromhex(nonce)
cipher = ARC4.new(nonce + FLAG.encode())
cmd = cipher.decrypt(ciphertext)
if cmd == b"ping":
return {"msg": "Pong!"}
else:
return {"error": f"Unknown command: {cmd.hex()}"}
```
## Padding Attacks
* Đây có lẽ là tag liên quan tới kĩ thuật [Padding oracle attack](https://en.wikipedia.org/wiki/Padding_oracle_attack)
* Đối với những dạng bài AES-CBC, đặc biệt khi server có chức năng check padding thì kĩ thuật này sẽ là giải pháp hữu hiệu để giải quyết.
* Mình đã tham khảo từ những nguồn này: [blog](https://research.nccgroup.com/2021/02/17/cryptopals-exploiting-cbc-padding-oracles/), [video](https://www.youtube.com/watch?v=uDHo-UAM6_4).
* Trước khi tìm hiểu thì còn 1 khái niệm nữa là [PKCS7 Padding](https://www.youtube.com/watch?v=3OTMLUEPZUc).
* Hãy nhìn vào cách block đầu tiên được giải mã:

* **Padding oracle attack** ở đây là chúng ta sẽ brute-force để thay đổi giá trị bytes iv cuối cùng sao cho khi gửi lên server được trả về padding đúng. Tức là với 1 bytes cuối của iv thì mình phải brute sao cho plaintext decrypt được có bytes cuối là b'\x01',với 2 bytes cuối của iv thì mình phải brute sao cho plaintext decrypt được có 2bytes cuối là b'\x02\x02'. Tương tự....
* Cứ mỗi lần brute bytes iv như thế, mình sẽ tìm ra được bytes của **decrypt(ct)** tương ứng tại vị trí đó. Đến khi thu được **decrypt(ct)** hoàn chỉnh thì **xor(iv_gốc,decrypt(ct))** chính là plaintext cần tìm.
* Nếu mình giải thích hơi khó hiểu thì các bạn nên xem [video](https://www.youtube.com/watch?v=uDHo-UAM6_4) để tiếp cận dễ dàng hơn.
### Challenge 22:Pad Thai

* Source:
```py
#!/usr/bin/env python3
from Crypto.Util.Padding import unpad
from Crypto.Cipher import AES
from os import urandom
from utils import listener
FLAG = 'crypto{?????????????????????????????????????????????????????}'
class Challenge:
def __init__(self):
self.before_input = "Let's practice padding oracle attacks! Recover my message and I'll send you a flag.\n"
self.message = urandom(16).hex()
self.key = urandom(16)
def get_ct(self):
iv = urandom(16)
cipher = AES.new(self.key, AES.MODE_CBC, iv=iv)
ct = cipher.encrypt(self.message.encode("ascii"))
return {"ct": (iv+ct).hex()}
def check_padding(self, ct):
ct = bytes.fromhex(ct)
iv, ct = ct[:16], ct[16:]
cipher = AES.new(self.key, AES.MODE_CBC, iv=iv)
pt = cipher.decrypt(ct) # does not remove padding
try:
unpad(pt, 16)
except ValueError:
good = False
else:
good = True
return {"result": good}
def check_message(self, message):
if message != self.message:
self.exit = True
return {"error": "incorrect message"}
return {"flag": FLAG}
#
# This challenge function is called on your input, which must be JSON
# encoded
#
def challenge(self, msg):
if "option" not in msg or msg["option"] not in ("encrypt", "unpad", "check"):
return {"error": "Option must be one of: encrypt, unpad, check"}
if msg["option"] == "encrypt": return self.get_ct()
elif msg["option"] == "unpad": return self.check_padding(msg["ct"])
elif msg["option"] == "check": return self.check_message(msg["message"])
import builtins; builtins.Challenge = Challenge # hack to enable challenge to be run locally, see https://cryptohack.org/faq/#listener
listener.start_server(port=13421)
```
* Áp dụng đúng những gì đã nói ở trên thôi.
* Script:
```py
from Crypto.Util.number import *
from gmpy2 import *
import math
from pwn import *
from tqdm import tqdm
from hashlib import sha256
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from hashlib import md5
import json
import time
BLOCK_SIZE = 16
def single_block_attack(block, oracle):
"""Returns the decryption of the given ciphertext block"""
# zeroing_iv starts out nulled. each iteration of the main loop will add
# one byte to it, working from right to left, until it is fully populated,
# at which point it contains the result of DEC(ct_block)
zeroing_iv = [0] * BLOCK_SIZE
for pad_val in tqdm(range(1, BLOCK_SIZE+1)):
padding_iv = [pad_val ^ b for b in zeroing_iv]
for candidate in tqdm(range(256)):
padding_iv[-pad_val] = candidate
iv = bytes(padding_iv)
if oracle(iv, block):
if pad_val == 1:
# make sure the padding really is of length 1 by changing
# the penultimate block and querying the oracle again
padding_iv[-2] ^= 1
iv = bytes(padding_iv)
if not oracle(iv, block):
continue # false positive; keep searching
break
else:
raise Exception("no valid padding byte found (is the oracle working correctly?)")
zeroing_iv[-pad_val] = candidate ^ pad_val
return zeroing_iv
def full_attack(iv, ct, oracle):
"""Given the iv, ciphertext, and a padding oracle, finds and returns the plaintext"""
assert len(iv) == BLOCK_SIZE and len(ct) % BLOCK_SIZE == 0
msg = iv + ct
blocks = [msg[i:i+BLOCK_SIZE] for i in range(0, len(msg), BLOCK_SIZE)]
result = b''
# loop over pairs of consecutive blocks performing CBC decryption on them
iv = blocks[0]
for ct in blocks[1:]:
dec = single_block_attack(ct, oracle)
pt = bytes(iv_byte ^ dec_byte for iv_byte, dec_byte in zip(iv, dec))
result += pt
iv = ct
return result
def json_send(hsh):
io.sendline(json.dumps(hsh).encode())
def oracle(iv:bytes,ct:bytes):
ct = (iv+ct).hex()
io.sendline(json.dumps({"option":"unpad","ct":ct}).encode())
good = io.recvuntil(b'\n',drop=True)
return good != b'{"result": false}'
HOST = "socket.cryptohack.org"
PORT = 13421
io = remote(HOST,PORT)
io.recvuntil(b'\n',drop=True)
io.sendline(json.dumps({"option":"encrypt"}).encode())
ct = io.recvuntil(b'\n',drop=True).decode()[8:-2]
ct = bytes.fromhex(ct)
iv, ct = ct[:16], ct[16:]
result = full_attack(iv, ct, oracle)
print(result.decode())
io.sendline(json.dumps({"option":"check","message":str(result.decode())}).encode())
flag = io.recvuntil(b'\n',drop=True)
print(flag)
```
* **Flag:crypto{if_you_ask_enough_times_you_usually_get_what_you_want}**
* 1 cách giải khác nữa:
```py
from pwn import remote, xor
from json import loads, dumps
from tqdm import trange
io = remote('socket.cryptohack.org', 13421)
io.recv().decode()
def oracle(iv, ct):
io.sendline(dumps({"option":"unpad", "ct": iv.hex()+ct.hex()}).encode())
return b'true' in io.recv()
io.sendline(dumps({"option":"encrypt"}).encode())
out = bytes.fromhex(loads(io.recv())["ct"])
iv, ct = out[:16], out[16:]
def attack_block(padding_oracle, iv, c):
r = bytes()
for i in reversed(range(16)):
s = bytes([16 - i] * (16 - i))
for b in trange(256):
iv_ = bytes(i) + xor(s, bytes([b]) + r)
if padding_oracle(iv_, c):
r = bytes([b]) + r
break
return xor(iv, r)
def attack(padding_oracle, iv, c):
p = attack_block(padding_oracle, iv, c[0:16])
for i in range(16, len(c), 16):
p += attack_block(padding_oracle, c[i - 16:i], c[i:i + 16])
print(p)
return p
sol = attack(oracle, iv, ct)
io.sendline(dumps({"option":"check", "message": sol.decode()}).encode())
print(io.recv())
# https://github.com/jvdsn/crypto-attacks/blob/master/attacks/cbc/padding_oracle.py
# crypto{if_you_ask_enough_times_you_usually_get_what_you_want}
```