securevault-pqc 0.1.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.
- securevault_pqc-0.1.0/PKG-INFO +110 -0
- securevault_pqc-0.1.0/README.md +75 -0
- securevault_pqc-0.1.0/securevault.py +388 -0
- securevault_pqc-0.1.0/securevault_pqc.egg-info/PKG-INFO +110 -0
- securevault_pqc-0.1.0/securevault_pqc.egg-info/SOURCES.txt +9 -0
- securevault_pqc-0.1.0/securevault_pqc.egg-info/dependency_links.txt +1 -0
- securevault_pqc-0.1.0/securevault_pqc.egg-info/entry_points.txt +2 -0
- securevault_pqc-0.1.0/securevault_pqc.egg-info/requires.txt +3 -0
- securevault_pqc-0.1.0/securevault_pqc.egg-info/top_level.txt +1 -0
- securevault_pqc-0.1.0/setup.cfg +4 -0
- securevault_pqc-0.1.0/setup.py +43 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: securevault-pqc
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Post-quantum file encryption using ML-KEM-768 + X25519
|
|
5
|
+
Home-page: https://github.com/yourusername/securevault
|
|
6
|
+
Author: Reggy Mane
|
|
7
|
+
Author-email: your.email@example.com
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/yourusername/securevault/issues
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Intended Audience :: Information Technology
|
|
12
|
+
Classifier: Topic :: Security :: Cryptography
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Requires-Dist: liboqs-python>=0.8.0
|
|
23
|
+
Requires-Dist: cryptography>=41.0.0
|
|
24
|
+
Requires-Dist: click>=8.1.0
|
|
25
|
+
Dynamic: author
|
|
26
|
+
Dynamic: author-email
|
|
27
|
+
Dynamic: classifier
|
|
28
|
+
Dynamic: description
|
|
29
|
+
Dynamic: description-content-type
|
|
30
|
+
Dynamic: home-page
|
|
31
|
+
Dynamic: project-url
|
|
32
|
+
Dynamic: requires-dist
|
|
33
|
+
Dynamic: requires-python
|
|
34
|
+
Dynamic: summary
|
|
35
|
+
|
|
36
|
+
# SecureVault - Post-Quantum File Encryption
|
|
37
|
+
***Protect your files from future quantum computers.***
|
|
38
|
+
##
|
|
39
|
+
|
|
40
|
+

|
|
41
|
+

|
|
42
|
+

|
|
43
|
+

|
|
44
|
+

|
|
45
|
+

