@@ -59,3 +59,56 @@ def test_encrypt_decrypt():
5959 decrypted = cipher .decrypt (ciphertext )
6060
6161 assert decrypted == plaintext , "Decryption failed. Plaintext does not match."
62+
63+ def test_key_reuse_simple ():
64+ """
65+ Test the vulnerability of key reuse in ChaCha20 encryption.
66+
67+ This test demonstrates the security flaw of reusing the same key and nonce
68+ for different plaintexts in stream ciphers. It exploits the property that
69+ XORing two ciphertexts from the same keystream cancels out the keystream,
70+ revealing the XOR of the plaintexts.
71+
72+ Encrypt two different plaintexts with the same key and nonce.
73+ XOR the resulting ciphertexts to remove the keystream, leaving only the XOR of plaintexts.
74+ XOR the result with the first plaintext to recover the second plaintext.
75+ Assert that the recovered plaintext matches the original second plaintext.
76+
77+ Expected Behavior:
78+ - If the ChaCha20 implementation is correct, reusing the same key and nonce
79+ will expose the XOR relationship between plaintexts.
80+ - The test should successfully recover the second plaintext using XOR operations.
81+
82+ Assertion:
83+ - Raises an AssertionError if the recovered plaintext does not match the
84+ original second plaintext, indicating a failure in the XOR recovery logic.
85+
86+ Output:
87+ - Prints the original second plaintext.
88+ - Prints the recovered plaintext (should be identical to the original).
89+ - Displays the XOR result (hexadecimal format) for inspection.
90+
91+ Security Note:
92+ - This test highlights why it is critical never to reuse the same key and nonce
93+ in stream ciphers like ChaCha20.
94+ """
95+
96+
97+ cipher1 = ChaCha20 (VALID_KEY , VALID_NONCE )
98+ cipher2 = ChaCha20 (VALID_KEY , VALID_NONCE )
99+
100+ plaintext1 = b"Hello, this is message one!"
101+ plaintext2 = b"Hi there, this is message two!"
102+
103+ ciphertext1 = cipher1 .encrypt (plaintext1 )
104+ ciphertext2 = cipher2 .encrypt (plaintext2 )
105+
106+ xor_result = []
107+ for c1_byte , c2_byte in zip (ciphertext1 , ciphertext2 ):
108+ xor_result .append (c1_byte ^ c2_byte )
109+ xor_bytes = bytes (xor_result )
110+ recovered = []
111+ for xor_byte , p1_byte in zip (xor_bytes , plaintext1 ):
112+ recovered .append (xor_byte ^ p1_byte )
113+ recovered_plaintext = bytes (recovered )
114+ assert recovered_plaintext == plaintext2 , "Failed to recover second plaintext from XOR pattern"
0 commit comments