nexus-crypt 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nexus/__init__.py +14 -0
- nexus/constants.py +28 -0
- nexus/core.py +232 -0
- nexus/exceptions.py +11 -0
- nexus/kem.py +25 -0
- nexus/math_utils.py +124 -0
- nexus/signature.py +26 -0
- nexus_crypt-1.0.0.dist-info/METADATA +133 -0
- nexus_crypt-1.0.0.dist-info/RECORD +18 -0
- nexus_crypt-1.0.0.dist-info/WHEEL +5 -0
- nexus_crypt-1.0.0.dist-info/licenses/LICENSE +21 -0
- nexus_crypt-1.0.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +1 -0
- tests/test_cipher.py +259 -0
- tests/test_integrations.py +281 -0
- tests/test_kem.py +0 -0
- tests/test_math_utils.py +241 -0
- tests/test_signature.py +0 -0
tests/test_cipher.py
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import secrets
|
|
3
|
+
from nexus.core import NEXUSCipher
|
|
4
|
+
from nexus.exceptions import NEXUSError, DecryptionError
|
|
5
|
+
from nexus.constants import BLOCK_SIZE, NONCE_SIZE
|
|
6
|
+
|
|
7
|
+
class TestNEXUSCipher:
|
|
8
|
+
|
|
9
|
+
def test_initialization_256bit(self):
|
|
10
|
+
key = secrets.token_bytes(32)
|
|
11
|
+
cipher = NEXUSCipher(key, key_size=256)
|
|
12
|
+
assert cipher.key_size == 256
|
|
13
|
+
assert cipher.rounds == 20
|
|
14
|
+
assert len(cipher.round_keys) == 20
|
|
15
|
+
|
|
16
|
+
def test_initialization_384bit(self):
|
|
17
|
+
key = secrets.token_bytes(48)
|
|
18
|
+
cipher = NEXUSCipher(key, key_size=384)
|
|
19
|
+
assert cipher.key_size == 384
|
|
20
|
+
assert cipher.rounds == 22
|
|
21
|
+
|
|
22
|
+
def test_initialization_512bit(self):
|
|
23
|
+
key = secrets.token_bytes(64)
|
|
24
|
+
cipher = NEXUSCipher(key, key_size=512)
|
|
25
|
+
assert cipher.key_size == 512
|
|
26
|
+
assert cipher.rounds == 24
|
|
27
|
+
|
|
28
|
+
def test_invalid_key_size(self):
|
|
29
|
+
key = secrets.token_bytes(32)
|
|
30
|
+
with pytest.raises(NEXUSError):
|
|
31
|
+
NEXUSCipher(key, key_size=128)
|
|
32
|
+
|
|
33
|
+
def test_invalid_key_length(self):
|
|
34
|
+
key = secrets.token_bytes(16)
|
|
35
|
+
with pytest.raises(NEXUSError):
|
|
36
|
+
NEXUSCipher(key, key_size=256)
|
|
37
|
+
|
|
38
|
+
def test_block_encryption_decryption(self):
|
|
39
|
+
key = secrets.token_bytes(32)
|
|
40
|
+
cipher = NEXUSCipher(key, key_size=256)
|
|
41
|
+
|
|
42
|
+
plaintext = b"A" * BLOCK_SIZE
|
|
43
|
+
nonce = secrets.token_bytes(NONCE_SIZE)
|
|
44
|
+
|
|
45
|
+
ciphertext = cipher.encrypt_block(plaintext, nonce)
|
|
46
|
+
decrypted = cipher.decrypt_block(ciphertext, nonce)
|
|
47
|
+
|
|
48
|
+
assert len(ciphertext) == BLOCK_SIZE
|
|
49
|
+
assert decrypted == plaintext
|
|
50
|
+
|
|
51
|
+
def test_different_nonces_produce_different_ciphertext(self):
|
|
52
|
+
key = secrets.token_bytes(32)
|
|
53
|
+
cipher = NEXUSCipher(key, key_size=256)
|
|
54
|
+
|
|
55
|
+
plaintext = b"Test message" * 5
|
|
56
|
+
nonce1 = secrets.token_bytes(NONCE_SIZE)
|
|
57
|
+
nonce2 = secrets.token_bytes(NONCE_SIZE)
|
|
58
|
+
|
|
59
|
+
ct1, _ = cipher.encrypt(plaintext, nonce1)
|
|
60
|
+
|
|
61
|
+
cipher = NEXUSCipher(key, key_size=256)
|
|
62
|
+
ct2, _ = cipher.encrypt(plaintext, nonce2)
|
|
63
|
+
|
|
64
|
+
assert ct1 != ct2
|
|
65
|
+
|
|
66
|
+
def test_encrypt_decrypt_short_message(self):
|
|
67
|
+
key = secrets.token_bytes(32)
|
|
68
|
+
cipher = NEXUSCipher(key, key_size=256)
|
|
69
|
+
|
|
70
|
+
plaintext = b"Hello, World!"
|
|
71
|
+
ciphertext, nonce = cipher.encrypt(plaintext)
|
|
72
|
+
|
|
73
|
+
cipher2 = NEXUSCipher(key, key_size=256)
|
|
74
|
+
decrypted = cipher2.decrypt(ciphertext, nonce)
|
|
75
|
+
|
|
76
|
+
assert decrypted == plaintext
|
|
77
|
+
|
|
78
|
+
def test_encrypt_decrypt_long_message(self):
|
|
79
|
+
key = secrets.token_bytes(32)
|
|
80
|
+
cipher = NEXUSCipher(key, key_size=256)
|
|
81
|
+
|
|
82
|
+
plaintext = b"Long message! " * 100
|
|
83
|
+
ciphertext, nonce = cipher.encrypt(plaintext)
|
|
84
|
+
|
|
85
|
+
cipher2 = NEXUSCipher(key, key_size=256)
|
|
86
|
+
decrypted = cipher2.decrypt(ciphertext, nonce)
|
|
87
|
+
|
|
88
|
+
assert decrypted == plaintext
|
|
89
|
+
|
|
90
|
+
def test_encrypt_decrypt_exact_block_size(self):
|
|
91
|
+
key = secrets.token_bytes(32)
|
|
92
|
+
cipher = NEXUSCipher(key, key_size=256)
|
|
93
|
+
|
|
94
|
+
plaintext = b"X" * BLOCK_SIZE
|
|
95
|
+
ciphertext, nonce = cipher.encrypt(plaintext)
|
|
96
|
+
|
|
97
|
+
cipher2 = NEXUSCipher(key, key_size=256)
|
|
98
|
+
decrypted = cipher2.decrypt(ciphertext, nonce)
|
|
99
|
+
|
|
100
|
+
assert decrypted == plaintext
|
|
101
|
+
|
|
102
|
+
def test_padding_applied_correctly(self):
|
|
103
|
+
key = secrets.token_bytes(32)
|
|
104
|
+
cipher = NEXUSCipher(key, key_size=256)
|
|
105
|
+
|
|
106
|
+
plaintext = b"Short"
|
|
107
|
+
ciphertext, nonce = cipher.encrypt(plaintext)
|
|
108
|
+
assert len(ciphertext) % BLOCK_SIZE == 0
|
|
109
|
+
|
|
110
|
+
cipher2 = NEXUSCipher(key, key_size=256)
|
|
111
|
+
decrypted = cipher2.decrypt(ciphertext, nonce)
|
|
112
|
+
|
|
113
|
+
assert decrypted == plaintext
|
|
114
|
+
|
|
115
|
+
def test_wrong_key_fails_decryption(self):
|
|
116
|
+
|
|
117
|
+
key1 = secrets.token_bytes(32)
|
|
118
|
+
key2 = secrets.token_bytes(32)
|
|
119
|
+
|
|
120
|
+
cipher1 = NEXUSCipher(key1, key_size=256)
|
|
121
|
+
plaintext = b"Secret message"
|
|
122
|
+
ciphertext, nonce = cipher1.encrypt(plaintext)
|
|
123
|
+
|
|
124
|
+
cipher2 = NEXUSCipher(key2, key_size=256)
|
|
125
|
+
decrypted = cipher2.decrypt(ciphertext, nonce)
|
|
126
|
+
|
|
127
|
+
assert decrypted != plaintext
|
|
128
|
+
|
|
129
|
+
def test_wrong_nonce_fails_decryption(self):
|
|
130
|
+
key = secrets.token_bytes(32)
|
|
131
|
+
|
|
132
|
+
cipher1 = NEXUSCipher(key, key_size=256)
|
|
133
|
+
plaintext = b"Secret message"
|
|
134
|
+
ciphertext, nonce1 = cipher1.encrypt(plaintext)
|
|
135
|
+
|
|
136
|
+
nonce2 = secrets.token_bytes(NONCE_SIZE)
|
|
137
|
+
cipher2 = NEXUSCipher(key, key_size=256)
|
|
138
|
+
decrypted = cipher2.decrypt(ciphertext, nonce2)
|
|
139
|
+
|
|
140
|
+
assert decrypted != plaintext
|
|
141
|
+
|
|
142
|
+
def test_sbox_regeneration(self):
|
|
143
|
+
key = secrets.token_bytes(32)
|
|
144
|
+
cipher = NEXUSCipher(key, key_size=256)
|
|
145
|
+
|
|
146
|
+
initial_sboxes = [s[:] for s in cipher.sboxes]
|
|
147
|
+
|
|
148
|
+
large_data = secrets.token_bytes(1024 * 1024)
|
|
149
|
+
cipher.encrypt(large_data)
|
|
150
|
+
|
|
151
|
+
assert cipher.sboxes != initial_sboxes
|
|
152
|
+
|
|
153
|
+
def test_ciphertext_avalanche_effect(self):
|
|
154
|
+
key = secrets.token_bytes(32)
|
|
155
|
+
|
|
156
|
+
plaintext1 = b"A" * BLOCK_SIZE
|
|
157
|
+
plaintext2 = b"B" + b"A" * (BLOCK_SIZE - 1)
|
|
158
|
+
nonce = secrets.token_bytes(NONCE_SIZE)
|
|
159
|
+
|
|
160
|
+
cipher1 = NEXUSCipher(key, key_size=256)
|
|
161
|
+
ct1 = cipher1.encrypt_block(plaintext1, nonce)
|
|
162
|
+
|
|
163
|
+
cipher2 = NEXUSCipher(key, key_size=256)
|
|
164
|
+
ct2 = cipher2.encrypt_block(plaintext2, nonce)
|
|
165
|
+
|
|
166
|
+
diff_bits = sum(bin(b1 ^ b2).count('1') for b1, b2 in zip(ct1, ct2))
|
|
167
|
+
|
|
168
|
+
expected_bits = BLOCK_SIZE * 8
|
|
169
|
+
assert diff_bits > 0
|
|
170
|
+
|
|
171
|
+
def test_nonce_generation(self):
|
|
172
|
+
"""Test automatic nonce generation"""
|
|
173
|
+
key = secrets.token_bytes(32)
|
|
174
|
+
cipher = NEXUSCipher(key, key_size=256)
|
|
175
|
+
|
|
176
|
+
plaintext = b"Test"
|
|
177
|
+
ct1, nonce1 = cipher.encrypt(plaintext)
|
|
178
|
+
|
|
179
|
+
cipher2 = NEXUSCipher(key, key_size=256)
|
|
180
|
+
ct2, nonce2 = cipher2.encrypt(plaintext)
|
|
181
|
+
|
|
182
|
+
# Nonces should be different
|
|
183
|
+
assert nonce1 != nonce2
|
|
184
|
+
# Ciphertexts should be different
|
|
185
|
+
assert ct1 != ct2
|
|
186
|
+
|
|
187
|
+
def test_empty_message(self):
|
|
188
|
+
"""Test encryption of empty message"""
|
|
189
|
+
key = secrets.token_bytes(32)
|
|
190
|
+
cipher = NEXUSCipher(key, key_size=256)
|
|
191
|
+
|
|
192
|
+
plaintext = b""
|
|
193
|
+
ciphertext, nonce = cipher.encrypt(plaintext)
|
|
194
|
+
|
|
195
|
+
# Should still produce output (padding)
|
|
196
|
+
assert len(ciphertext) == BLOCK_SIZE
|
|
197
|
+
|
|
198
|
+
cipher2 = NEXUSCipher(key, key_size=256)
|
|
199
|
+
decrypted = cipher2.decrypt(ciphertext, nonce)
|
|
200
|
+
|
|
201
|
+
assert decrypted == plaintext
|
|
202
|
+
|
|
203
|
+
def test_binary_data_encryption(self):
|
|
204
|
+
"""Test encryption of binary data"""
|
|
205
|
+
key = secrets.token_bytes(32)
|
|
206
|
+
cipher = NEXUSCipher(key, key_size=256)
|
|
207
|
+
|
|
208
|
+
# Random binary data
|
|
209
|
+
plaintext = secrets.token_bytes(500)
|
|
210
|
+
ciphertext, nonce = cipher.encrypt(plaintext)
|
|
211
|
+
|
|
212
|
+
cipher2 = NEXUSCipher(key, key_size=256)
|
|
213
|
+
decrypted = cipher2.decrypt(ciphertext, nonce)
|
|
214
|
+
|
|
215
|
+
assert decrypted == plaintext
|
|
216
|
+
|
|
217
|
+
def test_unicode_text_encryption(self):
|
|
218
|
+
"""Test encryption of Unicode text"""
|
|
219
|
+
key = secrets.token_bytes(32)
|
|
220
|
+
cipher = NEXUSCipher(key, key_size=256)
|
|
221
|
+
|
|
222
|
+
plaintext = "Hello 世界! 🚀".encode('utf-8')
|
|
223
|
+
ciphertext, nonce = cipher.encrypt(plaintext)
|
|
224
|
+
|
|
225
|
+
cipher2 = NEXUSCipher(key, key_size=256)
|
|
226
|
+
decrypted = cipher2.decrypt(ciphertext, nonce)
|
|
227
|
+
|
|
228
|
+
assert decrypted == plaintext
|
|
229
|
+
assert decrypted.decode('utf-8') == "Hello 世界! 🚀"
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class TestNEXUSCipherPerformance:
|
|
233
|
+
"""Performance benchmarks for NEXUS-Cipher"""
|
|
234
|
+
|
|
235
|
+
def test_encryption_speed_1kb(self, benchmark):
|
|
236
|
+
"""Benchmark encryption of 1KB"""
|
|
237
|
+
key = secrets.token_bytes(32)
|
|
238
|
+
cipher = NEXUSCipher(key, key_size=256)
|
|
239
|
+
data = secrets.token_bytes(1024)
|
|
240
|
+
|
|
241
|
+
benchmark(cipher.encrypt, data)
|
|
242
|
+
|
|
243
|
+
def test_encryption_speed_1mb(self, benchmark):
|
|
244
|
+
"""Benchmark encryption of 1MB"""
|
|
245
|
+
key = secrets.token_bytes(32)
|
|
246
|
+
cipher = NEXUSCipher(key, key_size=256)
|
|
247
|
+
data = secrets.token_bytes(1024 * 1024)
|
|
248
|
+
|
|
249
|
+
benchmark(cipher.encrypt, data)
|
|
250
|
+
|
|
251
|
+
def test_decryption_speed(self, benchmark):
|
|
252
|
+
"""Benchmark decryption speed"""
|
|
253
|
+
key = secrets.token_bytes(32)
|
|
254
|
+
cipher = NEXUSCipher(key, key_size=256)
|
|
255
|
+
data = secrets.token_bytes(1024 * 1024)
|
|
256
|
+
ciphertext, nonce = cipher.encrypt(data)
|
|
257
|
+
|
|
258
|
+
cipher2 = NEXUSCipher(key, key_size=256)
|
|
259
|
+
benchmark(cipher2.decrypt, ciphertext, nonce)
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"""Integration tests for complete NEXUS suite"""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
import secrets
|
|
5
|
+
from nexus import NEXUS
|
|
6
|
+
from nexus.exceptions import NEXUSError, SignatureVerificationError
|
|
7
|
+
|
|
8
|
+
class TestNEXUSIntegration:
|
|
9
|
+
"""Integration tests for full NEXUS protocol"""
|
|
10
|
+
|
|
11
|
+
def test_full_protocol_flow(self):
|
|
12
|
+
"""Test complete protocol: KEM + Encrypt + Sign"""
|
|
13
|
+
# Initialize NEXUS for both parties
|
|
14
|
+
alice = NEXUS(key_size=256)
|
|
15
|
+
bob = NEXUS(key_size=256)
|
|
16
|
+
|
|
17
|
+
# Generate keys
|
|
18
|
+
alice_kem_pk, alice_kem_sk = alice.generate_kem_keypair()
|
|
19
|
+
alice_sign_pk, alice_sign_sk = alice.generate_signing_keypair()
|
|
20
|
+
|
|
21
|
+
bob_kem_pk, bob_kem_sk = bob.generate_kem_keypair()
|
|
22
|
+
bob_sign_pk, bob_sign_sk = bob.generate_signing_keypair()
|
|
23
|
+
|
|
24
|
+
# Alice establishes session with Bob
|
|
25
|
+
kem_ct, alice_shared = alice.establish_session(bob_kem_pk)
|
|
26
|
+
|
|
27
|
+
# Bob accepts session
|
|
28
|
+
bob_shared = bob.accept_session(kem_ct, bob_kem_sk)
|
|
29
|
+
|
|
30
|
+
# Verify shared secrets match
|
|
31
|
+
assert alice_shared == bob_shared
|
|
32
|
+
|
|
33
|
+
# Alice sends encrypted + signed message
|
|
34
|
+
message = b"Hello Bob! This is Alice."
|
|
35
|
+
package = alice.encrypt_and_sign(message, alice_sign_sk)
|
|
36
|
+
|
|
37
|
+
# Bob verifies and decrypts
|
|
38
|
+
decrypted = bob.verify_and_decrypt(package, alice_sign_pk)
|
|
39
|
+
|
|
40
|
+
assert decrypted == message
|
|
41
|
+
|
|
42
|
+
def test_bidirectional_communication(self):
|
|
43
|
+
"""Test both parties can send messages"""
|
|
44
|
+
alice = NEXUS(key_size=256)
|
|
45
|
+
bob = NEXUS(key_size=256)
|
|
46
|
+
|
|
47
|
+
# Setup
|
|
48
|
+
alice_kem_pk, alice_kem_sk = alice.generate_kem_keypair()
|
|
49
|
+
alice_sign_pk, alice_sign_sk = alice.generate_signing_keypair()
|
|
50
|
+
|
|
51
|
+
bob_kem_pk, bob_kem_sk = bob.generate_kem_keypair()
|
|
52
|
+
bob_sign_pk, bob_sign_sk = bob.generate_signing_keypair()
|
|
53
|
+
|
|
54
|
+
# Alice -> Bob session
|
|
55
|
+
kem_ct_ab, _ = alice.establish_session(bob_kem_pk)
|
|
56
|
+
bob.accept_session(kem_ct_ab, bob_kem_sk)
|
|
57
|
+
|
|
58
|
+
# Bob -> Alice session
|
|
59
|
+
bob_to_alice = NEXUS(key_size=256)
|
|
60
|
+
alice_to_bob_receiver = NEXUS(key_size=256)
|
|
61
|
+
kem_ct_ba, _ = bob_to_alice.establish_session(alice_kem_pk)
|
|
62
|
+
alice_to_bob_receiver.accept_session(kem_ct_ba, alice_kem_sk)
|
|
63
|
+
|
|
64
|
+
# Alice -> Bob
|
|
65
|
+
msg_ab = b"Message from Alice to Bob"
|
|
66
|
+
pkg_ab = alice.encrypt_and_sign(msg_ab, alice_sign_sk)
|
|
67
|
+
dec_ab = bob.verify_and_decrypt(pkg_ab, alice_sign_pk)
|
|
68
|
+
assert dec_ab == msg_ab
|
|
69
|
+
|
|
70
|
+
# Bob -> Alice
|
|
71
|
+
msg_ba = b"Message from Bob to Alice"
|
|
72
|
+
pkg_ba = bob_to_alice.encrypt_and_sign(msg_ba, bob_sign_sk)
|
|
73
|
+
dec_ba = alice_to_bob_receiver.verify_and_decrypt(pkg_ba, bob_sign_pk)
|
|
74
|
+
assert dec_ba == msg_ba
|
|
75
|
+
|
|
76
|
+
def test_signature_verification_fails_with_wrong_key(self):
|
|
77
|
+
"""Test signature verification fails with wrong public key"""
|
|
78
|
+
alice = NEXUS(key_size=256)
|
|
79
|
+
bob = NEXUS(key_size=256)
|
|
80
|
+
eve = NEXUS(key_size=256)
|
|
81
|
+
|
|
82
|
+
# Setup
|
|
83
|
+
alice_kem_pk, alice_kem_sk = alice.generate_kem_keypair()
|
|
84
|
+
alice_sign_pk, alice_sign_sk = alice.generate_signing_keypair()
|
|
85
|
+
|
|
86
|
+
bob_kem_pk, bob_kem_sk = bob.generate_kem_keypair()
|
|
87
|
+
|
|
88
|
+
eve_sign_pk, _ = eve.generate_signing_keypair()
|
|
89
|
+
|
|
90
|
+
# Establish session
|
|
91
|
+
kem_ct, _ = alice.establish_session(bob_kem_pk)
|
|
92
|
+
bob.accept_session(kem_ct, bob_kem_sk)
|
|
93
|
+
|
|
94
|
+
# Alice sends message
|
|
95
|
+
message = b"Secret message"
|
|
96
|
+
package = alice.encrypt_and_sign(message, alice_sign_sk)
|
|
97
|
+
|
|
98
|
+
# Bob tries to verify with Eve's key (should fail)
|
|
99
|
+
with pytest.raises(SignatureVerificationError):
|
|
100
|
+
bob.verify_and_decrypt(package, eve_sign_pk)
|
|
101
|
+
|
|
102
|
+
def test_tampered_ciphertext_fails_verification(self):
|
|
103
|
+
"""Test that tampering with ciphertext fails signature verification"""
|
|
104
|
+
alice = NEXUS(key_size=256)
|
|
105
|
+
bob = NEXUS(key_size=256)
|
|
106
|
+
|
|
107
|
+
# Setup
|
|
108
|
+
alice_kem_pk, alice_kem_sk = alice.generate_kem_keypair()
|
|
109
|
+
alice_sign_pk, alice_sign_sk = alice.generate_signing_keypair()
|
|
110
|
+
|
|
111
|
+
bob_kem_pk, bob_kem_sk = bob.generate_kem_keypair()
|
|
112
|
+
|
|
113
|
+
# Establish session
|
|
114
|
+
kem_ct, _ = alice.establish_session(bob_kem_pk)
|
|
115
|
+
bob.accept_session(kem_ct, bob_kem_sk)
|
|
116
|
+
|
|
117
|
+
# Alice sends message
|
|
118
|
+
message = b"Original message"
|
|
119
|
+
package = alice.encrypt_and_sign(message, alice_sign_sk)
|
|
120
|
+
|
|
121
|
+
# Tamper with ciphertext
|
|
122
|
+
tampered_ct = bytearray(package['ciphertext'])
|
|
123
|
+
tampered_ct[0] ^= 1 # Flip one bit
|
|
124
|
+
package['ciphertext'] = bytes(tampered_ct)
|
|
125
|
+
|
|
126
|
+
# Verification should fail
|
|
127
|
+
with pytest.raises(SignatureVerificationError):
|
|
128
|
+
bob.verify_and_decrypt(package, alice_sign_pk)
|
|
129
|
+
|
|
130
|
+
def test_multiple_messages_same_session(self):
|
|
131
|
+
"""Test multiple messages can be sent in same session"""
|
|
132
|
+
alice = NEXUS(key_size=256)
|
|
133
|
+
bob = NEXUS(key_size=256)
|
|
134
|
+
|
|
135
|
+
# Setup
|
|
136
|
+
alice_kem_pk, alice_kem_sk = alice.generate_kem_keypair()
|
|
137
|
+
alice_sign_pk, alice_sign_sk = alice.generate_signing_keypair()
|
|
138
|
+
|
|
139
|
+
bob_kem_pk, bob_kem_sk = bob.generate_kem_keypair()
|
|
140
|
+
|
|
141
|
+
# Establish session
|
|
142
|
+
kem_ct, _ = alice.establish_session(bob_kem_pk)
|
|
143
|
+
bob.accept_session(kem_ct, bob_kem_sk)
|
|
144
|
+
|
|
145
|
+
# Send multiple messages
|
|
146
|
+
messages = [
|
|
147
|
+
b"First message",
|
|
148
|
+
b"Second message",
|
|
149
|
+
b"Third message with more content"
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
for msg in messages:
|
|
153
|
+
package = alice.encrypt_and_sign(msg, alice_sign_sk)
|
|
154
|
+
decrypted = bob.verify_and_decrypt(package, alice_sign_pk)
|
|
155
|
+
assert decrypted == msg
|
|
156
|
+
|
|
157
|
+
def test_large_message_transfer(self):
|
|
158
|
+
"""Test transfer of large message"""
|
|
159
|
+
alice = NEXUS(key_size=256)
|
|
160
|
+
bob = NEXUS(key_size=256)
|
|
161
|
+
|
|
162
|
+
# Setup
|
|
163
|
+
alice_kem_pk, alice_kem_sk = alice.generate_kem_keypair()
|
|
164
|
+
alice_sign_pk, alice_sign_sk = alice.generate_signing_keypair()
|
|
165
|
+
|
|
166
|
+
bob_kem_pk, bob_kem_sk = bob.generate_kem_keypair()
|
|
167
|
+
|
|
168
|
+
# Establish session
|
|
169
|
+
kem_ct, _ = alice.establish_session(bob_kem_pk)
|
|
170
|
+
bob.accept_session(kem_ct, bob_kem_sk)
|
|
171
|
+
|
|
172
|
+
# Large message (1MB)
|
|
173
|
+
message = secrets.token_bytes(1024 * 1024)
|
|
174
|
+
package = alice.encrypt_and_sign(message, alice_sign_sk)
|
|
175
|
+
decrypted = bob.verify_and_decrypt(package, alice_sign_pk)
|
|
176
|
+
|
|
177
|
+
assert decrypted == message
|
|
178
|
+
|
|
179
|
+
def test_hash_functionality(self):
|
|
180
|
+
"""Test hash function"""
|
|
181
|
+
nexus = NEXUS()
|
|
182
|
+
|
|
183
|
+
data = b"Test data to hash"
|
|
184
|
+
hash1 = nexus.hash(data)
|
|
185
|
+
hash2 = nexus.hash(data)
|
|
186
|
+
|
|
187
|
+
# Should be deterministic
|
|
188
|
+
assert hash1 == hash2
|
|
189
|
+
|
|
190
|
+
# Should be 32 bytes (SHA-256)
|
|
191
|
+
assert len(hash1) == 32
|
|
192
|
+
|
|
193
|
+
# Different data should produce different hash
|
|
194
|
+
hash3 = nexus.hash(b"Different data")
|
|
195
|
+
assert hash1 != hash3
|
|
196
|
+
|
|
197
|
+
def test_session_not_established_error(self):
|
|
198
|
+
"""Test error when trying to encrypt without session"""
|
|
199
|
+
alice = NEXUS(key_size=256)
|
|
200
|
+
_, alice_sign_sk = alice.generate_signing_keypair()
|
|
201
|
+
|
|
202
|
+
message = b"Test"
|
|
203
|
+
|
|
204
|
+
with pytest.raises(NEXUSError, match="Session not established"):
|
|
205
|
+
alice.encrypt_and_sign(message, alice_sign_sk)
|
|
206
|
+
|
|
207
|
+
def test_different_key_sizes(self):
|
|
208
|
+
"""Test protocol works with different key sizes"""
|
|
209
|
+
for key_size in [256, 384, 512]:
|
|
210
|
+
alice = NEXUS(key_size=key_size)
|
|
211
|
+
bob = NEXUS(key_size=key_size)
|
|
212
|
+
|
|
213
|
+
alice_kem_pk, alice_kem_sk = alice.generate_kem_keypair()
|
|
214
|
+
alice_sign_pk, alice_sign_sk = alice.generate_signing_keypair()
|
|
215
|
+
|
|
216
|
+
bob_kem_pk, bob_kem_sk = bob.generate_kem_keypair()
|
|
217
|
+
|
|
218
|
+
kem_ct, _ = alice.establish_session(bob_kem_pk)
|
|
219
|
+
bob.accept_session(kem_ct, bob_kem_sk)
|
|
220
|
+
|
|
221
|
+
message = f"Testing with {key_size}-bit key".encode()
|
|
222
|
+
package = alice.encrypt_and_sign(message, alice_sign_sk)
|
|
223
|
+
decrypted = bob.verify_and_decrypt(package, alice_sign_pk)
|
|
224
|
+
|
|
225
|
+
assert decrypted == message
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class TestPerfectForwardSecrecy:
|
|
229
|
+
"""Test Perfect Forward Secrecy properties"""
|
|
230
|
+
|
|
231
|
+
def test_new_session_different_keys(self):
|
|
232
|
+
"""Test that new sessions generate different shared secrets"""
|
|
233
|
+
alice = NEXUS(key_size=256)
|
|
234
|
+
bob_kem_pk, bob_kem_sk = alice.generate_kem_keypair()
|
|
235
|
+
|
|
236
|
+
# Session 1
|
|
237
|
+
alice1 = NEXUS(key_size=256)
|
|
238
|
+
_, shared1 = alice1.establish_session(bob_kem_pk)
|
|
239
|
+
|
|
240
|
+
# Session 2
|
|
241
|
+
alice2 = NEXUS(key_size=256)
|
|
242
|
+
_, shared2 = alice2.establish_session(bob_kem_pk)
|
|
243
|
+
|
|
244
|
+
# Different sessions should have different shared secrets
|
|
245
|
+
assert shared1 != shared2
|
|
246
|
+
|
|
247
|
+
def test_compromise_one_session_doesnt_affect_others(self):
|
|
248
|
+
"""Test that compromising one session doesn't affect others"""
|
|
249
|
+
alice = NEXUS(key_size=256)
|
|
250
|
+
bob = NEXUS(key_size=256)
|
|
251
|
+
|
|
252
|
+
bob_kem_pk, bob_kem_sk = bob.generate_kem_keypair()
|
|
253
|
+
alice_sign_pk, alice_sign_sk = alice.generate_signing_keypair()
|
|
254
|
+
|
|
255
|
+
# Session 1
|
|
256
|
+
alice1 = NEXUS(key_size=256)
|
|
257
|
+
kem_ct1, shared1 = alice1.establish_session(bob_kem_pk)
|
|
258
|
+
bob1 = NEXUS(key_size=256)
|
|
259
|
+
bob1.accept_session(kem_ct1, bob_kem_sk)
|
|
260
|
+
|
|
261
|
+
msg1 = b"Message in session 1"
|
|
262
|
+
pkg1 = alice1.encrypt_and_sign(msg1, alice_sign_sk)
|
|
263
|
+
|
|
264
|
+
# Session 2 (independent)
|
|
265
|
+
alice2 = NEXUS(key_size=256)
|
|
266
|
+
kem_ct2, shared2 = alice2.establish_session(bob_kem_pk)
|
|
267
|
+
bob2 = NEXUS(key_size=256)
|
|
268
|
+
bob2.accept_session(kem_ct2, bob_kem_sk)
|
|
269
|
+
|
|
270
|
+
msg2 = b"Message in session 2"
|
|
271
|
+
pkg2 = alice2.encrypt_and_sign(msg2, alice_sign_sk)
|
|
272
|
+
|
|
273
|
+
# Both should decrypt correctly
|
|
274
|
+
dec1 = bob1.verify_and_decrypt(pkg1, alice_sign_pk)
|
|
275
|
+
dec2 = bob2.verify_and_decrypt(pkg2, alice_sign_pk)
|
|
276
|
+
|
|
277
|
+
assert dec1 == msg1
|
|
278
|
+
assert dec2 == msg2
|
|
279
|
+
|
|
280
|
+
# Sessions are independent
|
|
281
|
+
assert shared1 != shared2
|
tests/test_kem.py
ADDED
|
File without changes
|