quant-bvz-secure 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.
- quant_bvz_secure-0.1.0/LICENSE +21 -0
- quant_bvz_secure-0.1.0/PKG-INFO +136 -0
- quant_bvz_secure-0.1.0/README.md +106 -0
- quant_bvz_secure-0.1.0/pyproject.toml +36 -0
- quant_bvz_secure-0.1.0/qcrypt/__init__.py +27 -0
- quant_bvz_secure-0.1.0/qcrypt/cli.py +118 -0
- quant_bvz_secure-0.1.0/qcrypt/exceptions.py +22 -0
- quant_bvz_secure-0.1.0/qcrypt/keystore.py +123 -0
- quant_bvz_secure-0.1.0/qcrypt/pqc/__init__.py +5 -0
- quant_bvz_secure-0.1.0/qcrypt/pqc/hybrid.py +155 -0
- quant_bvz_secure-0.1.0/qcrypt/pqc/kem.py +96 -0
- quant_bvz_secure-0.1.0/qcrypt/pqc/signatures.py +58 -0
- quant_bvz_secure-0.1.0/qcrypt/qkd/__init__.py +4 -0
- quant_bvz_secure-0.1.0/qcrypt/qkd/backend.py +88 -0
- quant_bvz_secure-0.1.0/qcrypt/qkd/bb84.py +160 -0
- quant_bvz_secure-0.1.0/qcrypt/qkd/channel.py +80 -0
- quant_bvz_secure-0.1.0/qcrypt/symmetric/__init__.py +3 -0
- quant_bvz_secure-0.1.0/qcrypt/symmetric/aead.py +90 -0
- quant_bvz_secure-0.1.0/quant_bvz_secure.egg-info/PKG-INFO +136 -0
- quant_bvz_secure-0.1.0/quant_bvz_secure.egg-info/SOURCES.txt +23 -0
- quant_bvz_secure-0.1.0/quant_bvz_secure.egg-info/dependency_links.txt +1 -0
- quant_bvz_secure-0.1.0/quant_bvz_secure.egg-info/requires.txt +13 -0
- quant_bvz_secure-0.1.0/quant_bvz_secure.egg-info/top_level.txt +1 -0
- quant_bvz_secure-0.1.0/setup.cfg +4 -0
- quant_bvz_secure-0.1.0/tests/test_basic.py +80 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Bartosz Radomski
|
|
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,136 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: quant-bvz-secure
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Biblioteka kryptograficzna: post-kwantowa wymiana kluczy (Kyber/Dilithium) + symulacja QKD (BB84) + AEAD
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Repository, https://github.com/Jokovexor/quant-bvz-secure
|
|
7
|
+
Keywords: post-quantum,pqc,kyber,dilithium,qkd,bb84,cryptography
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Topic :: Security :: Cryptography
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: cryptography>=42.0
|
|
20
|
+
Provides-Extra: pqc
|
|
21
|
+
Requires-Dist: liboqs-python>=0.10.0; extra == "pqc"
|
|
22
|
+
Provides-Extra: qkd-hw
|
|
23
|
+
Requires-Dist: qiskit>=1.0; extra == "qkd-hw"
|
|
24
|
+
Requires-Dist: qiskit-aer>=0.14; extra == "qkd-hw"
|
|
25
|
+
Provides-Extra: all
|
|
26
|
+
Requires-Dist: liboqs-python>=0.10.0; extra == "all"
|
|
27
|
+
Requires-Dist: qiskit>=1.0; extra == "all"
|
|
28
|
+
Requires-Dist: qiskit-aer>=0.14; extra == "all"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# qcrypt
|
|
32
|
+
|
|
33
|
+
Biblioteka kryptograficzna łącząca **kryptografię post-kwantową (PQC)**
|
|
34
|
+
z **symulacją kwantowej dystrybucji klucza (QKD / BB84)**.
|
|
35
|
+
|
|
36
|
+
## Ważne zastrzeżenie
|
|
37
|
+
|
|
38
|
+
Czysty Python nie ma dostępu do sprzętu kwantowego. Ta biblioteka dzieli się
|
|
39
|
+
na dwie realistyczne części:
|
|
40
|
+
|
|
41
|
+
- **`qcrypt.pqc`** — prawdziwe, produkcyjne algorytmy odporne na ataki
|
|
42
|
+
komputerów kwantowych (Kyber/ML-KEM, Dilithium/ML-DSA), działające na
|
|
43
|
+
zwykłym procesorze *już dziś*. To jest realne zastosowanie.
|
|
44
|
+
- **`qcrypt.qkd`** — **symulacja matematyczna** protokołu BB84 do celów
|
|
45
|
+
edukacyjnych. Domyślnie nie wykonuje żadnych operacji na prawdziwych
|
|
46
|
+
kubitach. Można podłączyć `QiskitBackend`, żeby faktycznie budować i
|
|
47
|
+
wykonywać obwody kwantowe na symulatorze Qiskit Aer (lub realnym QPU IBM).
|
|
48
|
+
|
|
49
|
+
## Instalacja
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install -e . # rdzeń (AEAD, symulacja BB84, X25519)
|
|
53
|
+
pip install -e .[pqc] # + Kyber/Dilithium (liboqs-python)
|
|
54
|
+
pip install -e .[qkd-hw] # + prawdziwe obwody kwantowe (Qiskit)
|
|
55
|
+
pip install -e .[all] # wszystko
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`liboqs-python` wymaga skompilowania biblioteki C `liboqs` — zobacz
|
|
59
|
+
https://github.com/open-quantum-safe/liboqs-python
|
|
60
|
+
|
|
61
|
+
## Szybki start
|
|
62
|
+
|
|
63
|
+
### 1. Hybrydowa wymiana klucza (produkcyjna)
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from qcrypt import HybridKEM, AEAD
|
|
67
|
+
|
|
68
|
+
bob = HybridKEM()
|
|
69
|
+
bundle = bob.generate_keypair() # Bob -> wysyła do Alice
|
|
70
|
+
|
|
71
|
+
ciphertext, alice_secret = HybridKEM.encapsulate(bundle) # Alice
|
|
72
|
+
bob_secret = bob.decapsulate(ciphertext) # Bob
|
|
73
|
+
|
|
74
|
+
assert alice_secret == bob_secret
|
|
75
|
+
|
|
76
|
+
cipher = AEAD(key=alice_secret)
|
|
77
|
+
blob = cipher.encrypt(b"tajna wiadomosc")
|
|
78
|
+
plaintext = AEAD(key=bob_secret).decrypt(blob)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 2. Symulacja BB84 (edukacyjna)
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from qcrypt.qkd.bb84 import BB84Protocol
|
|
85
|
+
from qcrypt.qkd.channel import QuantumChannel
|
|
86
|
+
|
|
87
|
+
# Kanał bez podsłuchu
|
|
88
|
+
channel = QuantumChannel(noise_level=0.02)
|
|
89
|
+
result = BB84Protocol(channel=channel, n_qubits=2048).run()
|
|
90
|
+
print(result.qber, result.eavesdropper_detected)
|
|
91
|
+
|
|
92
|
+
# Kanał z podsłuchem — QBER wzrośnie i zostanie wykryty
|
|
93
|
+
channel_eve = QuantumChannel(eavesdropper=True)
|
|
94
|
+
result_eve = BB84Protocol(channel=channel_eve, n_qubits=2048).run()
|
|
95
|
+
assert result_eve.eavesdropper_detected
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 3. CLI
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
python -m qcrypt.cli bb84-demo --qubits 4096
|
|
102
|
+
python -m qcrypt.cli bb84-demo --qubits 4096 --eve # symulacja podsłuchu
|
|
103
|
+
python -m qcrypt.cli hybrid-demo --classical-only # bez liboqs
|
|
104
|
+
python -m qcrypt.cli encrypt --key-hex <64 hex znaki> --in plik.txt --out plik.enc
|
|
105
|
+
python -m qcrypt.cli decrypt --key-hex <64 hex znaki> --in plik.enc --out plik.txt
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Struktura
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
qcrypt/
|
|
112
|
+
├── pqc/
|
|
113
|
+
│ ├── kem.py # Kyber/ML-KEM przez liboqs-python
|
|
114
|
+
│ ├── signatures.py # Dilithium/ML-DSA przez liboqs-python
|
|
115
|
+
│ └── hybrid.py # X25519 + Kyber połączone przez HKDF
|
|
116
|
+
├── qkd/
|
|
117
|
+
│ ├── bb84.py # orkiestracja protokołu, sifting, QBER, privacy amplification
|
|
118
|
+
│ ├── channel.py # model kanału + symulowany podsłuchujący (Eve)
|
|
119
|
+
│ └── backend.py # SimulatedBackend (domyślny) / QiskitBackend (opcjonalny)
|
|
120
|
+
├── symmetric/
|
|
121
|
+
│ └── aead.py # AES-256-GCM + HKDF do wyprowadzania klucza
|
|
122
|
+
├── keystore.py # zapis/odczyt kluczy do JSON (base64)
|
|
123
|
+
├── exceptions.py
|
|
124
|
+
└── cli.py
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Bezpieczeństwo
|
|
128
|
+
|
|
129
|
+
- Implementacje PQC pochodzą z zaudytowanej biblioteki `liboqs` — nie
|
|
130
|
+
implementujemy własnej kryptografii kratowej.
|
|
131
|
+
- `HybridKEM` łączy X25519 z Kyber: złamanie musi dotyczyć **obu**
|
|
132
|
+
algorytmów jednocześnie, zgodnie z zaleceniami NIST na okres przejściowy.
|
|
133
|
+
- Moduł `qkd` jest **edukacyjny**. Nie używaj go jako jedynej warstwy
|
|
134
|
+
bezpieczeństwa w systemie produkcyjnym bez realnego sprzętu kwantowego i
|
|
135
|
+
zaufanego kanału uwierzytelniania klasycznego (BB84 wymaga uwierzytelnionego
|
|
136
|
+
kanału klasycznego do sifting, inaczej podatny na man-in-the-middle).
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# qcrypt
|
|
2
|
+
|
|
3
|
+
Biblioteka kryptograficzna łącząca **kryptografię post-kwantową (PQC)**
|
|
4
|
+
z **symulacją kwantowej dystrybucji klucza (QKD / BB84)**.
|
|
5
|
+
|
|
6
|
+
## Ważne zastrzeżenie
|
|
7
|
+
|
|
8
|
+
Czysty Python nie ma dostępu do sprzętu kwantowego. Ta biblioteka dzieli się
|
|
9
|
+
na dwie realistyczne części:
|
|
10
|
+
|
|
11
|
+
- **`qcrypt.pqc`** — prawdziwe, produkcyjne algorytmy odporne na ataki
|
|
12
|
+
komputerów kwantowych (Kyber/ML-KEM, Dilithium/ML-DSA), działające na
|
|
13
|
+
zwykłym procesorze *już dziś*. To jest realne zastosowanie.
|
|
14
|
+
- **`qcrypt.qkd`** — **symulacja matematyczna** protokołu BB84 do celów
|
|
15
|
+
edukacyjnych. Domyślnie nie wykonuje żadnych operacji na prawdziwych
|
|
16
|
+
kubitach. Można podłączyć `QiskitBackend`, żeby faktycznie budować i
|
|
17
|
+
wykonywać obwody kwantowe na symulatorze Qiskit Aer (lub realnym QPU IBM).
|
|
18
|
+
|
|
19
|
+
## Instalacja
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install -e . # rdzeń (AEAD, symulacja BB84, X25519)
|
|
23
|
+
pip install -e .[pqc] # + Kyber/Dilithium (liboqs-python)
|
|
24
|
+
pip install -e .[qkd-hw] # + prawdziwe obwody kwantowe (Qiskit)
|
|
25
|
+
pip install -e .[all] # wszystko
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`liboqs-python` wymaga skompilowania biblioteki C `liboqs` — zobacz
|
|
29
|
+
https://github.com/open-quantum-safe/liboqs-python
|
|
30
|
+
|
|
31
|
+
## Szybki start
|
|
32
|
+
|
|
33
|
+
### 1. Hybrydowa wymiana klucza (produkcyjna)
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from qcrypt import HybridKEM, AEAD
|
|
37
|
+
|
|
38
|
+
bob = HybridKEM()
|
|
39
|
+
bundle = bob.generate_keypair() # Bob -> wysyła do Alice
|
|
40
|
+
|
|
41
|
+
ciphertext, alice_secret = HybridKEM.encapsulate(bundle) # Alice
|
|
42
|
+
bob_secret = bob.decapsulate(ciphertext) # Bob
|
|
43
|
+
|
|
44
|
+
assert alice_secret == bob_secret
|
|
45
|
+
|
|
46
|
+
cipher = AEAD(key=alice_secret)
|
|
47
|
+
blob = cipher.encrypt(b"tajna wiadomosc")
|
|
48
|
+
plaintext = AEAD(key=bob_secret).decrypt(blob)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 2. Symulacja BB84 (edukacyjna)
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from qcrypt.qkd.bb84 import BB84Protocol
|
|
55
|
+
from qcrypt.qkd.channel import QuantumChannel
|
|
56
|
+
|
|
57
|
+
# Kanał bez podsłuchu
|
|
58
|
+
channel = QuantumChannel(noise_level=0.02)
|
|
59
|
+
result = BB84Protocol(channel=channel, n_qubits=2048).run()
|
|
60
|
+
print(result.qber, result.eavesdropper_detected)
|
|
61
|
+
|
|
62
|
+
# Kanał z podsłuchem — QBER wzrośnie i zostanie wykryty
|
|
63
|
+
channel_eve = QuantumChannel(eavesdropper=True)
|
|
64
|
+
result_eve = BB84Protocol(channel=channel_eve, n_qubits=2048).run()
|
|
65
|
+
assert result_eve.eavesdropper_detected
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 3. CLI
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
python -m qcrypt.cli bb84-demo --qubits 4096
|
|
72
|
+
python -m qcrypt.cli bb84-demo --qubits 4096 --eve # symulacja podsłuchu
|
|
73
|
+
python -m qcrypt.cli hybrid-demo --classical-only # bez liboqs
|
|
74
|
+
python -m qcrypt.cli encrypt --key-hex <64 hex znaki> --in plik.txt --out plik.enc
|
|
75
|
+
python -m qcrypt.cli decrypt --key-hex <64 hex znaki> --in plik.enc --out plik.txt
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Struktura
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
qcrypt/
|
|
82
|
+
├── pqc/
|
|
83
|
+
│ ├── kem.py # Kyber/ML-KEM przez liboqs-python
|
|
84
|
+
│ ├── signatures.py # Dilithium/ML-DSA przez liboqs-python
|
|
85
|
+
│ └── hybrid.py # X25519 + Kyber połączone przez HKDF
|
|
86
|
+
├── qkd/
|
|
87
|
+
│ ├── bb84.py # orkiestracja protokołu, sifting, QBER, privacy amplification
|
|
88
|
+
│ ├── channel.py # model kanału + symulowany podsłuchujący (Eve)
|
|
89
|
+
│ └── backend.py # SimulatedBackend (domyślny) / QiskitBackend (opcjonalny)
|
|
90
|
+
├── symmetric/
|
|
91
|
+
│ └── aead.py # AES-256-GCM + HKDF do wyprowadzania klucza
|
|
92
|
+
├── keystore.py # zapis/odczyt kluczy do JSON (base64)
|
|
93
|
+
├── exceptions.py
|
|
94
|
+
└── cli.py
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Bezpieczeństwo
|
|
98
|
+
|
|
99
|
+
- Implementacje PQC pochodzą z zaudytowanej biblioteki `liboqs` — nie
|
|
100
|
+
implementujemy własnej kryptografii kratowej.
|
|
101
|
+
- `HybridKEM` łączy X25519 z Kyber: złamanie musi dotyczyć **obu**
|
|
102
|
+
algorytmów jednocześnie, zgodnie z zaleceniami NIST na okres przejściowy.
|
|
103
|
+
- Moduł `qkd` jest **edukacyjny**. Nie używaj go jako jedynej warstwy
|
|
104
|
+
bezpieczeństwa w systemie produkcyjnym bez realnego sprzętu kwantowego i
|
|
105
|
+
zaufanego kanału uwierzytelniania klasycznego (BB84 wymaga uwierzytelnionego
|
|
106
|
+
kanału klasycznego do sifting, inaczej podatny na man-in-the-middle).
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "quant-bvz-secure"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Biblioteka kryptograficzna: post-kwantowa wymiana kluczy (Kyber/Dilithium) + symulacja QKD (BB84) + AEAD"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
keywords = ["post-quantum", "pqc", "kyber", "dilithium", "qkd", "bb84", "cryptography"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"Topic :: Security :: Cryptography",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"cryptography>=42.0",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.optional-dependencies]
|
|
28
|
+
pqc = ["liboqs-python>=0.10.0"]
|
|
29
|
+
qkd-hw = ["qiskit>=1.0", "qiskit-aer>=0.14"]
|
|
30
|
+
all = ["liboqs-python>=0.10.0", "qiskit>=1.0", "qiskit-aer>=0.14"]
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Repository = "https://github.com/Jokovexor/quant-bvz-secure"
|
|
34
|
+
|
|
35
|
+
[tool.setuptools.packages.find]
|
|
36
|
+
include = ["qcrypt*"]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
qcrypt — biblioteka kryptograficzna łącząca:
|
|
3
|
+
1) Post-Quantum Cryptography (PQC) — realne algorytmy odporne na komputery
|
|
4
|
+
kwantowe (Kyber/ML-KEM, Dilithium/ML-DSA przez liboqs, jeśli dostępne).
|
|
5
|
+
2) Symulator QKD (BB84) — edukacyjna symulacja kwantowej dystrybucji klucza.
|
|
6
|
+
3) Warstwę symetryczną AEAD (AES-256-GCM) do faktycznego szyfrowania danych.
|
|
7
|
+
|
|
8
|
+
WAŻNE: moduł qkd to symulacja matematyczna protokołu BB84 na klasycznym
|
|
9
|
+
komputerze — nie wykonuje żadnych operacji na prawdziwym sprzęcie kwantowym,
|
|
10
|
+
chyba że podłączysz backend Qiskit (patrz qcrypt.qkd.backend).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
__version__ = "0.1.0"
|
|
14
|
+
|
|
15
|
+
from qcrypt.symmetric.aead import AEAD
|
|
16
|
+
from qcrypt.pqc.hybrid import HybridKEM
|
|
17
|
+
from qcrypt.qkd.bb84 import BB84Protocol
|
|
18
|
+
from qcrypt.exceptions import QCryptError, PQCUnavailableError, EavesdropDetected
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"AEAD",
|
|
22
|
+
"HybridKEM",
|
|
23
|
+
"BB84Protocol",
|
|
24
|
+
"QCryptError",
|
|
25
|
+
"PQCUnavailableError",
|
|
26
|
+
"EavesdropDetected",
|
|
27
|
+
]
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Prosty interfejs wiersza poleceń do demonstracji biblioteki qcrypt.
|
|
3
|
+
|
|
4
|
+
Użycie:
|
|
5
|
+
python -m qcrypt.cli bb84-demo [--qubits N] [--eve] [--noise 0.02]
|
|
6
|
+
python -m qcrypt.cli hybrid-demo
|
|
7
|
+
python -m qcrypt.cli encrypt --key-hex <hex> --in plik.txt --out plik.enc
|
|
8
|
+
python -m qcrypt.cli decrypt --key-hex <hex> --in plik.enc --out plik.txt
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
from qcrypt.qkd.bb84 import BB84Protocol
|
|
17
|
+
from qcrypt.qkd.channel import QuantumChannel
|
|
18
|
+
from qcrypt.pqc.hybrid import HybridKEM
|
|
19
|
+
from qcrypt.symmetric.aead import AEAD
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def cmd_bb84_demo(args: argparse.Namespace) -> None:
|
|
23
|
+
channel = QuantumChannel(eavesdropper=args.eve, noise_level=args.noise)
|
|
24
|
+
protocol = BB84Protocol(channel=channel, n_qubits=args.qubits)
|
|
25
|
+
result = protocol.run()
|
|
26
|
+
|
|
27
|
+
print(f"Wysłane kubity: {result.raw_key_length}")
|
|
28
|
+
print(f"Po sifting: {result.sifted_key_length}")
|
|
29
|
+
print(f"Estymowany QBER: {result.qber:.2%}")
|
|
30
|
+
print(f"Eve przechwytywała: {'TAK' if args.eve else 'NIE'}")
|
|
31
|
+
print(f"Wykryto podsłuch: {'TAK' if result.eavesdropper_detected else 'NIE'}")
|
|
32
|
+
if not result.eavesdropper_detected:
|
|
33
|
+
print(f"Finalny klucz (hex): {result.shared_key.hex()}")
|
|
34
|
+
else:
|
|
35
|
+
print("Klucz ODRZUCONY — kanał uznany za skompromitowany.")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def cmd_hybrid_demo(args: argparse.Namespace) -> None:
|
|
39
|
+
bob = HybridKEM(classical_only=args.classical_only)
|
|
40
|
+
bundle = bob.generate_keypair()
|
|
41
|
+
|
|
42
|
+
ciphertext, alice_secret = HybridKEM.encapsulate(bundle)
|
|
43
|
+
bob_secret = bob.decapsulate(ciphertext)
|
|
44
|
+
|
|
45
|
+
print(f"Tryb: {'tylko klasyczny (X25519)' if bob.classical_only else 'hybrydowy (X25519 + Kyber)'}")
|
|
46
|
+
print(f"Sekret Alice (hex): {alice_secret.hex()}")
|
|
47
|
+
print(f"Sekret Boba (hex): {bob_secret.hex()}")
|
|
48
|
+
print(f"Sekrety zgodne: {'TAK' if alice_secret == bob_secret else 'NIE'}")
|
|
49
|
+
|
|
50
|
+
cipher = AEAD(key=alice_secret)
|
|
51
|
+
blob = cipher.encrypt(b"Wiadomosc zaszyfrowana wspolnym sekretem")
|
|
52
|
+
decrypted = AEAD(key=bob_secret).decrypt(blob)
|
|
53
|
+
print(f"Test AEAD: {decrypted.decode('utf-8')}")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def cmd_encrypt(args: argparse.Namespace) -> None:
|
|
57
|
+
key = bytes.fromhex(args.key_hex)
|
|
58
|
+
with open(args.infile, "rb") as f:
|
|
59
|
+
data = f.read()
|
|
60
|
+
blob = AEAD(key=key).encrypt(data)
|
|
61
|
+
with open(args.outfile, "wb") as f:
|
|
62
|
+
f.write(blob)
|
|
63
|
+
print(f"Zaszyfrowano -> {args.outfile}")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def cmd_decrypt(args: argparse.Namespace) -> None:
|
|
67
|
+
key = bytes.fromhex(args.key_hex)
|
|
68
|
+
with open(args.infile, "rb") as f:
|
|
69
|
+
blob = f.read()
|
|
70
|
+
data = AEAD(key=key).decrypt(blob)
|
|
71
|
+
with open(args.outfile, "wb") as f:
|
|
72
|
+
f.write(data)
|
|
73
|
+
print(f"Odszyfrowano -> {args.outfile}")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
77
|
+
parser = argparse.ArgumentParser(prog="qcrypt", description="Demo biblioteki qcrypt")
|
|
78
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
79
|
+
|
|
80
|
+
p_bb84 = sub.add_parser("bb84-demo", help="Symulacja protokołu BB84")
|
|
81
|
+
p_bb84.add_argument("--qubits", type=int, default=1024)
|
|
82
|
+
p_bb84.add_argument("--eve", action="store_true", help="Włącz symulowanego podsłuchującego")
|
|
83
|
+
p_bb84.add_argument("--noise", type=float, default=0.0, help="Poziom szumu kanału 0.0-1.0")
|
|
84
|
+
p_bb84.set_defaults(func=cmd_bb84_demo)
|
|
85
|
+
|
|
86
|
+
p_hybrid = sub.add_parser("hybrid-demo", help="Hybrydowa wymiana kluczy X25519+Kyber")
|
|
87
|
+
p_hybrid.add_argument("--classical-only", action="store_true",
|
|
88
|
+
help="Wymuś tryb bez PQC (gdy liboqs niedostępne)")
|
|
89
|
+
p_hybrid.set_defaults(func=cmd_hybrid_demo)
|
|
90
|
+
|
|
91
|
+
p_enc = sub.add_parser("encrypt", help="Zaszyfruj plik AES-256-GCM")
|
|
92
|
+
p_enc.add_argument("--key-hex", required=True)
|
|
93
|
+
p_enc.add_argument("--in", dest="infile", required=True)
|
|
94
|
+
p_enc.add_argument("--out", dest="outfile", required=True)
|
|
95
|
+
p_enc.set_defaults(func=cmd_encrypt)
|
|
96
|
+
|
|
97
|
+
p_dec = sub.add_parser("decrypt", help="Odszyfruj plik")
|
|
98
|
+
p_dec.add_argument("--key-hex", required=True)
|
|
99
|
+
p_dec.add_argument("--in", dest="infile", required=True)
|
|
100
|
+
p_dec.add_argument("--out", dest="outfile", required=True)
|
|
101
|
+
p_dec.set_defaults(func=cmd_decrypt)
|
|
102
|
+
|
|
103
|
+
return parser
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def main(argv: list[str] | None = None) -> int:
|
|
107
|
+
parser = build_parser()
|
|
108
|
+
args = parser.parse_args(argv)
|
|
109
|
+
try:
|
|
110
|
+
args.func(args)
|
|
111
|
+
except Exception as exc:
|
|
112
|
+
print(f"Blad: {exc}", file=sys.stderr)
|
|
113
|
+
return 1
|
|
114
|
+
return 0
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
if __name__ == "__main__":
|
|
118
|
+
sys.exit(main())
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Wyjątki używane w całej bibliotece qcrypt."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class QCryptError(Exception):
|
|
5
|
+
"""Bazowy wyjątek biblioteki."""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PQCUnavailableError(QCryptError):
|
|
9
|
+
"""Podnoszony, gdy biblioteka liboqs-python (moduł `oqs`) nie jest
|
|
10
|
+
zainstalowana, a wywołany kod wymaga prawdziwego algorytmu PQC
|
|
11
|
+
(Kyber/ML-KEM, Dilithium/ML-DSA)."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class EavesdropDetected(QCryptError):
|
|
15
|
+
"""Podnoszony przez symulator BB84, gdy poziom błędów (QBER) w próbce
|
|
16
|
+
kontrolnej przekracza bezpieczny próg, co sugeruje podsłuch kanału
|
|
17
|
+
kwantowego."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DecryptionError(QCryptError):
|
|
21
|
+
"""Podnoszony, gdy odszyfrowanie/uwierzytelnienie AEAD się nie powiedzie
|
|
22
|
+
(zły klucz, uszkodzone dane lub próba manipulacji)."""
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Magazyn kluczy — serializacja kluczy publicznych/sekretów do plików JSON
|
|
3
|
+
(dane binarne kodowane base64). Nie przechowuje kluczy prywatnych obiektów
|
|
4
|
+
oqs/cryptography bezpośrednio (te żyją tylko w pamięci procesu) — przechowuje
|
|
5
|
+
surowe bajty.
|
|
6
|
+
|
|
7
|
+
Domyślnie (bez podania hasła) plik jest plaintext-base64 — wygodne do
|
|
8
|
+
szybkich testów/demo, ale NIE do produkcji. Podając `password`, cały
|
|
9
|
+
zawartość magazynu jest szyfrowana AES-256-GCM kluczem wyprowadzonym z hasła
|
|
10
|
+
przez Scrypt (odpornym na ataki brute-force na GPU/ASIC), przy użyciu
|
|
11
|
+
istniejącej klasy `AEAD`. Plik na dysku jest wtedy dodatkowo chroniony
|
|
12
|
+
uprawnieniami 0600 (tylko właściciel może czytać/pisać).
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import base64
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
from cryptography.hazmat.primitives.kdf.scrypt import Scrypt
|
|
23
|
+
|
|
24
|
+
from qcrypt.symmetric.aead import AEAD
|
|
25
|
+
from qcrypt.exceptions import DecryptionError
|
|
26
|
+
|
|
27
|
+
SALT_SIZE = 16
|
|
28
|
+
SCRYPT_N = 2**16 # koszt CPU/pamięci — bezpieczny domyślny poziom (2026)
|
|
29
|
+
SCRYPT_R = 8
|
|
30
|
+
SCRYPT_P = 1
|
|
31
|
+
SCRYPT_KEY_LEN = 32
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class KeyStore:
|
|
35
|
+
"""
|
|
36
|
+
Przykład bez hasła (plaintext — tylko do testów/demo):
|
|
37
|
+
store = KeyStore("keys.json")
|
|
38
|
+
store.save("bob_public", public_key_bytes)
|
|
39
|
+
pub = store.load("bob_public")
|
|
40
|
+
|
|
41
|
+
Przykład z hasłem (zaszyfrowany magazyn — zalecane produkcyjnie):
|
|
42
|
+
store = KeyStore("keys.json", password="korzen-slonecznik-42")
|
|
43
|
+
store.save("bob_secret", secret_key_bytes)
|
|
44
|
+
secret = store.load("bob_secret")
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(self, path: str | Path, *, password: str | None = None):
|
|
48
|
+
self.path = Path(path)
|
|
49
|
+
self._password = password
|
|
50
|
+
self._salt: bytes | None = None
|
|
51
|
+
self._data: dict[str, str] = {}
|
|
52
|
+
|
|
53
|
+
if self.path.exists():
|
|
54
|
+
self._load_from_disk()
|
|
55
|
+
elif password is not None:
|
|
56
|
+
self._salt = os.urandom(SALT_SIZE)
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def is_encrypted(self) -> bool:
|
|
60
|
+
return self._password is not None
|
|
61
|
+
|
|
62
|
+
def save(self, name: str, key_bytes: bytes) -> None:
|
|
63
|
+
self._data[name] = base64.b64encode(key_bytes).decode("ascii")
|
|
64
|
+
self._flush()
|
|
65
|
+
|
|
66
|
+
def load(self, name: str) -> bytes:
|
|
67
|
+
if name not in self._data:
|
|
68
|
+
raise KeyError(f"Brak klucza o nazwie '{name}' w {self.path}")
|
|
69
|
+
return base64.b64decode(self._data[name])
|
|
70
|
+
|
|
71
|
+
def delete(self, name: str) -> None:
|
|
72
|
+
self._data.pop(name, None)
|
|
73
|
+
self._flush()
|
|
74
|
+
|
|
75
|
+
def list_keys(self) -> list[str]:
|
|
76
|
+
return list(self._data.keys())
|
|
77
|
+
|
|
78
|
+
# -- szyfrowanie magazynu na dysku -------------------------------------
|
|
79
|
+
|
|
80
|
+
def _derive_master_key(self, salt: bytes) -> bytes:
|
|
81
|
+
assert self._password is not None
|
|
82
|
+
kdf = Scrypt(salt=salt, length=SCRYPT_KEY_LEN, n=SCRYPT_N, r=SCRYPT_R, p=SCRYPT_P)
|
|
83
|
+
return kdf.derive(self._password.encode("utf-8"))
|
|
84
|
+
|
|
85
|
+
def _load_from_disk(self) -> None:
|
|
86
|
+
raw = self.path.read_bytes()
|
|
87
|
+
|
|
88
|
+
if self._password is None:
|
|
89
|
+
# tryb plaintext (kompatybilność wsteczna / demo)
|
|
90
|
+
self._data = json.loads(raw.decode("utf-8"))
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
if len(raw) < SALT_SIZE:
|
|
94
|
+
raise DecryptionError(f"Plik {self.path} jest za krótki, brak salt.")
|
|
95
|
+
salt, blob = raw[:SALT_SIZE], raw[SALT_SIZE:]
|
|
96
|
+
self._salt = salt
|
|
97
|
+
master_key = self._derive_master_key(salt)
|
|
98
|
+
try:
|
|
99
|
+
plaintext = AEAD(key=master_key).decrypt(blob)
|
|
100
|
+
except DecryptionError as exc:
|
|
101
|
+
raise DecryptionError(
|
|
102
|
+
f"Nie udało się odszyfrować magazynu {self.path} — błędne hasło "
|
|
103
|
+
"lub plik został uszkodzony/zmanipulowany."
|
|
104
|
+
) from exc
|
|
105
|
+
self._data = json.loads(plaintext.decode("utf-8"))
|
|
106
|
+
|
|
107
|
+
def _flush(self) -> None:
|
|
108
|
+
tmp_path = self.path.with_suffix(".tmp")
|
|
109
|
+
payload = json.dumps(self._data, indent=2).encode("utf-8")
|
|
110
|
+
|
|
111
|
+
if self._password is not None:
|
|
112
|
+
if self._salt is None:
|
|
113
|
+
self._salt = os.urandom(SALT_SIZE)
|
|
114
|
+
master_key = self._derive_master_key(self._salt)
|
|
115
|
+
payload = self._salt + AEAD(key=master_key).encrypt(payload)
|
|
116
|
+
|
|
117
|
+
# Uprawnienia 0600 od razu przy tworzeniu pliku (nie po fakcie —
|
|
118
|
+
# unikamy okna czasowego, w którym plik byłby czytelny dla innych).
|
|
119
|
+
fd = os.open(tmp_path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
|
|
120
|
+
with os.fdopen(fd, "wb") as f:
|
|
121
|
+
f.write(payload)
|
|
122
|
+
os.replace(tmp_path, self.path)
|
|
123
|
+
os.chmod(self.path, 0o600)
|