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.
@@ -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)
@@ -0,0 +1,5 @@
1
+ from qcrypt.pqc.hybrid import HybridKEM
2
+ from qcrypt.pqc.kem import KyberKEM, PQC_AVAILABLE
3
+ from qcrypt.pqc.signatures import DilithiumSigner
4
+
5
+ __all__ = ["HybridKEM", "KyberKEM", "DilithiumSigner", "PQC_AVAILABLE"]