src/pqc_lint/suggestions.py
| 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 | |