|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
## The Problem
|
|
49
|
+
Quantum computers will break RSA/ECC encryption within 10-20 years.
|
|
50
|
+
Attackers are stealing encrypted data NOW to decrypt LATER.
|
|
51
|
+
|
|
52
|
+
This is what we called "**harvest now, decrypt later**" attacks.
|
|
53
|
+
|
|
54
|
+
## The Solution
|
|
55
|
+
SecureVault uses ML-KEM-768 (NIST standardized post-quantum crypto)
|
|
56
|
+
in hybrid mode with X25519 for maximum security. Even if quantum computers break one layer, your data stays protected.
|
|
57
|
+
|
|
58
|
+
## Features
|
|
59
|
+
|
|
60
|
+
- **Hybrid encryption**: X25519 + ML-KEM-768 (defense in depth²)
|
|
61
|
+
- **Password-protected keys**: PBKDF2 key derivation (100k iterations)
|
|
62
|
+
- **Cross-platform**: Works on Linux, macOS, and Windows
|
|
63
|
+
- **Future-proof**: Quantum-resistant for 30+ years
|
|
64
|
+
- **Easy CLI**: Simple commands, no crypto knowledge needed
|
|
65
|
+
- **Educational**: Shows security info about your files
|
|
66
|
+
|
|
67
|
+
## Installation
|
|
68
|
+
|
|
69
|
+
### Prerequisites
|
|
70
|
+
- Python 3.9 or higher
|
|
71
|
+
- pip
|
|
72
|
+
|
|
73
|
+
### Setup
|
|
74
|
+
```bash
|
|
75
|
+
# Clone the repo
|
|
76
|
+
git clone https://github.com/yourusername/securevault.git
|
|
77
|
+
cd securevault
|
|
78
|
+
|
|
79
|
+
# Install dependencies
|
|
80
|
+
pip install -r requirements.txt
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Quick Start
|
|
84
|
+
```bash
|
|
85
|
+
# 1. Generate your keypair
|
|
86
|
+
python3 secure_vault.py keygen --password mypassword --output my_keys.key
|
|
87
|
+
# Creates: my_keys.key (private) and my_keys_public.key (public)
|
|
88
|
+
|
|
89
|
+
# 2. Encrypt a file
|
|
90
|
+
echo "Secret data" > secret.txt
|
|
91
|
+
python3 secure_vault.py encrypt secret.txt my_keys_public.key
|
|
92
|
+
|
|
93
|
+
# 3. Check security info
|
|
94
|
+
python3 secure_vault.py info secret.txt.vault
|
|
95
|
+
|
|
96
|
+
# 4. Decrypt it
|
|
97
|
+
python3 secure_vault.py decrypt secret.txt.vault my_keys.key --password mypassword
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Technical Details
|
|
101
|
+
- Hybrid: X25519 + ML-KEM-768
|
|
102
|
+
- File encryption: AES-256-GCM (Fernet)
|
|
103
|
+
- Key derivation: PBKDF2
|
|
104
|
+
|
|
105
|
+
## Performance
|
|
106
|
+
[Benchmark results]
|
|
107
|
+
|
|
108
|
+
## Why This Matters
|
|
109
|
+
[Quantum threat timeline]
|
|
110
|
+
[Use cases]
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# SecureVault - Post-Quantum File Encryption
|
|
2
|
+
***Protect your files from future quantum computers.***
|
|
3
|
+
##
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## The Problem
|
|
14
|
+
Quantum computers will break RSA/ECC encryption within 10-20 years.
|
|
15
|
+
Attackers are stealing encrypted data NOW to decrypt LATER.
|
|
16
|
+
|
|
17
|
+
This is what we called "**harvest now, decrypt later**" attacks.
|
|
18
|
+
|
|
19
|
+
## The Solution
|
|
20
|
+
SecureVault uses ML-KEM-768 (NIST standardized post-quantum crypto)
|
|
21
|
+
in hybrid mode with X25519 for maximum security. Even if quantum computers break one layer, your data stays protected.
|
|
22
|
+
|
|
23
|
+
## Features
|
|
24
|
+
|
|
25
|
+
- **Hybrid encryption**: X25519 + ML-KEM-768 (defense in depth²)
|
|
26
|
+
- **Password-protected keys**: PBKDF2 key derivation (100k iterations)
|
|
27
|
+
- **Cross-platform**: Works on Linux, macOS, and Windows
|
|
28
|
+
- **Future-proof**: Quantum-resistant for 30+ years
|
|
29
|
+
- **Easy CLI**: Simple commands, no crypto knowledge needed
|
|
30
|
+
- **Educational**: Shows security info about your files
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
### Prerequisites
|
|
35
|
+
- Python 3.9 or higher
|
|
36
|
+
- pip
|
|
37
|
+
|
|
38
|
+
### Setup
|
|
39
|
+
```bash
|
|
40
|
+
# Clone the repo
|
|
41
|
+
git clone https://github.com/yourusername/securevault.git
|
|
42
|
+
cd securevault
|
|
43
|
+
|
|
44
|
+
# Install dependencies
|
|
45
|
+
pip install -r requirements.txt
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Quick Start
|
|
49
|
+
```bash
|
|
50
|
+
# 1. Generate your keypair
|
|
51
|
+
python3 secure_vault.py keygen --password mypassword --output my_keys.key
|
|
52
|
+
# Creates: my_keys.key (private) and my_keys_public.key (public)
|
|
53
|
+
|
|
54
|
+
# 2. Encrypt a file
|
|
55
|
+
echo "Secret data" > secret.txt
|
|
56
|
+
python3 secure_vault.py encrypt secret.txt my_keys_public.key
|
|
57
|
+
|
|
58
|
+
# 3. Check security info
|
|
59
|
+
python3 secure_vault.py info secret.txt.vault
|
|
60
|
+
|
|
61
|
+
# 4. Decrypt it
|
|
62
|
+
python3 secure_vault.py decrypt secret.txt.vault my_keys.key --password mypassword
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Technical Details
|
|
66
|
+
- Hybrid: X25519 + ML-KEM-768
|
|
67
|
+
- File encryption: AES-256-GCM (Fernet)
|
|
68
|
+
- Key derivation: PBKDF2
|
|
69
|
+
|
|
70
|
+
## Performance
|
|
71
|
+
[Benchmark results]
|
|
72
|
+
|
|
73
|
+
## Why This Matters
|
|
74
|
+
[Quantum threat timeline]
|
|
75
|
+
[Use cases]
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import oqs
|
|
2
|
+
from cryptography.hazmat.primitives.asymmetric import x25519
|
|
3
|
+
from cryptography.hazmat.primitives import serialization, hashes
|
|
4
|
+
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
|
5
|
+
from cryptography.fernet import Fernet
|
|
6
|
+
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
7
|
+
import click
|
|
8
|
+
import os
|
|
9
|
+
import json
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
import base64
|
|
12
|
+
|
|
13
|
+
def generate_hybrid_keypair():
|
|
14
|
+
# Generate X25519 key pair
|
|
15
|
+
private_key = x25519.X25519PrivateKey.generate()
|
|
16
|
+
public_key = private_key.public_key()
|
|
17
|
+
|
|
18
|
+
# Generate PQC key pair using oqs
|
|
19
|
+
kem = oqs.KeyEncapsulation('ML-KEM-768')
|
|
20
|
+
pqc_public_key = kem.generate_keypair()
|
|
21
|
+
pqc_secret_key = kem.export_secret_key()
|
|
22
|
+
|
|
23
|
+
# serialize keys
|
|
24
|
+
private_bytes = private_key.private_bytes(
|
|
25
|
+
encoding=serialization.Encoding.Raw,
|
|
26
|
+
format=serialization.PrivateFormat.Raw,
|
|
27
|
+
encryption_algorithm=serialization.NoEncryption()
|
|
28
|
+
)
|
|
29
|
+
public_bytes = public_key.public_bytes(
|
|
30
|
+
encoding=serialization.Encoding.Raw,
|
|
31
|
+
format=serialization.PublicFormat.Raw
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# bundle keys together
|
|
36
|
+
return {
|
|
37
|
+
'private_key': private_bytes,
|
|
38
|
+
'public_key': public_bytes,
|
|
39
|
+
'pqc_private_key': pqc_secret_key,
|
|
40
|
+
'pqc_public_key': pqc_public_key,
|
|
41
|
+
'algorithm': 'X25519+ML-KEM-768'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
def save_keypair_to_file(keypair, filepath, password=None):
|
|
45
|
+
# Serialize keypair to JSON
|
|
46
|
+
keypair_serialized= {
|
|
47
|
+
'private_key': base64.b64encode(keypair['private_key']).decode('utf-8'),
|
|
48
|
+
'public_key': base64.b64encode(keypair['public_key']).decode('utf-8'),
|
|
49
|
+
'pqc_private_key': base64.b64encode(keypair['pqc_private_key']).decode('utf-8'),
|
|
50
|
+
'pqc_public_key': base64.b64encode(keypair['pqc_public_key']).decode('utf-8'),
|
|
51
|
+
'algorithm': keypair['algorithm']
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
keypair_json = json.dumps(keypair_serialized).encode('utf-8')
|
|
55
|
+
|
|
56
|
+
if password:
|
|
57
|
+
salt = os.urandom(16)
|
|
58
|
+
kdf = PBKDF2HMAC(
|
|
59
|
+
algorithm=hashes.SHA256(),
|
|
60
|
+
length=32,
|
|
61
|
+
salt=salt,
|
|
62
|
+
iterations=100000,
|
|
63
|
+
)
|
|
64
|
+
key = base64.urlsafe_b64encode(kdf.derive(password.encode()))
|
|
65
|
+
fernet = Fernet(key)
|
|
66
|
+
encrypted_dt = fernet.encrypt(keypair_json)
|
|
67
|
+
with open(filepath, 'wb') as f:
|
|
68
|
+
f.write(salt + encrypted_dt)
|
|
69
|
+
|
|
70
|
+
else:
|
|
71
|
+
with open(filepath, 'wb') as f:
|
|
72
|
+
f.write(keypair_json)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def load_keypair_from_file(filepath, password=None):
|
|
79
|
+
with open(filepath, 'rb') as f:
|
|
80
|
+
data = f.read()
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
#if encrypted
|
|
84
|
+
if password:
|
|
85
|
+
#derive key from password
|
|
86
|
+
salt = data[:16]
|
|
87
|
+
encrypted_dt = data[16:]
|
|
88
|
+
kdf = PBKDF2HMAC(
|
|
89
|
+
algorithm=hashes.SHA256(),
|
|
90
|
+
length=32,
|
|
91
|
+
salt=salt,
|
|
92
|
+
iterations=100000,
|
|
93
|
+
)
|
|
94
|
+
key = base64.urlsafe_b64encode(kdf.derive(password.encode()))
|
|
95
|
+
fernet = Fernet(key)
|
|
96
|
+
decrypted_dt = fernet.decrypt(encrypted_dt)
|
|
97
|
+
keypair_json = decrypted_dt
|
|
98
|
+
else:
|
|
99
|
+
keypair_json = data
|
|
100
|
+
keypair_dict = json.loads(keypair_json)
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
'private_key': base64.b64decode(keypair_dict['private_key']),
|
|
104
|
+
'public_key': base64.b64decode(keypair_dict['public_key']),
|
|
105
|
+
'pqc_private_key': base64.b64decode(keypair_dict['pqc_private_key']),
|
|
106
|
+
'pqc_public_key': base64.b64decode(keypair_dict['pqc_public_key']),
|
|
107
|
+
'algorithm': keypair_dict['algorithm']
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
#yooo this is a lot harder than i thought lol
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# def hybrid_encrypt_file(input_file, output_file, recipient_public_keys):
|
|
115
|
+
|
|
116
|
+
# #generate random file encryption key(32 bytes for AES-256)
|
|
117
|
+
# file_key = os.urandom(32)
|
|
118
|
+
|
|
119
|
+
# #encrypt file with AES (using Fernet)
|
|
120
|
+
# fernet = Fernet(base64.urlsafe_b64encode(file_key))
|
|
121
|
+
# with open(input_file, 'rb') as f:
|
|
122
|
+
# data = f.read()
|
|
123
|
+
|
|
124
|
+
# encrypted_dt = fernet.encrypt(data)
|
|
125
|
+
# #wrap file_key with classical crypto (x25519)
|
|
126
|
+
# private_key = x25519.X25519PrivateKey.generate()
|
|
127
|
+
# peer_public_key = x25519.X25519PublicKey.from_public_bytes(recipient_public_keys['public_key'])
|
|
128
|
+
# share_secret_classical = private_key.exchange(peer_public_key)
|
|
129
|
+
|
|
130
|
+
# # Create AESGCM instance with the shared secret
|
|
131
|
+
# #yo this was so hard to find an dunderstand
|
|
132
|
+
# aesgcm = AESGCM(share_secret_classical)
|
|
133
|
+
# classical_wrappped_key = aesgcm.encrypt(os.urandom(12), file_key, None)
|
|
134
|
+
|
|
135
|
+
# #wrap file_key with PQC crypto(ML-KEM-768)
|
|
136
|
+
# kem = oqs.KeyEncapsulation('ML-KEM-768')
|
|
137
|
+
# kem.import_public_key(recipient_public_keys['pqc_public_key'])
|
|
138
|
+
# pqc_ciphertext, pqc_shared_secret = kem.encap_secret(recipient_public_keys['pqc_public_key'])
|
|
139
|
+
|
|
140
|
+
# aesgcm_pqc = AESGCM(pqc_shared_secret)
|
|
141
|
+
# pqc_wrapped_key = aesgcm_pqc.encrypt(os.urandom(12), file_key, None)
|
|
142
|
+
|
|
143
|
+
# # package the whole thing into output file
|
|
144
|
+
# with open(output_file, 'wb') as f:
|
|
145
|
+
# f.write(encrypted_dt)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# return {
|
|
149
|
+
# 'encrypted_data': encrypted_dt.decode('utf-8'),
|
|
150
|
+
# 'classical_wrapped_key': classical_wrappped_key.decode('utf-8'),
|
|
151
|
+
# 'pqc_wrapped_key': pqc_wrapped_key.decode('utf-8'),
|
|
152
|
+
# 'pqc_ciphertext': pqc_ciphertext.decode('utf-8'),
|
|
153
|
+
# 'algorithm': 'hybrid-x25519-mlkem768'
|
|
154
|
+
# }
|
|
155
|
+
|
|
156
|
+
def hybrid_encrypt_file(input_file, output_file, recipient_public_keys):
|
|
157
|
+
"""
|
|
158
|
+
Encrypt file using recipient's public keys with hybrid classical + PQC
|
|
159
|
+
"""
|
|
160
|
+
# Step 1: Read the file
|
|
161
|
+
with open(input_file, 'rb') as f:
|
|
162
|
+
file_data = f.read()
|
|
163
|
+
|
|
164
|
+
# Step 2: Generate random symmetric key for file encryption
|
|
165
|
+
file_key = Fernet.generate_key()
|
|
166
|
+
|
|
167
|
+
# Step 3: Encrypt the file with symmetric key
|
|
168
|
+
fernet = Fernet(file_key)
|
|
169
|
+
encrypted_data = fernet.encrypt(file_data)
|
|
170
|
+
|
|
171
|
+
# Step 4: Wrap file_key with CLASSICAL crypto (X25519 + AES-GCM)
|
|
172
|
+
ephemeral_private = x25519.X25519PrivateKey.generate()
|
|
173
|
+
ephemeral_public = ephemeral_private.public_key()
|
|
174
|
+
|
|
175
|
+
recipient_public_classical = x25519.X25519PublicKey.from_public_bytes(
|
|
176
|
+
recipient_public_keys['classical_public_key']
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
shared_secret_classical = ephemeral_private.exchange(recipient_public_classical)
|
|
180
|
+
|
|
181
|
+
aesgcm = AESGCM(shared_secret_classical)
|
|
182
|
+
nonce_classical = os.urandom(12)
|
|
183
|
+
|
|
184
|
+
# Decode Fernet key to get raw 32 bytes
|
|
185
|
+
file_key_bytes = base64.urlsafe_b64decode(file_key)
|
|
186
|
+
classical_wrapped_key = aesgcm.encrypt(nonce_classical, file_key_bytes, None)
|
|
187
|
+
|
|
188
|
+
# Step 5: Wrap file_key with POST-QUANTUM crypto (ML-KEM-768)
|
|
189
|
+
kem = oqs.KeyEncapsulation('ML-KEM-768')
|
|
190
|
+
pqc_ciphertext, shared_secret_pqc = kem.encap_secret(recipient_public_keys['pqc_public_key'])
|
|
191
|
+
|
|
192
|
+
aesgcm_pqc = AESGCM(shared_secret_pqc)
|
|
193
|
+
nonce_pqc = os.urandom(12)
|
|
194
|
+
pqc_wrapped_key = aesgcm_pqc.encrypt(nonce_pqc, file_key_bytes, None)
|
|
195
|
+
|
|
196
|
+
# Step 6: Create encrypted package with all components
|
|
197
|
+
package = {
|
|
198
|
+
'encrypted_data': base64.b64encode(encrypted_data).decode('utf-8'),
|
|
199
|
+
'classical_wrapped_key': base64.b64encode(classical_wrapped_key).decode('utf-8'),
|
|
200
|
+
'pqc_wrapped_key': base64.b64encode(pqc_wrapped_key).decode('utf-8'),
|
|
201
|
+
'pqc_ciphertext': base64.b64encode(pqc_ciphertext).decode('utf-8'),
|
|
202
|
+
'ephemeral_public_key': base64.b64encode(
|
|
203
|
+
ephemeral_public.public_bytes(
|
|
204
|
+
encoding=serialization.Encoding.Raw,
|
|
205
|
+
format=serialization.PublicFormat.Raw
|
|
206
|
+
)
|
|
207
|
+
).decode('utf-8'),
|
|
208
|
+
'nonce_classical': base64.b64encode(nonce_classical).decode('utf-8'),
|
|
209
|
+
'nonce_pqc': base64.b64encode(nonce_pqc).decode('utf-8'),
|
|
210
|
+
'algorithm': 'hybrid-x25519-mlkem768'
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
# Step 7: Write to output file as JSON
|
|
214
|
+
with open(output_file, 'w') as f:
|
|
215
|
+
json.dump(package, f, indent=2)
|
|
216
|
+
|
|
217
|
+
return package
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def hybrid_decrypt_file(input_file, output_file, private_keys):
|
|
221
|
+
"""
|
|
222
|
+
Decrypt file using your private keys
|
|
223
|
+
"""
|
|
224
|
+
# Step 1: Load encrypted package from file
|
|
225
|
+
with open(input_file, 'r') as f:
|
|
226
|
+
package = json.load(f)
|
|
227
|
+
|
|
228
|
+
# Step 2: Base64 decode all the fields
|
|
229
|
+
encrypted_data = base64.b64decode(package['encrypted_data'])
|
|
230
|
+
classical_wrapped_key = base64.b64decode(package['classical_wrapped_key'])
|
|
231
|
+
pqc_wrapped_key = base64.b64decode(package['pqc_wrapped_key'])
|
|
232
|
+
pqc_ciphertext = base64.b64decode(package['pqc_ciphertext'])
|
|
233
|
+
ephemeral_public_bytes = base64.b64decode(package['ephemeral_public_key'])
|
|
234
|
+
nonce_classical = base64.b64decode(package['nonce_classical'])
|
|
235
|
+
nonce_pqc = base64.b64decode(package['nonce_pqc'])
|
|
236
|
+
|
|
237
|
+
# Step 3: Unwrap file_key using CLASSICAL crypto
|
|
238
|
+
private_key = x25519.X25519PrivateKey.from_private_bytes(
|
|
239
|
+
private_keys['private_key']
|
|
240
|
+
)
|
|
241
|
+
ephemeral_public = x25519.X25519PublicKey.from_public_bytes(ephemeral_public_bytes)
|
|
242
|
+
|
|
243
|
+
shared_secret_classical = private_key.exchange(ephemeral_public)
|
|
244
|
+
|
|
245
|
+
aesgcm = AESGCM(shared_secret_classical)
|
|
246
|
+
file_key_classical = aesgcm.decrypt(nonce_classical, classical_wrapped_key, None)
|
|
247
|
+
|
|
248
|
+
# Step 4: Unwrap file_key using POST-QUANTUM crypto
|
|
249
|
+
kem = oqs.KeyEncapsulation('ML-KEM-768', private_keys['pqc_private_key'])
|
|
250
|
+
|
|
251
|
+
shared_secret_pqc = kem.decap_secret(pqc_ciphertext)
|
|
252
|
+
|
|
253
|
+
aesgcm_pqc = AESGCM(shared_secret_pqc)
|
|
254
|
+
file_key_pqc = aesgcm_pqc.decrypt(nonce_pqc, pqc_wrapped_key, None)
|
|
255
|
+
|
|
256
|
+
# Step 5: VERIFY both unwraps produced same file_key
|
|
257
|
+
if file_key_classical != file_key_pqc:
|
|
258
|
+
raise ValueError("Security Error: Key mismatch between classical and PQC!")
|
|
259
|
+
|
|
260
|
+
# Step 6: Decrypt file with file_key
|
|
261
|
+
fernet = Fernet(base64.urlsafe_b64encode(file_key_classical))
|
|
262
|
+
decrypted_data = fernet.decrypt(encrypted_data)
|
|
263
|
+
|
|
264
|
+
# Step 7: Write to output file
|
|
265
|
+
with open(output_file, 'wb') as f:
|
|
266
|
+
f.write(decrypted_data)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
# ==================== CLI COMMANDS ====================
|
|
270
|
+
|
|
271
|
+
@click.group()
|
|
272
|
+
def cli():
|
|
273
|
+
"""SecureVault - Post-Quantum File Encryption"""
|
|
274
|
+
pass
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
@cli.command()
|
|
278
|
+
@click.option('--password', prompt=True, hide_input=True, confirmation_prompt=True,
|
|
279
|
+
help='Password to protect private key')
|
|
280
|
+
@click.option('--output', default='keypair.key', help='Output file for keypair')
|
|
281
|
+
def keygen(password, output):
|
|
282
|
+
"""
|
|
283
|
+
Generate new hybrid keypair
|
|
284
|
+
"""
|
|
285
|
+
keypair = generate_hybrid_keypair()
|
|
286
|
+
|
|
287
|
+
# Save private keypair (password protected)
|
|
288
|
+
save_keypair_to_file(keypair, output, password)
|
|
289
|
+
|
|
290
|
+
# Save public keys separately (NO password - anyone can use!)
|
|
291
|
+
public_key_file = output.replace('.key', '_public.key')
|
|
292
|
+
public_only = {
|
|
293
|
+
'private_key': b'', # Empty, not needed for public key file
|
|
294
|
+
'public_key': keypair['public_key'],
|
|
295
|
+
'pqc_private_key': b'', # Empty, not needed
|
|
296
|
+
'pqc_public_key': keypair['pqc_public_key'],
|
|
297
|
+
'algorithm': keypair['algorithm']
|
|
298
|
+
}
|
|
299
|
+
save_keypair_to_file(public_only, public_key_file, password=None)
|
|
300
|
+
|
|
301
|
+
click.echo(click.style(f"✓ Private keypair saved to {output}", fg='green'))
|
|
302
|
+
click.echo(click.style(f"✓ Public keys saved to {public_key_file}", fg='green'))
|
|
303
|
+
click.echo(f"Algorithm: {keypair['algorithm']}")
|
|
304
|
+
click.echo(click.style("🔒 Classical: X25519", fg='cyan'))
|
|
305
|
+
click.echo(click.style("🔐 Post-Quantum: ML-KEM-768", fg='magenta'))
|
|
306
|
+
click.echo(click.style(f"\n📤 Share {public_key_file} with others!", fg='yellow'))
|
|
307
|
+
|
|
308
|
+
@cli.command()
|
|
309
|
+
@click.argument('input_file', type=click.Path(exists=True))
|
|
310
|
+
@click.argument('recipient_key', type=click.Path(exists=True))
|
|
311
|
+
@click.option('--output', help='Output encrypted file (default: input_file.vault)')
|
|
312
|
+
def encrypt(input_file, recipient_key, output): # ← REMOVED password parameter
|
|
313
|
+
"""
|
|
314
|
+
Encrypt a file for a recipient using their PUBLIC keys (no password needed)
|
|
315
|
+
"""
|
|
316
|
+
if output is None:
|
|
317
|
+
output = input_file + '.vault'
|
|
318
|
+
|
|
319
|
+
# Load recipient's keys WITHOUT password (we only need public keys!)
|
|
320
|
+
try:
|
|
321
|
+
recipient_keys = load_keypair_from_file(recipient_key, password=None)
|
|
322
|
+
except Exception as e:
|
|
323
|
+
click.echo(click.style("✗ Error: Could not load recipient's public key", fg='red'), err=True)
|
|
324
|
+
click.echo(click.style(" Make sure the key file is not password-protected, or use the public key file", fg='yellow'), err=True)
|
|
325
|
+
raise
|
|
326
|
+
|
|
327
|
+
recipient_public_keys = {
|
|
328
|
+
'classical_public_key': recipient_keys['public_key'],
|
|
329
|
+
'pqc_public_key': recipient_keys['pqc_public_key']
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
hybrid_encrypt_file(input_file, output, recipient_public_keys)
|
|
333
|
+
|
|
334
|
+
click.echo(click.style(f"✓ Encrypted {input_file} → {output}", fg='green'))
|
|
335
|
+
click.echo(click.style("🔒 Protected by classical AND post-quantum cryptography", fg='cyan'))
|
|
336
|
+
click.echo(click.style("🛡️ Quantum-resistant until 2050+", fg='magenta'))
|
|
337
|
+
|
|
338
|
+
@cli.command()
|
|
339
|
+
@click.argument('input_file', type=click.Path(exists=True))
|
|
340
|
+
@click.argument('private_key_file', type=click.Path(exists=True))
|
|
341
|
+
@click.option('--password', prompt=True, hide_input=True, help='Private key password')
|
|
342
|
+
@click.option('--output', help='Output decrypted file')
|
|
343
|
+
def decrypt(input_file, private_key_file, password, output):
|
|
344
|
+
"""
|
|
345
|
+
Decrypt a file using your private key
|
|
346
|
+
"""
|
|
347
|
+
private_keys = load_keypair_from_file(private_key_file, password)
|
|
348
|
+
|
|
349
|
+
if output is None:
|
|
350
|
+
output = input_file.replace('.vault', '')
|
|
351
|
+
if output == input_file: # No .vault extension found
|
|
352
|
+
output = input_file + '.decrypted'
|
|
353
|
+
|
|
354
|
+
hybrid_decrypt_file(input_file, output, private_keys)
|
|
355
|
+
|
|
356
|
+
click.echo(click.style(f"✓ Decrypted {input_file} → {output}", fg='green'))
|
|
357
|
+
click.echo(click.style("🔓 Verified with both classical and PQC keys", fg='cyan'))
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
@cli.command()
|
|
361
|
+
@click.argument('encrypted_file', type=click.Path(exists=True))
|
|
362
|
+
def info(encrypted_file):
|
|
363
|
+
"""
|
|
364
|
+
Show security information about encrypted file
|
|
365
|
+
"""
|
|
366
|
+
with open(encrypted_file, 'r') as f:
|
|
367
|
+
package = json.load(f)
|
|
368
|
+
|
|
369
|
+
click.echo(click.style("\n🔐 SecureVault Encrypted File Info", fg='cyan', bold=True))
|
|
370
|
+
click.echo("─" * 50)
|
|
371
|
+
click.echo(f"Algorithm: {click.style(package['algorithm'], fg='yellow')}")
|
|
372
|
+
click.echo(f"Classical protection: {click.style('✓ X25519', fg='green')}")
|
|
373
|
+
click.echo(f"Post-quantum protection: {click.style('✓ ML-KEM-768', fg='green')}")
|
|
374
|
+
click.echo(f"Quantum resistant: {click.style('YES', fg='green', bold=True)}")
|
|
375
|
+
click.echo(f"Safe until: {click.style('2050+ (estimated)', fg='magenta')}")
|
|
376
|
+
|
|
377
|
+
# File size info
|
|
378
|
+
encrypted_size = len(base64.b64decode(package['encrypted_data']))
|
|
379
|
+
click.echo(f"\nEncrypted data size: {click.style(f'{encrypted_size} bytes', fg='white')}")
|
|
380
|
+
click.echo("─" * 50)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
if __name__ == '__main__':
|
|
384
|
+
cli()
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: securevault-pqc
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Post-quantum file encryption using ML-KEM-768 + X25519
|
|
5
|
+
Home-page: https://github.com/yourusername/securevault
|
|
6
|
+
Author: Reggy Mane
|
|
7
|
+
Author-email: your.email@example.com
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/yourusername/securevault/issues
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Intended Audience :: Information Technology
|
|
12
|
+
Classifier: Topic :: Security :: Cryptography
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Requires-Dist: liboqs-python>=0.8.0
|
|
23
|
+
Requires-Dist: cryptography>=41.0.0
|
|
24
|
+
Requires-Dist: click>=8.1.0
|
|
25
|
+
Dynamic: author
|
|
26
|
+
Dynamic: author-email
|
|
27
|
+
Dynamic: classifier
|
|
28
|
+
Dynamic: description
|
|
29
|
+
Dynamic: description-content-type
|
|
30
|
+
Dynamic: home-page
|
|
31
|
+
Dynamic: project-url
|
|
32
|
+
Dynamic: requires-dist
|
|
33
|
+
Dynamic: requires-python
|
|
34
|
+
Dynamic: summary
|
|
35
|
+
|
|
36
|
+
# SecureVault - Post-Quantum File Encryption
|
|
37
|
+
***Protect your files from future quantum computers.***
|
|
38
|
+
##
|
|
39
|
+
|
|
40
|
+

|
|
41
|
+

|
|
42
|
+

|
|
43
|
+

|
|
44
|
+

|
|
45
|
+

|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
## The Problem
|
|
49
|
+
Quantum computers will break RSA/ECC encryption within 10-20 years.
|
|
50
|
+
Attackers are stealing encrypted data NOW to decrypt LATER.
|
|
51
|
+
|
|
52
|
+
This is what we called "**harvest now, decrypt later**" attacks.
|
|
53
|
+
|
|
54
|
+
## The Solution
|
|
55
|
+
SecureVault uses ML-KEM-768 (NIST standardized post-quantum crypto)
|
|
56
|
+
in hybrid mode with X25519 for maximum security. Even if quantum computers break one layer, your data stays protected.
|
|
57
|
+
|
|
58
|
+
## Features
|
|
59
|
+
|
|
60
|
+
- **Hybrid encryption**: X25519 + ML-KEM-768 (defense in depth²)
|
|
61
|
+
- **Password-protected keys**: PBKDF2 key derivation (100k iterations)
|
|
62
|
+
- **Cross-platform**: Works on Linux, macOS, and Windows
|
|
63
|
+
- **Future-proof**: Quantum-resistant for 30+ years
|
|
64
|
+
- **Easy CLI**: Simple commands, no crypto knowledge needed
|
|
65
|
+
- **Educational**: Shows security info about your files
|
|
66
|
+
|
|
67
|
+
## Installation
|
|
68
|
+
|
|
69
|
+
### Prerequisites
|
|
70
|
+
- Python 3.9 or higher
|
|
71
|
+
- pip
|
|
72
|
+
|
|
73
|
+
### Setup
|
|
74
|
+
```bash
|
|
75
|
+
# Clone the repo
|
|
76
|
+
git clone https://github.com/yourusername/securevault.git
|
|
77
|
+
cd securevault
|
|
78
|
+
|
|
79
|
+
# Install dependencies
|
|
80
|
+
pip install -r requirements.txt
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Quick Start
|
|
84
|
+
```bash
|
|
85
|
+
# 1. Generate your keypair
|
|
86
|
+
python3 secure_vault.py keygen --password mypassword --output my_keys.key
|
|
87
|
+
# Creates: my_keys.key (private) and my_keys_public.key (public)
|
|
88
|
+
|
|
89
|
+
# 2. Encrypt a file
|
|
90
|
+
echo "Secret data" > secret.txt
|
|
91
|
+
python3 secure_vault.py encrypt secret.txt my_keys_public.key
|
|
92
|
+
|
|
93
|
+
# 3. Check security info
|
|
94
|
+
python3 secure_vault.py info secret.txt.vault
|
|
95
|
+
|
|
96
|
+
# 4. Decrypt it
|
|
97
|
+
python3 secure_vault.py decrypt secret.txt.vault my_keys.key --password mypassword
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Technical Details
|
|
101
|
+
- Hybrid: X25519 + ML-KEM-768
|
|
102
|
+
- File encryption: AES-256-GCM (Fernet)
|
|
103
|
+
- Key derivation: PBKDF2
|
|
104
|
+
|
|
105
|
+
## Performance
|
|
106
|
+
[Benchmark results]
|
|
107
|
+
|
|
108
|
+
## Why This Matters
|
|
109
|
+
[Quantum threat timeline]
|
|
110
|
+
[Use cases]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
securevault.py
|
|
3
|
+
setup.py
|
|
4
|
+
securevault_pqc.egg-info/PKG-INFO
|
|
5
|
+
securevault_pqc.egg-info/SOURCES.txt
|
|
6
|
+
securevault_pqc.egg-info/dependency_links.txt
|
|
7
|
+
securevault_pqc.egg-info/entry_points.txt
|
|
8
|
+
securevault_pqc.egg-info/requires.txt
|
|
9
|
+
securevault_pqc.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
securevault
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
with open("README.md", "r", encoding="utf-8") as fh:
|
|
4
|
+
long_description = fh.read()
|
|
5
|
+
|
|
6
|
+
setup(
|
|
7
|
+
name="securevault-pqc", # Name on PyPI (securevault might be taken)
|
|
8
|
+
version="0.1.0",
|
|
9
|
+
author="Reggy Mane",
|
|
10
|
+
author_email="your.email@example.com",
|
|
11
|
+
description="Post-quantum file encryption using ML-KEM-768 + X25519",
|
|
12
|
+
long_description=long_description,
|
|
13
|
+
long_description_content_type="text/markdown",
|
|
14
|
+
url="https://github.com/yourusername/securevault",
|
|
15
|
+
project_urls={
|
|
16
|
+
"Bug Tracker": "https://github.com/yourusername/securevault/issues",
|
|
17
|
+
},
|
|
18
|
+
classifiers=[
|
|
19
|
+
"Development Status :: 4 - Beta",
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
"Intended Audience :: Information Technology",
|
|
22
|
+
"Topic :: Security :: Cryptography",
|
|
23
|
+
"License :: OSI Approved :: MIT License",
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"Programming Language :: Python :: 3.9",
|
|
26
|
+
"Programming Language :: Python :: 3.10",
|
|
27
|
+
"Programming Language :: Python :: 3.11",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Operating System :: OS Independent",
|
|
30
|
+
],
|
|
31
|
+
py_modules=["securevault"],
|
|
32
|
+
python_requires=">=3.9",
|
|
33
|
+
install_requires=[
|
|
34
|
+
"liboqs-python>=0.8.0",
|
|
35
|
+
"cryptography>=41.0.0",
|
|
36
|
+
"click>=8.1.0",
|
|
37
|
+
],
|
|
38
|
+
entry_points={
|
|
39
|
+
"console_scripts": [
|
|
40
|
+
"securevault=securevault:cli", # Creates 'securevault' command
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
)
|