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.
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