Skip to content

Commit c84c0f5

Browse files
Move crypto directory under pydatastructs
1 parent c4746f4 commit c84c0f5

File tree

2 files changed

+114
-0
lines changed

2 files changed

+114
-0
lines changed
File renamed without changes.
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import random
2+
import string
3+
from crypto.ChaCha20 import ChaCha20
4+
5+
VALID_KEY = b"\x00" *32
6+
assert len(VALID_KEY) == 32, "VALID_KEY must be exactly 32 bytes"
7+
VALID_NONCE = B"\x00" * 12
8+
assert len(VALID_NONCE) == 12, "VALID_NONCE must be exactly 12 bytes"
9+
10+
secure_rng = random.SystemRandom()
11+
12+
def test_invalid_key_size():
13+
"""Test invalid key sizes."""
14+
try:
15+
ChaCha20(b"short_key", VALID_NONCE)
16+
except ValueError as e:
17+
assert "Key must be exactly 32 bytes" in str(e)
18+
else:
19+
assert False, "ValueError was not raised for short key"
20+
21+
try:
22+
ChaCha20(b"A" * 33, VALID_NONCE)
23+
except ValueError as e:
24+
assert "Key must be exactly 32 bytes" in str(e)
25+
else:
26+
assert False, "ValueError was not raised for long key"
27+
28+
def test_invalid_nonce_size():
29+
"""Test invalid nonce sizes."""
30+
try:
31+
ChaCha20(VALID_KEY, b"short")
32+
except ValueError as e:
33+
assert "Nonce must be exactly 12 bytes" in str(e)
34+
else:
35+
assert False, "ValueError was not raised for short nonce"
36+
37+
try:
38+
ChaCha20(VALID_KEY, b"A" * 13)
39+
except ValueError as e:
40+
assert "Nonce must be exactly 12 bytes" in str(e)
41+
else:
42+
assert False, "ValueError was not raised for long nonce"
43+
44+
def test_invalid_counter_values():
45+
"""Test invalid counter values for ChaCha20."""
46+
for invalid_counter in [-1, -100, -999999]:
47+
try:
48+
ChaCha20(VALID_KEY, VALID_NONCE, counter=invalid_counter)
49+
except ValueError as e:
50+
assert "Counter must be a non-negative integer" in str(e)
51+
else:
52+
assert False, f"ValueError not raised for counter={invalid_counter}"
53+
54+
def test_encrypt_decrypt():
55+
"""Test encryption and decryption are symmetric."""
56+
cipher = ChaCha20(VALID_KEY, VALID_NONCE)
57+
plaintext = b"Hello, ChaCha20!"
58+
ciphertext = cipher.encrypt(plaintext)
59+
decrypted = cipher.decrypt(ciphertext)
60+
61+
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

Comments
 (0)