src/pqc_lint/suggestions.py
3.0 KB · 46 lines · python Raw
1 """Map classical cryptographic primitives to PQC replacements."""
2
3 from __future__ import annotations
4
5 # Canonical classical -> PQC replacement map
6 CLASSICAL_TO_PQC: dict[str, dict[str, str]] = {
7 # Signatures
8 "RSA-PSS": {"replacement": "ML-DSA-65 (FIPS 204)", "reason": "RSA is broken by Shor's algorithm. ML-DSA is the NIST PQC signature standard."},
9 "RSA-PKCS1v15": {"replacement": "ML-DSA-65 (FIPS 204)", "reason": "RSA is broken by Shor's algorithm. PKCS1v15 is additionally vulnerable to padding oracle attacks."},
10 "RSA-signing": {"replacement": "ML-DSA-65 (FIPS 204)", "reason": "RSA signatures are broken by Shor's algorithm. Use ML-DSA for quantum-safe signing."},
11 "ECDSA": {"replacement": "ML-DSA-65 (FIPS 204)", "reason": "Elliptic curve signatures are broken by Shor's algorithm. ML-DSA is quantum-safe."},
12 "DSA": {"replacement": "ML-DSA-44 (FIPS 204) or SLH-DSA", "reason": "DSA is broken by Shor's. Deprecated even in classical settings."},
13 "Ed25519": {"replacement": "ML-DSA-44 (FIPS 204)", "reason": "Ed25519 is classical EC signing - broken by Shor's. Consider ML-DSA for PQC; SLH-DSA for stateless hash-based alternative."},
14
15 # Key exchange / encapsulation
16 "RSA-encryption":{"replacement": "ML-KEM-768 (FIPS 203)", "reason": "RSA encryption is broken by Shor's. ML-KEM is the NIST PQC KEM standard."},
17 "RSA-OAEP": {"replacement": "ML-KEM-768 (FIPS 203)", "reason": "RSA-OAEP is broken by Shor's algorithm once CRQC exists."},
18 "DH": {"replacement": "ML-KEM-768 (FIPS 203)", "reason": "Finite-field Diffie-Hellman is broken by Shor's algorithm."},
19 "ECDH": {"replacement": "ML-KEM-768 (FIPS 203)", "reason": "Elliptic curve Diffie-Hellman is broken by Shor's algorithm."},
20 "X25519": {"replacement": "ML-KEM-512 (FIPS 203)", "reason": "X25519 is classical EC key agreement - broken by Shor's."},
21
22 # Weak hashes (classical, not strictly quantum but still bad)
23 "MD5": {"replacement": "SHA3-256 or SHAKE-256", "reason": "MD5 is cryptographically broken. Grover's algorithm doesn't make it worse but it's already unusable."},
24 "SHA1": {"replacement": "SHA3-256 or SHAKE-256", "reason": "SHA-1 is broken (SHAttered). Use SHA3-256 for quantum-safe hashing (256-bit -> 128-bit under Grover's)."},
25 }
26
27
28 def suggest_replacement(classical_name: str) -> str:
29 """Return a human-readable suggestion string for a classical primitive."""
30 name = classical_name.upper().replace("_", "-").replace(" ", "-")
31 # normalize common variants
32 lookup_key = None
33 for key in CLASSICAL_TO_PQC:
34 if key.upper().replace("_", "-") == name:
35 lookup_key = key
36 break
37 if not lookup_key:
38 for key in CLASSICAL_TO_PQC:
39 if key.upper().split("-")[0] == name.split("-")[0]:
40 lookup_key = key
41 break
42 if not lookup_key:
43 return ""
44 entry = CLASSICAL_TO_PQC[lookup_key]
45 return f"Use {entry['replacement']}. {entry['reason']}"
46