nexus-crypt 1.0.0__tar.gz
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_crypt-1.0.0/LICENSE +21 -0
- nexus_crypt-1.0.0/MANIFEST.in +5 -0
- nexus_crypt-1.0.0/PKG-INFO +133 -0
- nexus_crypt-1.0.0/README.md +96 -0
- nexus_crypt-1.0.0/examples/basic_usage.py +50 -0
- nexus_crypt-1.0.0/nexus/__init__.py +14 -0
- nexus_crypt-1.0.0/nexus/constants.py +28 -0
- nexus_crypt-1.0.0/nexus/core.py +232 -0
- nexus_crypt-1.0.0/nexus/exceptions.py +11 -0
- nexus_crypt-1.0.0/nexus/kem.py +25 -0
- nexus_crypt-1.0.0/nexus/math_utils.py +124 -0
- nexus_crypt-1.0.0/nexus/signature.py +26 -0
- nexus_crypt-1.0.0/nexus_crypt.egg-info/PKG-INFO +133 -0
- nexus_crypt-1.0.0/nexus_crypt.egg-info/SOURCES.txt +24 -0
- nexus_crypt-1.0.0/nexus_crypt.egg-info/dependency_links.txt +1 -0
- nexus_crypt-1.0.0/nexus_crypt.egg-info/requires.txt +8 -0
- nexus_crypt-1.0.0/nexus_crypt.egg-info/top_level.txt +2 -0
- nexus_crypt-1.0.0/requirements.txt +6 -0
- nexus_crypt-1.0.0/setup.cfg +4 -0
- nexus_crypt-1.0.0/setup.py +35 -0
- nexus_crypt-1.0.0/tests/__init__.py +1 -0
- nexus_crypt-1.0.0/tests/test_cipher.py +259 -0
- nexus_crypt-1.0.0/tests/test_integrations.py +281 -0
- nexus_crypt-1.0.0/tests/test_kem.py +0 -0
- nexus_crypt-1.0.0/tests/test_math_utils.py +241 -0
- nexus_crypt-1.0.0/tests/test_signature.py +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Harshith Madhavaram
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nexus-crypt
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Post-quantum cryptographic suite with PFS, encryption, signatures, and hashing
|
|
5
|
+
Home-page: https://github.com/Harshith2412/nexus-crypt
|
|
6
|
+
Author: Harshith Madhavaram
|
|
7
|
+
Author-email: madhavaram.harshith2412@gmail.com
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Topic :: Security :: Cryptography
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Requires-Python: >=3.8
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: pycryptodome>=3.19.0
|
|
20
|
+
Requires-Dist: pqcrypto>=0.1.0
|
|
21
|
+
Requires-Dist: numpy>=1.24.0
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
24
|
+
Requires-Dist: black; extra == "dev"
|
|
25
|
+
Requires-Dist: mypy; extra == "dev"
|
|
26
|
+
Dynamic: author
|
|
27
|
+
Dynamic: author-email
|
|
28
|
+
Dynamic: classifier
|
|
29
|
+
Dynamic: description
|
|
30
|
+
Dynamic: description-content-type
|
|
31
|
+
Dynamic: home-page
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
Dynamic: provides-extra
|
|
34
|
+
Dynamic: requires-dist
|
|
35
|
+
Dynamic: requires-python
|
|
36
|
+
Dynamic: summary
|
|
37
|
+
|
|
38
|
+
NEXUS-Crypt
|
|
39
|
+
|
|
40
|
+
Post-Quantum Cryptographic Suite with Perfect Forward Secrecy
|
|
41
|
+
|
|
42
|
+
NEXUS-Crypt is a unified cryptographic library combining:
|
|
43
|
+
- NEXUS-Cipher: Custom lattice based symmetric encryption
|
|
44
|
+
- ML-KEM (Kyber): Key encapsulation for Perfect Forward Secrecy
|
|
45
|
+
- ML-DSA (Dilithium): Post-quantum digital signatures
|
|
46
|
+
- SHA-256: Cryptographic hashing
|
|
47
|
+
|
|
48
|
+
Features
|
|
49
|
+
|
|
50
|
+
Post-quantum secure (resistant to quantum computer attacks)
|
|
51
|
+
Perfect Forward Secrecy (PFS) via ML-KEM
|
|
52
|
+
Authenticated encryption with ML-DSA signatures
|
|
53
|
+
Constant time operations (side-channel resistant)
|
|
54
|
+
Easy to use API
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
```bash
|
|
58
|
+
pip install nexus-crypt
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Quick Start
|
|
62
|
+
```python
|
|
63
|
+
from nexus import NEXUS
|
|
64
|
+
|
|
65
|
+
nexus = NEXUS(key_size=256)
|
|
66
|
+
|
|
67
|
+
kem_pk, kem_sk = nexus.generate_kem_keypair()
|
|
68
|
+
sign_pk, sign_sk = nexus.generate_signing_keypair()
|
|
69
|
+
|
|
70
|
+
ciphertext, shared_secret = nexus.establish_session(kem_pk)
|
|
71
|
+
|
|
72
|
+
message = b"Secret data"
|
|
73
|
+
package = nexus.encrypt_and_sign(message, sign_sk)
|
|
74
|
+
|
|
75
|
+
plaintext = nexus.verify_and_decrypt(package, sign_pk)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Security Properties
|
|
79
|
+
|
|
80
|
+
- Quantum Resistance: Based on lattice problems (LWE, NTRU)
|
|
81
|
+
- Key Sizes: 256, 384, or 512 bits
|
|
82
|
+
- Block Size: 512 bits (64 bytes)
|
|
83
|
+
- Rounds: 20-24 depending on key size
|
|
84
|
+
|
|
85
|
+
## Use Cases
|
|
86
|
+
|
|
87
|
+
- Automotive CAN bus encryption
|
|
88
|
+
- OTA firmware updates
|
|
89
|
+
- V2X communication
|
|
90
|
+
- IoT device security
|
|
91
|
+
- Secure messaging
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
MIT License
|
|
96
|
+
|
|
97
|
+
## Author
|
|
98
|
+
|
|
99
|
+
Harshith Madhavaram
|
|
100
|
+
MS Cybersecurity '2027
|
|
101
|
+
Northeastern University, Boston
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## `.gitignore`**
|
|
107
|
+
```
|
|
108
|
+
__pycache__/
|
|
109
|
+
*.py[cod]
|
|
110
|
+
*$py.class
|
|
111
|
+
*.so
|
|
112
|
+
.Python
|
|
113
|
+
build/
|
|
114
|
+
develop-eggs/
|
|
115
|
+
dist/
|
|
116
|
+
downloads/
|
|
117
|
+
eggs/
|
|
118
|
+
.eggs/
|
|
119
|
+
lib/
|
|
120
|
+
lib64/
|
|
121
|
+
parts/
|
|
122
|
+
sdist/
|
|
123
|
+
var/
|
|
124
|
+
wheels/
|
|
125
|
+
*.egg-info/
|
|
126
|
+
.installed.cfg
|
|
127
|
+
*.egg
|
|
128
|
+
.pytest_cache/
|
|
129
|
+
.coverage
|
|
130
|
+
htmlcov/
|
|
131
|
+
.env
|
|
132
|
+
venv/
|
|
133
|
+
ENV/
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
NEXUS-Crypt
|
|
2
|
+
|
|
3
|
+
Post-Quantum Cryptographic Suite with Perfect Forward Secrecy
|
|
4
|
+
|
|
5
|
+
NEXUS-Crypt is a unified cryptographic library combining:
|
|
6
|
+
- NEXUS-Cipher: Custom lattice based symmetric encryption
|
|
7
|
+
- ML-KEM (Kyber): Key encapsulation for Perfect Forward Secrecy
|
|
8
|
+
- ML-DSA (Dilithium): Post-quantum digital signatures
|
|
9
|
+
- SHA-256: Cryptographic hashing
|
|
10
|
+
|
|
11
|
+
Features
|
|
12
|
+
|
|
13
|
+
Post-quantum secure (resistant to quantum computer attacks)
|
|
14
|
+
Perfect Forward Secrecy (PFS) via ML-KEM
|
|
15
|
+
Authenticated encryption with ML-DSA signatures
|
|
16
|
+
Constant time operations (side-channel resistant)
|
|
17
|
+
Easy to use API
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
```bash
|
|
21
|
+
pip install nexus-crypt
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
```python
|
|
26
|
+
from nexus import NEXUS
|
|
27
|
+
|
|
28
|
+
nexus = NEXUS(key_size=256)
|
|
29
|
+
|
|
30
|
+
kem_pk, kem_sk = nexus.generate_kem_keypair()
|
|
31
|
+
sign_pk, sign_sk = nexus.generate_signing_keypair()
|
|
32
|
+
|
|
33
|
+
ciphertext, shared_secret = nexus.establish_session(kem_pk)
|
|
34
|
+
|
|
35
|
+
message = b"Secret data"
|
|
36
|
+
package = nexus.encrypt_and_sign(message, sign_sk)
|
|
37
|
+
|
|
38
|
+
plaintext = nexus.verify_and_decrypt(package, sign_pk)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Security Properties
|
|
42
|
+
|
|
43
|
+
- Quantum Resistance: Based on lattice problems (LWE, NTRU)
|
|
44
|
+
- Key Sizes: 256, 384, or 512 bits
|
|
45
|
+
- Block Size: 512 bits (64 bytes)
|
|
46
|
+
- Rounds: 20-24 depending on key size
|
|
47
|
+
|
|
48
|
+
## Use Cases
|
|
49
|
+
|
|
50
|
+
- Automotive CAN bus encryption
|
|
51
|
+
- OTA firmware updates
|
|
52
|
+
- V2X communication
|
|
53
|
+
- IoT device security
|
|
54
|
+
- Secure messaging
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
MIT License
|
|
59
|
+
|
|
60
|
+
## Author
|
|
61
|
+
|
|
62
|
+
Harshith Madhavaram
|
|
63
|
+
MS Cybersecurity '2027
|
|
64
|
+
Northeastern University, Boston
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## `.gitignore`**
|
|
70
|
+
```
|
|
71
|
+
__pycache__/
|
|
72
|
+
*.py[cod]
|
|
73
|
+
*$py.class
|
|
74
|
+
*.so
|
|
75
|
+
.Python
|
|
76
|
+
build/
|
|
77
|
+
develop-eggs/
|
|
78
|
+
dist/
|
|
79
|
+
downloads/
|
|
80
|
+
eggs/
|
|
81
|
+
.eggs/
|
|
82
|
+
lib/
|
|
83
|
+
lib64/
|
|
84
|
+
parts/
|
|
85
|
+
sdist/
|
|
86
|
+
var/
|
|
87
|
+
wheels/
|
|
88
|
+
*.egg-info/
|
|
89
|
+
.installed.cfg
|
|
90
|
+
*.egg
|
|
91
|
+
.pytest_cache/
|
|
92
|
+
.coverage
|
|
93
|
+
htmlcov/
|
|
94
|
+
.env
|
|
95
|
+
venv/
|
|
96
|
+
ENV/
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Basic usage example for NEXUS-Crypt"""
|
|
2
|
+
|
|
3
|
+
from nexus import NEXUS
|
|
4
|
+
|
|
5
|
+
def main():
|
|
6
|
+
print("=== NEXUS-Crypt Demo ===\n")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
nexus = NEXUS(key_size=256)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
print("1. Generating keys...")
|
|
13
|
+
alice_kem_pk, alice_kem_sk = nexus.generate_kem_keypair()
|
|
14
|
+
alice_sign_pk, alice_sign_sk = nexus.generate_signing_keypair()
|
|
15
|
+
|
|
16
|
+
bob_kem_pk, bob_kem_sk = nexus.generate_kem_keypair()
|
|
17
|
+
bob_sign_pk, bob_sign_sk = nexus.generate_signing_keypair()
|
|
18
|
+
print("Keys generated\n")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
print("2. Alice establishing session with Bob...")
|
|
22
|
+
alice_nexus = NEXUS(key_size=256)
|
|
23
|
+
kem_ciphertext, alice_shared = alice_nexus.establish_session(bob_kem_pk)
|
|
24
|
+
print(f"Session established (shared secret: {alice_shared[:8].hex()}...)\n")
|
|
25
|
+
|
|
26
|
+
print("3. Bob accepting session...")
|
|
27
|
+
bob_nexus = NEXUS(key_size=256)
|
|
28
|
+
bob_shared = bob_nexus.accept_session(kem_ciphertext, bob_kem_sk)
|
|
29
|
+
print(f" Session accepted (shared secret: {bob_shared[:8].hex()}...)\n")
|
|
30
|
+
|
|
31
|
+
assert alice_shared == bob_shared, "Shared secrets don't match!"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
print("4. Alice encrypting and signing message...")
|
|
35
|
+
message = b"Hello Bob! This is a secure message using NEXUS-Crypt with PFS!"
|
|
36
|
+
package = alice_nexus.encrypt_and_sign(message, alice_sign_sk)
|
|
37
|
+
print(f" Message encrypted ({len(package['ciphertext'])} bytes)")
|
|
38
|
+
print(f"Signature generated ({len(package['signature'])} bytes)\n")
|
|
39
|
+
|
|
40
|
+
print("5. Bob verifying and decrypting...")
|
|
41
|
+
decrypted = bob_nexus.verify_and_decrypt(package, alice_sign_pk)
|
|
42
|
+
print(f" Signature verified")
|
|
43
|
+
print(f" Message decrypted: {decrypted.decode()}\n")
|
|
44
|
+
|
|
45
|
+
assert decrypted == message, "Decryption failed!"
|
|
46
|
+
|
|
47
|
+
print("=== All tests passed! ===")
|
|
48
|
+
|
|
49
|
+
if __name__ == "__main__":
|
|
50
|
+
main()
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
|
|
2
|
+
__version__ = "1.0.0"
|
|
3
|
+
__author__ = "Harshith Madhavaram"
|
|
4
|
+
|
|
5
|
+
from .core import NEXUSCipher, NEXUS
|
|
6
|
+
from .exceptions import NEXUSError, DecryptionError, SignatureVerificationError
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"NEXUS",
|
|
10
|
+
"NEXUSCipher",
|
|
11
|
+
"NEXUSError",
|
|
12
|
+
"DecryptionError",
|
|
13
|
+
"SignatureVerificationError",
|
|
14
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
STATE_SIZE = 512
|
|
4
|
+
WORD_SIZE = 128
|
|
5
|
+
NUM_WORDS = 4
|
|
6
|
+
BLOCK_SIZE = 64
|
|
7
|
+
|
|
8
|
+
ROUNDS_256 = 20
|
|
9
|
+
ROUNDS_384 = 22
|
|
10
|
+
ROUNDS_512 = 24
|
|
11
|
+
|
|
12
|
+
PRIME_ROTATIONS = [17, 31, 61, 127]
|
|
13
|
+
|
|
14
|
+
GF128_POLY = 0b10000111
|
|
15
|
+
|
|
16
|
+
LWE_MODULUS = 257
|
|
17
|
+
LWE_ERROR_SIGMA = 3.2
|
|
18
|
+
SBOX_COUNT = 8
|
|
19
|
+
SBOX_SIZE = 256
|
|
20
|
+
|
|
21
|
+
SBOX_REGEN_BYTES = 1024 * 1024
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
NTRU_N = 512
|
|
25
|
+
NTRU_Q = 2**128
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
NONCE_SIZE = 16
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import secrets
|
|
2
|
+
import hashlib
|
|
3
|
+
from typing import List, Tuple
|
|
4
|
+
from .math_utils import *
|
|
5
|
+
from .constants import *
|
|
6
|
+
from .exceptions import *
|
|
7
|
+
|
|
8
|
+
class NEXUSCipher:
|
|
9
|
+
|
|
10
|
+
def __init__(self, key: bytes, key_size: int = 256):
|
|
11
|
+
if key_size not in [256, 384, 512]:
|
|
12
|
+
raise NEXUSError("Key size must be 256, 384, or 512 bits")
|
|
13
|
+
|
|
14
|
+
expected_len = key_size // 8
|
|
15
|
+
if len(key) != expected_len:
|
|
16
|
+
raise NEXUSError(f"Key must be {expected_len} bytes for {key_size}-bit security")
|
|
17
|
+
|
|
18
|
+
self.key = key
|
|
19
|
+
self.key_size = key_size
|
|
20
|
+
self.rounds = self._get_rounds()
|
|
21
|
+
self.round_keys = self._derive_round_keys()
|
|
22
|
+
self.sboxes = [generate_lwe_sbox(key, i, 0) for i in range(SBOX_COUNT)]
|
|
23
|
+
self.sbox_counter = 0
|
|
24
|
+
self.bytes_processed = 0
|
|
25
|
+
self.gf_matrices = self._generate_gf_matrices()
|
|
26
|
+
|
|
27
|
+
def _get_rounds(self) -> int:
|
|
28
|
+
return {256: ROUNDS_256, 384: ROUNDS_384, 512: ROUNDS_512}[self.key_size]
|
|
29
|
+
|
|
30
|
+
def _derive_round_keys(self) -> List[List[int]]:
|
|
31
|
+
key_material = hkdf_sha256(self.key, self.rounds * 64, b"NEXUS-ROUND-KEYS")
|
|
32
|
+
round_keys = []
|
|
33
|
+
for i in range(self.rounds):
|
|
34
|
+
round_key_bytes = key_material[i*64:(i+1)*64]
|
|
35
|
+
round_key_words = bytes_to_words(round_key_bytes)
|
|
36
|
+
round_keys.append(round_key_words)
|
|
37
|
+
return round_keys
|
|
38
|
+
|
|
39
|
+
def _generate_gf_matrices(self) -> List[int]:
|
|
40
|
+
matrices = []
|
|
41
|
+
for i in range(NUM_WORDS):
|
|
42
|
+
h = hashlib.sha256()
|
|
43
|
+
h.update(self.key)
|
|
44
|
+
h.update(b"GF-MATRIX")
|
|
45
|
+
h.update(i.to_bytes(1, 'big'))
|
|
46
|
+
matrix_element = int.from_bytes(h.digest()[:16], 'big')
|
|
47
|
+
matrices.append(matrix_element)
|
|
48
|
+
return matrices
|
|
49
|
+
|
|
50
|
+
def _regenerate_sboxes_if_needed(self):
|
|
51
|
+
if self.bytes_processed >= SBOX_REGEN_BYTES:
|
|
52
|
+
self.sbox_counter += 1
|
|
53
|
+
self.sboxes = [generate_lwe_sbox(self.key, i, self.sbox_counter) for i in range(SBOX_COUNT)]
|
|
54
|
+
self.bytes_processed = 0
|
|
55
|
+
|
|
56
|
+
def _nexus_round(self, state: List[int], round_num: int) -> List[int]:
|
|
57
|
+
for i in range(NUM_WORDS):
|
|
58
|
+
state[i] = (state[i] + self.round_keys[round_num][i]) & ((1 << 128) - 1)
|
|
59
|
+
state[i] = rol(state[i], PRIME_ROTATIONS[i], 128)
|
|
60
|
+
lattice_const = generate_lattice_constant(round_num, i)
|
|
61
|
+
state[i] ^= lattice_const
|
|
62
|
+
|
|
63
|
+
state_bytes = bytearray(words_to_bytes(state))
|
|
64
|
+
for byte_idx in range(len(state_bytes)):
|
|
65
|
+
sbox_id = byte_idx % SBOX_COUNT
|
|
66
|
+
state_bytes[byte_idx] = self.sboxes[sbox_id][state_bytes[byte_idx]]
|
|
67
|
+
state = bytes_to_words(bytes(state_bytes))
|
|
68
|
+
|
|
69
|
+
for i in range(NUM_WORDS):
|
|
70
|
+
state[i] = gf128_multiply(state[i], self.gf_matrices[i])
|
|
71
|
+
|
|
72
|
+
state_bits = []
|
|
73
|
+
for word in state:
|
|
74
|
+
for bit_pos in range(128):
|
|
75
|
+
state_bits.append((word >> bit_pos) & 1)
|
|
76
|
+
|
|
77
|
+
ntru_perm = generate_ntru_permutation(self.key, round_num)
|
|
78
|
+
permuted_bits = [state_bits[ntru_perm[i]] for i in range(NTRU_N)]
|
|
79
|
+
|
|
80
|
+
state = []
|
|
81
|
+
for word_idx in range(NUM_WORDS):
|
|
82
|
+
word = 0
|
|
83
|
+
for bit_idx in range(128):
|
|
84
|
+
bit_pos = word_idx * 128 + bit_idx
|
|
85
|
+
word |= (permuted_bits[bit_pos] << bit_idx)
|
|
86
|
+
state.append(word)
|
|
87
|
+
|
|
88
|
+
return state
|
|
89
|
+
|
|
90
|
+
def _generate_keystream(self, nonce: bytes) -> bytes:
|
|
91
|
+
state = [0] * NUM_WORDS
|
|
92
|
+
nonce_extended = nonce + nonce + nonce + nonce
|
|
93
|
+
nonce_words = bytes_to_words(nonce_extended)
|
|
94
|
+
|
|
95
|
+
for i in range(NUM_WORDS):
|
|
96
|
+
state[i] ^= nonce_words[i]
|
|
97
|
+
|
|
98
|
+
for round_num in range(self.rounds):
|
|
99
|
+
state = self._nexus_round(state, round_num)
|
|
100
|
+
|
|
101
|
+
return words_to_bytes(state)
|
|
102
|
+
|
|
103
|
+
def encrypt_block(self, plaintext: bytes, nonce: bytes) -> bytes:
|
|
104
|
+
if len(plaintext) != BLOCK_SIZE:
|
|
105
|
+
raise NEXUSError(f"Block must be {BLOCK_SIZE} bytes")
|
|
106
|
+
if len(nonce) != NONCE_SIZE:
|
|
107
|
+
raise NEXUSError(f"Nonce must be {NONCE_SIZE} bytes")
|
|
108
|
+
|
|
109
|
+
keystream = self._generate_keystream(nonce)
|
|
110
|
+
ciphertext = bytes([p ^ k for p, k in zip(plaintext, keystream)])
|
|
111
|
+
self.bytes_processed += BLOCK_SIZE
|
|
112
|
+
self._regenerate_sboxes_if_needed()
|
|
113
|
+
return ciphertext
|
|
114
|
+
|
|
115
|
+
def decrypt_block(self, ciphertext: bytes, nonce: bytes) -> bytes:
|
|
116
|
+
if len(ciphertext) != BLOCK_SIZE:
|
|
117
|
+
raise NEXUSError(f"Block must be {BLOCK_SIZE} bytes")
|
|
118
|
+
if len(nonce) != NONCE_SIZE:
|
|
119
|
+
raise NEXUSError(f"Nonce must be {NONCE_SIZE} bytes")
|
|
120
|
+
|
|
121
|
+
keystream = self._generate_keystream(nonce)
|
|
122
|
+
plaintext = bytes([c ^ k for c, k in zip(ciphertext, keystream)])
|
|
123
|
+
self.bytes_processed += BLOCK_SIZE
|
|
124
|
+
self._regenerate_sboxes_if_needed()
|
|
125
|
+
return plaintext
|
|
126
|
+
|
|
127
|
+
def encrypt(self, plaintext: bytes, nonce: bytes = None) -> Tuple[bytes, bytes]:
|
|
128
|
+
if nonce is None:
|
|
129
|
+
nonce = secrets.token_bytes(NONCE_SIZE)
|
|
130
|
+
|
|
131
|
+
pad_len = BLOCK_SIZE - (len(plaintext) % BLOCK_SIZE)
|
|
132
|
+
plaintext += bytes([pad_len] * pad_len)
|
|
133
|
+
|
|
134
|
+
ciphertext = b''
|
|
135
|
+
for i in range(0, len(plaintext), BLOCK_SIZE):
|
|
136
|
+
block = plaintext[i:i+BLOCK_SIZE]
|
|
137
|
+
block_nonce = nonce + i.to_bytes(16, 'big')
|
|
138
|
+
block_nonce = hashlib.sha256(block_nonce).digest()[:NONCE_SIZE]
|
|
139
|
+
encrypted_block = self.encrypt_block(block, block_nonce)
|
|
140
|
+
ciphertext += encrypted_block
|
|
141
|
+
|
|
142
|
+
return ciphertext, nonce
|
|
143
|
+
|
|
144
|
+
def decrypt(self, ciphertext: bytes, nonce: bytes) -> bytes:
|
|
145
|
+
if len(ciphertext) % BLOCK_SIZE != 0:
|
|
146
|
+
raise DecryptionError("Invalid ciphertext length")
|
|
147
|
+
|
|
148
|
+
plaintext = b''
|
|
149
|
+
for i in range(0, len(ciphertext), BLOCK_SIZE):
|
|
150
|
+
block = ciphertext[i:i+BLOCK_SIZE]
|
|
151
|
+
block_nonce = nonce + i.to_bytes(16, 'big')
|
|
152
|
+
block_nonce = hashlib.sha256(block_nonce).digest()[:NONCE_SIZE]
|
|
153
|
+
decrypted_block = self.decrypt_block(block, block_nonce)
|
|
154
|
+
plaintext += decrypted_block
|
|
155
|
+
|
|
156
|
+
pad_len = plaintext[-1]
|
|
157
|
+
if pad_len <= BLOCK_SIZE and pad_len > 0:
|
|
158
|
+
plaintext = plaintext[:-pad_len]
|
|
159
|
+
|
|
160
|
+
return plaintext
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class NEXUS:
|
|
164
|
+
|
|
165
|
+
def __init__(self, key_size: int = 256):
|
|
166
|
+
self.key_size = key_size
|
|
167
|
+
self.cipher = None
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
from pqcrypto.kem.ml_kem_768 import generate_keypair as kyber_keygen
|
|
171
|
+
from pqcrypto.kem.ml_kem_768 import encrypt as kyber_encrypt
|
|
172
|
+
from pqcrypto.kem.ml_kem_768 import decrypt as kyber_decrypt
|
|
173
|
+
from pqcrypto.sign.ml_dsa_65 import generate_keypair as dilithium_keygen
|
|
174
|
+
from pqcrypto.sign.ml_dsa_65 import sign as dilithium_sign
|
|
175
|
+
from pqcrypto.sign.ml_dsa_65 import verify as dilithium_verify
|
|
176
|
+
|
|
177
|
+
self.kyber_keygen = kyber_keygen
|
|
178
|
+
self.kyber_encrypt = kyber_encrypt
|
|
179
|
+
self.kyber_decrypt = kyber_decrypt
|
|
180
|
+
self.dilithium_keygen = dilithium_keygen
|
|
181
|
+
self.dilithium_sign = dilithium_sign
|
|
182
|
+
self.dilithium_verify = dilithium_verify
|
|
183
|
+
except ImportError:
|
|
184
|
+
raise NEXUSError("Please install pqcrypto: pip install pqcrypto")
|
|
185
|
+
|
|
186
|
+
def generate_kem_keypair(self) -> Tuple[bytes, bytes]:
|
|
187
|
+
public_key, secret_key = self.kyber_keygen()
|
|
188
|
+
return public_key, secret_key
|
|
189
|
+
|
|
190
|
+
def generate_signing_keypair(self) -> Tuple[bytes, bytes]:
|
|
191
|
+
public_key, secret_key = self.dilithium_keygen()
|
|
192
|
+
return public_key, secret_key
|
|
193
|
+
|
|
194
|
+
def establish_session(self, peer_public_key: bytes) -> Tuple[bytes, bytes]:
|
|
195
|
+
ciphertext, shared_secret = self.kyber_encrypt(peer_public_key)
|
|
196
|
+
enc_key = hkdf_sha256(shared_secret, self.key_size // 8, b"NEXUS-ENC")
|
|
197
|
+
self.cipher = NEXUSCipher(enc_key, self.key_size)
|
|
198
|
+
return ciphertext, shared_secret
|
|
199
|
+
|
|
200
|
+
def accept_session(self, ciphertext: bytes, secret_key: bytes) -> bytes:
|
|
201
|
+
shared_secret = self.kyber_decrypt(secret_key, ciphertext)
|
|
202
|
+
enc_key = hkdf_sha256(shared_secret, self.key_size // 8, b"NEXUS-ENC")
|
|
203
|
+
self.cipher = NEXUSCipher(enc_key, self.key_size)
|
|
204
|
+
return shared_secret
|
|
205
|
+
|
|
206
|
+
def hash(self, data: bytes) -> bytes:
|
|
207
|
+
return hashlib.sha256(data).digest()
|
|
208
|
+
|
|
209
|
+
def encrypt_and_sign(self, plaintext: bytes, signing_key: bytes) -> dict:
|
|
210
|
+
if self.cipher is None:
|
|
211
|
+
raise NEXUSError("Session not established. Call establish_session first.")
|
|
212
|
+
|
|
213
|
+
ciphertext, nonce = self.cipher.encrypt(plaintext)
|
|
214
|
+
digest = self.hash(ciphertext)
|
|
215
|
+
signature = self.dilithium_sign(signing_key, digest)
|
|
216
|
+
|
|
217
|
+
return {'ciphertext': ciphertext, 'nonce': nonce, 'signature': signature}
|
|
218
|
+
|
|
219
|
+
def verify_and_decrypt(self, package: dict, verify_key: bytes) -> bytes:
|
|
220
|
+
if self.cipher is None:
|
|
221
|
+
raise NEXUSError("Session not established.")
|
|
222
|
+
|
|
223
|
+
digest = self.hash(package['ciphertext'])
|
|
224
|
+
try:
|
|
225
|
+
is_valid = self.dilithium_verify(verify_key, digest, package['signature'])
|
|
226
|
+
if not is_valid:
|
|
227
|
+
raise SignatureVerificationError("Signature verification failed")
|
|
228
|
+
except Exception as e:
|
|
229
|
+
raise SignatureVerificationError(f"Signature verification failed: {e}")
|
|
230
|
+
|
|
231
|
+
plaintext = self.cipher.decrypt(package['ciphertext'], package['nonce'])
|
|
232
|
+
return plaintext
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from typing import Tuple
|
|
2
|
+
class MLKEM:
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def __init__(self):
|
|
6
|
+
try:
|
|
7
|
+
from pqcrypto.kem.kyber768 import (
|
|
8
|
+
generate_keypair,
|
|
9
|
+
encrypt,
|
|
10
|
+
decrypt
|
|
11
|
+
)
|
|
12
|
+
self.generate_keypair = generate_keypair
|
|
13
|
+
self.encrypt = encrypt
|
|
14
|
+
self.decrypt = decrypt
|
|
15
|
+
except ImportError:
|
|
16
|
+
raise ImportError("pqcrypto not installed")
|
|
17
|
+
|
|
18
|
+
def keygen(self) -> Tuple[bytes, bytes]:
|
|
19
|
+
return self.generate_keypair()
|
|
20
|
+
|
|
21
|
+
def encapsulate(self, public_key: bytes) -> Tuple[bytes, bytes]:
|
|
22
|
+
return self.encrypt(public_key)
|
|
23
|
+
|
|
24
|
+
def decapsulate(self, ciphertext: bytes, secret_key: bytes) -> bytes:
|
|
25
|
+
return self.decrypt(ciphertext, secret_key)
|