// Catalogul de clasificare. Fiecare gasire primeste o clasa, un motiv scris si, cand e // vulnerabila, o recomandare de migrare. Textele pentru utilizator sunt in engleza. // // Reguli: o clasa nu se ghiceste. "unknown" inseamna ca valoarea nu se poate afla static. export const CLS = Object.freeze({ V: 'quantum-vulnerable', W: 'weak-now', S: 'quantum-safe', U: 'unknown', }); export const REC = Object.freeze({ SIG: 'Migrate signatures to ML-DSA-65 (FIPS 204), or to a hybrid (composite) classical+ML-DSA signature during the transition so that both would have to be broken. For long-lived roots of trust consider SLH-DSA (FIPS 205). Keys and signatures grow (ML-DSA-65: 1,952-byte public key, 3,309-byte signature): check protocol, storage and column limits. Composite signatures for X.509 were still an IETF draft when this rule was written.', KEX: 'For TLS, offer the hybrid group X25519MLKEM768 in TLS 1.3 (built into Go 1.24+ and OpenSSL 3.5+). For application-level key establishment use ML-KEM-768 (FIPS 203), ideally combined with X25519 during the transition. Traffic protected by a classical key exchange can be recorded now and decrypted later, so start with data that must stay confidential for years.', PKE: 'Replace RSA encryption and key transport with ML-KEM-768 (FIPS 203) used as a KEM in front of an AEAD such as AES-256-GCM, ideally hybrid with X25519 during the transition. Ciphertexts recorded today become readable once a cryptographically relevant quantum computer exists; re-encrypt data that must stay confidential for years.', SECP256K1: 'secp256k1 ECDSA is fixed by the account and transaction format of Ethereum-style and Bitcoin-style chains, so this is not a library swap. Plan the migration at the account level: move control of funds and roles to an account whose owner is a post-quantum key (for example an ML-DSA or Falcon key checked by a smart-contract account or by the chain itself), and stop reusing addresses whose public key is already exposed on chain.', JWT: 'There is no final standard for post-quantum JWS/JWT yet: ML-DSA for JOSE and COSE was an IETF draft when this rule was written, and mainstream JWT libraries do not ship it by default. What you can do now: keep token lifetimes short, use HS256/HS512 where issuer and verifier can share a secret, inventory where verification keys live so they can be rotated, and plan a hybrid (classical + ML-DSA) signature once the specification is final and your library supports it.', JWT_NONE: 'An unsigned JWT ("none") must never be accepted: remove it from the accepted algorithms.', HASH_WEAK: 'Replace with SHA-256, SHA-384 or SHA3-256. If this is a non-security checksum (cache key, deduplication), document that; for passwords use a password hash (Argon2id, scrypt, bcrypt, or PBKDF2-HMAC-SHA-256 with a high iteration count).', CIPHER_WEAK: 'Replace with AES-256-GCM or ChaCha20-Poly1305, with a unique nonce per key.', ECB: 'ECB leaks plaintext patterns. Use an authenticated mode: AES-256-GCM (unique nonce per key) or ChaCha20-Poly1305.', TLS_OLD: 'Disable SSL, TLS 1.0 and TLS 1.1; require at least TLS 1.2 and prefer TLS 1.3 with the hybrid group X25519MLKEM768.', TLS12: 'TLS 1.2 has no standardized post-quantum key exchange. Prefer TLS 1.3 and offer X25519MLKEM768; keep TLS 1.2 only for clients that require it, and measure how many still negotiate it.', TLS13: 'Check the negotiated key-exchange groups: offer X25519MLKEM768 first. Go 1.24+ does this by default when CurvePreferences is unset, and OpenSSL 3.5+ includes it in its default groups; older runtimes negotiate a classical group.', AES128: 'Grover\'s algorithm gives at most a quadratic speed-up, which NIST treats as security category 1 for AES-128. Frameworks such as CNSA 2.0 require AES-256; prefer AES-256 for data that must stay confidential for decades.', AES_SIZE: 'Key size is set at run time by the key length. AES-128 is NIST category 1 against a quantum attacker, AES-256 category 5; prefer AES-256 for long-lived data.', PREPQ: 'Pre-standard variant: migrate to the final NIST standard (ML-KEM FIPS 203, ML-DSA FIPS 204, SLH-DSA FIPS 205, or FN-DSA for Falcon once published). Keys and outputs are not interoperable with the final versions.', BROKEN_PQ: 'Broken by classical attacks in 2022. Remove it; use ML-KEM (FIPS 203) or ML-DSA (FIPS 204).', UNKNOWN: 'Find where the value comes from (configuration, environment, caller) and review it there; this tool does not guess.', LIB_MULTI: 'The library offers both classical and post-quantum-safe primitives; no specific call was recognized in this file. Review how it is used.', }); // numele canonice ale curbelor const ALIAS_CURBE = [ [/^(p-?256|prime256v1|secp256r1|nist256p|curvep256|p256)$/i, 'secp256r1'], [/^(p-?384|secp384r1|nist384p|curvep384|p384)$/i, 'secp384r1'], [/^(p-?521|secp521r1|nist521p|curvep521|p521)$/i, 'secp521r1'], [/^(p-?224|secp224r1|nist224p|p224)$/i, 'secp224r1'], [/^(p-?192|prime192v1|secp192r1|nist192p|p192)$/i, 'secp192r1'], [/^(secp256k1|k256|k-256)$/i, 'secp256k1'], [/^brainpoolp256r1$/i, 'brainpoolP256r1'], [/^brainpoolp384r1$/i, 'brainpoolP384r1'], [/^brainpoolp512r1$/i, 'brainpoolP512r1'], [/^(curve25519|x25519)$/i, 'Curve25519'], [/^(ed25519|edwards25519)$/i, 'Edwards25519'], [/^(curve448|x448)$/i, 'Curve448'], [/^(ed448|edwards448)$/i, 'Edwards448'], ]; const NIVEL_CURBA = { secp256r1: 128, secp384r1: 192, secp521r1: 256, secp224r1: 112, secp192r1: 96, secp256k1: 128, brainpoolP256r1: 128, brainpoolP384r1: 192, brainpoolP512r1: 256, }; export function curbaCanonica(s) { if (!s) return null; const t = String(s).trim(); for (const [re, nume] of ALIAS_CURBE) if (re.test(t)) return nume; return t; } // hash-uri: nume canonic, clasa, nivel NIST (categoria de coliziune), OID const HASH = { MD2: { n: 'MD2', c: CLS.W }, MD4: { n: 'MD4', c: CLS.W }, MD5: { n: 'MD5', c: CLS.W, oid: '1.2.840.113549.2.5' }, SHA1: { n: 'SHA-1', c: CLS.W, oid: '1.3.14.3.2.26' }, RIPEMD160: { n: 'RIPEMD-160', c: CLS.W }, SHA224: { n: 'SHA-224', c: CLS.S }, SHA256: { n: 'SHA-256', c: CLS.S, q: 2, cl: 128, oid: '2.16.840.1.101.3.4.2.1' }, SHA384: { n: 'SHA-384', c: CLS.S, q: 4, cl: 192, oid: '2.16.840.1.101.3.4.2.2' }, SHA512: { n: 'SHA-512', c: CLS.S, q: 4, cl: 256, oid: '2.16.840.1.101.3.4.2.3' }, 'SHA512/256': { n: 'SHA-512/256', c: CLS.S, q: 2 }, 'SHA512/224': { n: 'SHA-512/224', c: CLS.S }, 'SHA3-224': { n: 'SHA3-224', c: CLS.S }, 'SHA3-256': { n: 'SHA3-256', c: CLS.S, q: 2 }, 'SHA3-384': { n: 'SHA3-384', c: CLS.S, q: 4 }, 'SHA3-512': { n: 'SHA3-512', c: CLS.S, q: 4 }, SHAKE128: { n: 'SHAKE128', c: CLS.S, xof: true }, SHAKE256: { n: 'SHAKE256', c: CLS.S, xof: true }, BLAKE2B: { n: 'BLAKE2b', c: CLS.S }, BLAKE2S: { n: 'BLAKE2s', c: CLS.S }, BLAKE2B512: { n: 'BLAKE2b-512', c: CLS.S }, BLAKE2S256: { n: 'BLAKE2s-256', c: CLS.S }, SM3: { n: 'SM3', c: CLS.S }, KECCAK256: { n: 'Keccak-256', c: CLS.S }, }; export function hashCanonic(s) { if (!s) return null; let t = String(s).trim().toUpperCase().replace(/^RSA-/, ''); t = t.replace(/_/g, '-'); if (/^SHA-?1$|^SHA$/.test(t)) return 'SHA1'; if (/^SHA-?(224|256|384|512)$/.test(t)) return 'SHA' + t.replace(/\D/g, ''); if (/^SHA-?512[/-](224|256)$/.test(t)) return 'SHA512/' + t.slice(-3); if (/^SHA3-?(224|256|384|512)$/.test(t)) return 'SHA3-' + t.slice(-3); if (/^SHAKE-?(128|256)$/.test(t)) return 'SHAKE' + t.slice(-3); if (/^MD[245]$/.test(t)) return t; if (/^RIPEMD-?160$|^RMD160$/.test(t)) return 'RIPEMD160'; if (/^BLAKE2B-?512$/.test(t)) return 'BLAKE2B512'; if (/^BLAKE2S-?256$/.test(t)) return 'BLAKE2S256'; if (/^BLAKE2[BS]$/.test(t)) return t; if (/^SM3$/.test(t)) return 'SM3'; if (/^KECCAK-?256$/.test(t)) return 'KECCAK256'; return null; } function rez(o) { return { classification: o.c, quantumVulnerable: !!o.qv, reason: o.motiv, recommendation: o.rec || null, nistQuantumSecurityLevel: o.q, classicalSecurityLevel: o.cl, oid: o.oid, }; } const SHOR = 'Shor\'s algorithm on a cryptographically relevant quantum computer recovers the private key from the public key'; // intrarea: { grup, nume?, param?, curba?, hash?, mod?, primitiv?, context? } // iesirea: descrierea completa (nume canonic, primitiv, clasa, motiv, recomandare, niveluri) export function evalueaza(a) { const g = a.grup; const f = (x) => Object.assign({ grup: g, nume: a.nume, primitiv: a.primitiv || 'unknown', param: a.param, curba: a.curba, mod: a.mod, padding: a.padding }, x); if (g === 'UNKNOWN') { return f(rez({ c: CLS.U, motiv: a.motiv || 'Algorithm could not be determined statically.', rec: REC.UNKNOWN })); } if (g === 'RSA') { const bits = a.param ? Number(a.param) : null; const nume = a.nume || (bits ? `RSA-${bits}` : 'RSA'); const prim = a.primitiv || 'unknown'; const rec = prim === 'pke' ? REC.PKE : (a.context === 'jwt' ? REC.JWT : (prim === 'signature' ? REC.SIG : `${REC.SIG} If the key is used for encryption: ${REC.PKE}`)); const hashSlab = a.hash && HASH[a.hash] && HASH[a.hash].c === CLS.W; if (bits && bits < 2048) { return f({ nume, ...rez({ c: CLS.W, qv: true, motiv: `RSA with a ${bits}-bit modulus is below the 2048-bit minimum (NIST SP 800-131A); also quantum-vulnerable: ${SHOR}.`, rec, q: 0, oid: '1.2.840.113549.1.1.1' }) }); } if (hashSlab) { return f({ nume, ...rez({ c: CLS.W, qv: true, motiv: `RSA signature over ${HASH[a.hash].n}, which has practical collision attacks; also quantum-vulnerable: ${SHOR}.`, rec: `${REC.HASH_WEAK} ${rec}`, q: 0 }) }); } const cl = bits === 2048 ? 112 : bits === 3072 ? 128 : bits === 7680 ? 192 : bits === 15360 ? 256 : undefined; return f({ nume, ...rez({ c: CLS.V, qv: true, motiv: `RSA: ${SHOR}.`, rec, q: 0, cl, oid: '1.2.840.113549.1.1.1' }) }); } if (g === 'DSA') { const bits = a.param ? Number(a.param) : null; const nume = a.nume || (bits ? `DSA-${bits}` : 'DSA'); if ((bits && bits < 2048) || (a.hash && HASH[a.hash] && HASH[a.hash].c === CLS.W)) { return f({ nume, ...rez({ c: CLS.W, qv: true, motiv: `DSA with ${bits ? bits + '-bit parameters' : 'a weak hash'} is below current minimums; FIPS 186-5 no longer approves DSA for generating signatures; also ${SHOR}.`, rec: REC.SIG, q: 0 }) }); } return f({ nume, ...rez({ c: CLS.V, qv: true, motiv: `DSA: ${SHOR} (discrete logarithm). FIPS 186-5 no longer approves DSA for generating signatures.`, rec: REC.SIG, q: 0 }) }); } if (g === 'DH') { const bits = a.param && /^\d+$/.test(a.param) ? Number(a.param) : null; const nume = a.nume || (a.param ? `DH-${a.param}` : 'DH'); if (bits && bits < 2048) { return f({ nume, ...rez({ c: CLS.W, qv: true, motiv: `Finite-field Diffie-Hellman with a ${bits}-bit group is below the 2048-bit minimum; also ${SHOR} (discrete logarithm).`, rec: REC.KEX, q: 0 }) }); } return f({ nume, ...rez({ c: CLS.V, qv: true, motiv: `Finite-field Diffie-Hellman: ${SHOR} (discrete logarithm), so recorded key exchanges can be decrypted later.`, rec: REC.KEX, q: 0 }) }); } if (g === 'EC' || g === 'ECDSA' || g === 'ECDH' || g === 'SECP256K1') { const curba = curbaCanonica(a.curba) || (g === 'SECP256K1' ? 'secp256k1' : null); const e256k1 = curba === 'secp256k1'; const eticheta = g === 'SECP256K1' ? 'ECDSA' : g; const nume = a.nume || (curba ? `${eticheta}-${curba}` : eticheta); const cl = curba ? NIVEL_CURBA[curba] : undefined; const prim = a.primitiv || (g === 'ECDH' ? 'key-agree' : (g === 'EC' ? 'other' : 'signature')); let rec = g === 'ECDH' ? REC.KEX : (g === 'EC' ? `Signatures: ${REC.SIG} Key agreement: ${REC.KEX}` : REC.SIG); if (e256k1 && g !== 'ECDH') rec = REC.SECP256K1; if (a.context === 'jwt') rec = REC.JWT; const baza = { nume, primitiv: prim, curba: curba || undefined, grup: e256k1 ? 'SECP256K1' : g }; if (cl !== undefined && cl < 112) { return f({ ...baza, ...rez({ c: CLS.W, qv: true, motiv: `Elliptic curve ${curba} gives about ${cl}-bit classical security, below the 112-bit minimum; also ${SHOR} (elliptic-curve discrete logarithm).`, rec, q: 0, cl }) }); } if (a.hash && HASH[a.hash] && HASH[a.hash].c === CLS.W) { return f({ ...baza, ...rez({ c: CLS.W, qv: true, motiv: `ECDSA over ${HASH[a.hash].n}, which has practical collision attacks; also ${SHOR}.`, rec: `${REC.HASH_WEAK} ${rec}`, q: 0 }) }); } const pe = curba ? ` on ${curba}` : ''; const motiv = e256k1 ? `ECDSA on secp256k1: ${SHOR} (elliptic-curve discrete logarithm). On public blockchains the public key is visible on chain after the first signed transaction.` : `${eticheta}${pe}: ${SHOR} (elliptic-curve discrete logarithm).`; return f({ ...baza, ...rez({ c: CLS.V, qv: true, motiv, rec, q: 0, cl, oid: '1.2.840.10045.2.1' }) }); } if (g === 'EDDSA') { const nume = a.nume || 'Ed25519'; const e448 = /448/.test(nume); const rec = a.context === 'jwt' ? REC.JWT : REC.SIG; return f({ nume, primitiv: 'signature', curba: e448 ? 'Edwards448' : 'Edwards25519', ...rez({ c: CLS.V, qv: true, motiv: `${nume}: ${SHOR} (elliptic-curve discrete logarithm).`, rec, q: 0, cl: e448 ? 224 : 128, oid: e448 ? '1.3.101.113' : '1.3.101.112' }) }); } if (g === 'XDH') { const nume = a.nume || 'X25519'; const e448 = /448/.test(nume); return f({ nume, primitiv: 'key-agree', curba: e448 ? 'Curve448' : 'Curve25519', ...rez({ c: CLS.V, qv: true, motiv: `${nume} key agreement: ${SHOR} (elliptic-curve discrete logarithm), so recorded key exchanges can be decrypted later.`, rec: REC.KEX, q: 0, cl: e448 ? 224 : 128, oid: e448 ? '1.3.101.111' : '1.3.101.110' }) }); } if (g === 'PAIRING') { return f({ nume: a.nume || 'BLS12-381', primitiv: a.primitiv || 'signature', ...rez({ c: CLS.V, qv: true, motiv: `${a.nume || 'Pairing-based cryptography'}: ${SHOR} (discrete logarithm in the pairing groups).`, rec: REC.SIG, q: 0 }) }); } if (g === 'CLASSIC-SIG') { // semnatura clasica al carei tip de cheie nu se vede (RSA, DSA sau ECDSA) const h = a.hash && HASH[a.hash]; const nume = a.nume || `signature-with-${h ? h.n : 'unknown-hash'}`; if (h && h.c === CLS.W) { return f({ nume, primitiv: 'signature', ...rez({ c: CLS.W, qv: true, motiv: `${a.motiv || 'Classical signature'} over ${h.n}, which has practical collision attacks; the key type (RSA, DSA or ECDSA) is also quantum-vulnerable.`, rec: `${REC.HASH_WEAK} ${REC.SIG}`, q: 0 }) }); } return f({ nume, primitiv: 'signature', ...rez({ c: CLS.V, qv: true, motiv: `${a.motiv || 'Classical signature (RSA, DSA or ECDSA key)'}: ${SHOR}.`, rec: REC.SIG, q: 0 }) }); } if (g === 'HASH') { const h = HASH[a.hash]; if (!h) return f({ nume: a.nume || String(a.hash), primitiv: 'hash', ...rez({ c: CLS.U, motiv: `Hash "${a.hash}" is not in this tool's catalog.`, rec: REC.UNKNOWN }) }); const nota = a.nota ? ` ${a.nota}` : ''; if (h.c === CLS.W) { const motiv = a.hash === 'RIPEMD160' ? `RIPEMD-160 has a 160-bit output (about 80-bit collision resistance), below the 112-bit minimum for new designs.${nota}` : `${h.n} has practical collision attacks; unsafe for signatures, certificates and integrity against an adversary.${nota}`; return f({ nume: h.n, primitiv: 'hash', ...rez({ c: CLS.W, motiv, rec: REC.HASH_WEAK, q: 0, oid: h.oid }) }); } return f({ nume: h.n, primitiv: h.xof ? 'xof' : 'hash', ...rez({ c: CLS.S, motiv: `${h.n}: quantum attacks give at most a polynomial speed-up for collisions and preimages (Grover, BHT); output size keeps it within NIST categories.${nota}`, q: h.q, cl: h.cl, oid: h.oid }) }); } if (g === 'MAC') { if (!a.hash) return f({ nume: 'HMAC', primitiv: 'mac', ...rez({ c: CLS.U, motiv: 'HMAC whose hash function is bound to the key object (set where the key is imported or generated), not visible at this call.', rec: REC.UNKNOWN }) }); const h = HASH[a.hash]; const nume = `HMAC-${h ? h.n : String(a.hash || 'unknown')}`; if (!h) return f({ nume, primitiv: 'mac', ...rez({ c: CLS.U, motiv: `HMAC hash "${a.hash}" is not in this tool's catalog.`, rec: REC.UNKNOWN }) }); if (h.c === CLS.W) { return f({ nume, primitiv: 'mac', ...rez({ c: CLS.W, motiv: `${nume}: HMAC does not depend on collision resistance and has no practical break, but ${h.n} is deprecated for new designs and should be retired.`, rec: 'Move to HMAC-SHA-256 or HMAC-SHA-384.', q: 0 }) }); } return f({ nume, primitiv: 'mac', ...rez({ c: CLS.S, motiv: `${nume}: symmetric; a quantum attacker gains at most a quadratic speed-up (Grover) on key search.` }) }); } if (g === 'KDF') { const h = a.hash ? HASH[a.hash] : null; const nume = a.nume || 'KDF'; if (h && h.c === CLS.W) { return f({ nume, primitiv: 'kdf', ...rez({ c: CLS.W, motiv: `${nume} uses ${h.n}; not practically broken as a KDF, but deprecated for new designs.`, rec: 'Use PBKDF2-HMAC-SHA-256 (high iteration count), scrypt or Argon2id.', q: 0 }) }); } return f({ nume, primitiv: 'kdf', ...rez({ c: CLS.S, motiv: `${nume}: symmetric key derivation; a quantum attacker gains at most a quadratic speed-up (Grover).` }) }); } if (g === 'CIPHER') return cifru(a, f); if (g === 'MLKEM') { const p = String(a.param || ''); const q = p === '512' ? 1 : p === '768' ? 3 : p === '1024' ? 5 : undefined; const oid = p === '512' ? '2.16.840.1.101.3.4.4.1' : p === '768' ? '2.16.840.1.101.3.4.4.2' : p === '1024' ? '2.16.840.1.101.3.4.4.3' : undefined; const nume = p ? `ML-KEM-${p}` : 'ML-KEM'; return f({ nume, primitiv: 'kem', ...rez({ c: CLS.S, motiv: `${nume}: module-lattice KEM standardized in FIPS 203${q ? `, NIST category ${q}` : ' (parameter set not visible)'}.`, q, oid }) }); } if (g === 'MLDSA') { const p = String(a.param || ''); const q = p === '44' ? 2 : p === '65' ? 3 : p === '87' ? 5 : undefined; const oid = p === '44' ? '2.16.840.1.101.3.4.3.17' : p === '65' ? '2.16.840.1.101.3.4.3.18' : p === '87' ? '2.16.840.1.101.3.4.3.19' : undefined; const nume = p ? `ML-DSA-${p}` : 'ML-DSA'; return f({ nume, primitiv: 'signature', ...rez({ c: CLS.S, motiv: `${nume}: module-lattice signature standardized in FIPS 204${q ? `, NIST category ${q}` : ' (parameter set not visible)'}.`, q, oid }) }); } if (g === 'SLHDSA') { const p = String(a.param || ''); const m = /(128|192|256)/.exec(p); const q = m ? ({ 128: 1, 192: 3, 256: 5 })[m[1]] : undefined; const nume = p ? `SLH-DSA-${p}` : 'SLH-DSA'; return f({ nume, primitiv: 'signature', ...rez({ c: CLS.S, motiv: `${nume}: stateless hash-based signature standardized in FIPS 205${q ? `, NIST category ${q}` : ''}.`, q }) }); } if (g === 'FALCON') { const p = String(a.param || ''); const q = p === '512' ? 1 : p === '1024' ? 5 : undefined; const nume = p ? `Falcon-${p}` : 'Falcon'; return f({ nume, primitiv: 'signature', ...rez({ c: CLS.S, motiv: `${nume}: NTRU-lattice signature selected by NIST (to be standardized as FN-DSA)${q ? `, NIST category ${q}` : ''}.`, q }) }); } if (g === 'HASHSIG') { return f({ nume: a.nume || 'XMSS/LMS', primitiv: 'signature', ...rez({ c: CLS.S, motiv: `${a.nume || 'Stateful hash-based signature'}: approved in NIST SP 800-208; security depends on never reusing a one-time key state.` }) }); } if (g === 'PREPQ') { return f({ nume: a.nume, primitiv: a.primitiv, ...rez({ c: CLS.S, motiv: `${a.nume}: pre-standard version of a NIST-selected post-quantum scheme.`, rec: REC.PREPQ }) }); } if (g === 'BROKENPQ') { return f({ nume: a.nume, primitiv: a.primitiv, ...rez({ c: CLS.W, motiv: `${a.nume} was broken by classical attacks in 2022.`, rec: REC.BROKEN_PQ, q: 0 }) }); } if (g === 'HQC') { return f({ nume: a.nume || 'HQC', primitiv: 'kem', ...rez({ c: CLS.S, motiv: 'HQC: code-based KEM selected by NIST in 2025 as a second KEM; final standard pending.' }) }); } if (g === 'HYBRID-KEX') { const pq = /1024/.test(a.nume) ? 5 : 3; return f({ nume: a.nume, primitiv: 'kem', ...rez({ c: CLS.S, motiv: `${a.nume}: hybrid key exchange (classical ECDH + ML-KEM); stays secure if either component holds. The NIST category given is that of the ML-KEM component.`, q: pq }) }); } if (g === 'TLS') return tls(a, f); if (g === 'JWT-HMAC') { const h = HASH[a.hash]; return f({ nume: a.nume, primitiv: 'mac', ...rez({ c: CLS.S, motiv: `${a.nume} is HMAC-${h ? h.n : a.hash}: symmetric, so a quantum attacker gains at most a quadratic speed-up (Grover); the shared secret must be long and random.` }) }); } if (g === 'JWT-NONE') { return f({ nume: 'JWS none', primitiv: 'signature', ...rez({ c: CLS.W, motiv: 'The "none" algorithm means the token is not signed at all.', rec: REC.JWT_NONE, q: 0 }) }); } if (g === 'LIB-MULTI') { return f({ nume: a.nume, primitiv: 'unknown', ...rez({ c: CLS.U, motiv: a.motiv || `Imports ${a.nume}; no specific algorithm call recognized in this file.`, rec: REC.LIB_MULTI }) }); } if (g === 'SSH') { return f({ nume: a.nume || 'SSH', primitiv: 'unknown', ...rez({ c: CLS.U, motiv: a.motiv || 'SSH: key exchange and host-key algorithms are negotiated at run time and not visible here.', rec: 'Check the configured key-exchange algorithms: OpenSSH 9.9+ offers mlkem768x25519-sha256 and 9.0+ sntrup761x25519-sha512; host keys remain classical (Ed25519, ECDSA, RSA).' }) }); } return f(rez({ c: CLS.U, motiv: `No classification rule for group "${g}".`, rec: REC.UNKNOWN })); } function cifru(a, f) { const alg = String(a.nume || '').toUpperCase(); const mod = a.mod ? String(a.mod).toLowerCase() : undefined; if (/^(DES|DES-?CBC|DES-?ECB|DES-?CFB|DES-?OFB)$/.test(alg) || alg === 'DES') { return f({ nume: mod ? `DES-${mod.toUpperCase()}` : 'DES', primitiv: 'block-cipher', mod, ...rez({ c: CLS.W, motiv: 'DES has a 56-bit key and is brute-forceable today.', rec: REC.CIPHER_WEAK, q: 0 }) }); } if (/^(3DES|DESEDE|DES-?EDE3?|TRIPLEDES|TDEA|DES3)$/.test(alg)) { return f({ nume: mod ? `3DES-${mod.toUpperCase()}` : '3DES', primitiv: 'block-cipher', mod, ...rez({ c: CLS.W, motiv: '3DES (TDEA) has a 64-bit block (Sweet32 birthday attacks) and was disallowed by NIST after 2023.', rec: REC.CIPHER_WEAK, q: 0 }) }); } if (/^(RC4|ARC4|ARCFOUR)$/.test(alg)) { return f({ nume: 'RC4', primitiv: 'stream-cipher', ...rez({ c: CLS.W, motiv: 'RC4 has exploitable keystream biases and is prohibited in TLS (RFC 7465).', rec: REC.CIPHER_WEAK, q: 0 }) }); } if (/^(RC2|ARC2)$/.test(alg)) { return f({ nume: 'RC2', primitiv: 'block-cipher', ...rez({ c: CLS.W, motiv: 'RC2 is an obsolete cipher with a 64-bit block.', rec: REC.CIPHER_WEAK, q: 0 }) }); } if (/^(BLOWFISH|BF)$/.test(alg)) { return f({ nume: 'Blowfish', primitiv: 'block-cipher', mod, ...rez({ c: CLS.W, motiv: 'Blowfish has a 64-bit block (Sweet32 birthday attacks).', rec: REC.CIPHER_WEAK, q: 0 }) }); } if (/^(CAST5|CAST|IDEA|SEED)$/.test(alg)) { return f({ nume: alg, primitiv: 'block-cipher', mod, ...rez({ c: CLS.W, motiv: `${alg} has a 64-bit block (Sweet32 birthday attacks) or is obsolete.`, rec: REC.CIPHER_WEAK, q: 0 }) }); } if (/^CHACHA20(-POLY1305)?$|^XCHACHA20-POLY1305$|^XSALSA20-POLY1305$|^SALSA20$/.test(alg)) { const ae = /POLY1305/.test(alg); return f({ nume: a.nume, primitiv: ae ? 'ae' : 'stream-cipher', ...rez({ c: CLS.S, motiv: `${a.nume}: 256-bit key; a quantum attacker gains at most a quadratic speed-up (Grover) on key search.` }) }); } if (/^AES/.test(alg) || /^CAMELLIA/.test(alg) || /^SM4/.test(alg) || /^ARIA/.test(alg)) { const familie = /^CAMELLIA/.test(alg) ? 'Camellia' : /^SM4/.test(alg) ? 'SM4' : /^ARIA/.test(alg) ? 'ARIA' : 'AES'; const bits = a.param ? Number(a.param) : (familie === 'SM4' ? 128 : null); const numeMod = mod ? `-${mod.toUpperCase()}` : ''; const nume = `${familie}${bits ? '-' + bits : ''}${numeMod}`; const ae = mod && /^(gcm|ccm|ocb|siv|gcm-siv|eax|poly1305)$/.test(mod); const prim = ae ? 'ae' : 'block-cipher'; const modCdx = mod && ['cbc', 'ecb', 'ccm', 'gcm', 'cfb', 'ofb', 'ctr'].includes(mod) ? mod : (mod ? 'other' : undefined); if (mod === 'ecb') { return f({ nume, primitiv: prim, mod: modCdx, ...rez({ c: CLS.W, motiv: `${nume}: ECB mode encrypts equal blocks to equal ciphertext and leaks plaintext structure.${a.nota ? ' ' + a.nota : ''}`, rec: REC.ECB, q: 0 }) }); } const q = bits === 128 ? 1 : bits === 192 ? 3 : bits === 256 ? 5 : undefined; const cl = bits || undefined; const rec = bits === 128 ? REC.AES128 : (bits ? null : REC.AES_SIZE); const motiv = bits === 128 ? `${nume}: symmetric; Grover's algorithm reduces key search at most quadratically, NIST category 1.` : bits ? `${nume}: symmetric ${bits}-bit key; Grover's algorithm reduces key search at most quadratically, NIST category ${q}.` : `${nume}: symmetric; key size not visible statically (AES-128 is NIST category 1, AES-256 category 5).`; return f({ nume, primitiv: prim, mod: modCdx, param: bits ? String(bits) : undefined, ...rez({ c: CLS.S, motiv, rec, q, cl }) }); } return f({ nume: a.nume, primitiv: 'unknown', ...rez({ c: CLS.U, motiv: `Cipher "${a.nume}" is not in this tool's catalog.`, rec: REC.UNKNOWN }) }); } // a.param = versiunea ('1.0','1.1','1.2','1.3','ssl3','negotiated'); a.rol = 'min'|'max'|'only' function tls(a, f) { const v = String(a.param || ''); const rol = a.rol || 'only'; const numeV = v === 'ssl3' ? 'SSLv3' : v === 'ssl2' ? 'SSLv2' : (v === 'negotiated' ? 'TLS (negotiated)' : `TLSv${v}`); const nume = a.nume || numeV; const baza = { nume, primitiv: 'other', protocolType: 'tls', protocolVersion: /^1\.[0-3]$/.test(v) ? v : (v === 'ssl3' ? '3.0' : undefined), rol }; if (v === 'ssl2' || v === 'ssl3' || v === '1.0' || v === '1.1') { if (rol === 'max') { return f({ ...baza, ...rez({ c: CLS.W, qv: true, motiv: `${numeV} as the maximum version caps the connection at a deprecated protocol (RFC 8996).`, rec: REC.TLS_OLD, q: 0 }) }); } return f({ ...baza, ...rez({ c: CLS.W, qv: true, motiv: `${numeV} is deprecated (RFC 8996) and ${rol === 'min' ? 'allowed as the minimum version' : 'selected'} here.`, rec: REC.TLS_OLD, q: 0 }) }); } if (v === '1.2') { if (rol === 'min') { return f({ ...baza, ...rez({ c: CLS.V, qv: true, motiv: 'TLS 1.2 is allowed; TLS 1.2 has no standardized post-quantum key exchange, so a peer that negotiates it uses classical (EC)DHE or RSA key exchange.', rec: REC.TLS12, q: 0 }) }); } return f({ ...baza, ...rez({ c: CLS.V, qv: true, motiv: `TLS 1.2 ${rol === 'max' ? 'is the maximum version' : 'is selected'}; TLS 1.2 has no standardized post-quantum key exchange, so key exchange is classical (EC)DHE or RSA.`, rec: REC.TLS12, q: 0 }) }); } if (v === '1.3') { if (rol === 'max') { return f({ ...baza, ...rez({ c: CLS.U, motiv: 'TLS 1.3 is the maximum version; the minimum and the key-exchange groups are not visible here.', rec: REC.TLS13 }) }); } return f({ ...baza, ...rez({ c: CLS.U, motiv: 'TLS 1.3 only: whether key exchange is post-quantum depends on the negotiated group (X25519MLKEM768 or a classical group), which depends on runtime defaults and the peer.', rec: REC.TLS13 }) }); } return f({ ...baza, ...rez({ c: CLS.U, motiv: 'Protocol version is negotiated at run time from the runtime defaults; not visible statically.', rec: REC.TLS13 }) }); } // grupurile de schimb de chei TLS (ecdhCurve, CurvePreferences, jdk.tls.namedGroups) export function grupTls(nume) { const t = String(nume).trim(); if (/^(X25519MLKEM768|X25519Kyber768Draft00|SecP256r1MLKEM768|SecP384r1MLKEM1024|p256_mlkem768|p384_mlkem1024|x25519_mlkem768)$/i.test(t)) { if (/kyber/i.test(t)) return { grup: 'PREPQ', nume: t, primitiv: 'kem' }; return { grup: 'HYBRID-KEX', nume: t }; } if (/^(X25519|x25519)$/.test(t)) return { grup: 'XDH', nume: 'X25519' }; if (/^(X448|x448)$/.test(t)) return { grup: 'XDH', nume: 'X448' }; if (/^(MLKEM(512|768|1024)|mlkem(512|768|1024))$/i.test(t)) return { grup: 'MLKEM', param: t.replace(/\D/g, '') }; if (/^(ffdhe\d+)$/i.test(t)) return { grup: 'DH', param: t.toLowerCase() }; const c = curbaCanonica(t); if (c && /^secp|^brainpool/.test(c)) return { grup: 'ECDH', curba: c }; if (/^auto$/i.test(t)) return { grup: 'UNKNOWN', motiv: 'ecdhCurve "auto" selects groups from the runtime defaults; not visible statically.' }; return { grup: 'UNKNOWN', motiv: `Key-exchange group "${t}" is not in this tool's catalog.` }; } // algoritmii JWS (RFC 7518 + RFC 8037 + RFC 8812) export function jws(alg) { const m = /^(HS|RS|PS|ES)(256|384|512)$/.exec(alg); if (m) { const hash = 'SHA' + m[2]; if (m[1] === 'HS') return { grup: 'JWT-HMAC', nume: `JWS ${alg}`, hash, context: 'jwt' }; if (m[1] === 'RS') return { grup: 'RSA', nume: `JWS ${alg}`, primitiv: 'signature', padding: 'pkcs1v15', hash, context: 'jwt' }; if (m[1] === 'PS') return { grup: 'RSA', nume: `JWS ${alg}`, primitiv: 'signature', padding: 'other', hash, context: 'jwt' }; const curba = m[2] === '256' ? 'secp256r1' : m[2] === '384' ? 'secp384r1' : 'secp521r1'; return { grup: 'ECDSA', nume: `JWS ${alg}`, curba, hash, context: 'jwt' }; } if (alg === 'ES256K') return { grup: 'ECDSA', nume: 'JWS ES256K', curba: 'secp256k1', context: 'jwt' }; if (alg === 'EdDSA' || alg === 'Ed25519') return { grup: 'EDDSA', nume: `JWS ${alg}`, context: 'jwt' }; if (alg === 'none') return { grup: 'JWT-NONE' }; return null; } export const JWS_RE = /^(?:(?:HS|RS|PS|ES)(?:256|384|512)|ES256K|EdDSA|Ed25519|none)$/;