def analyze_n(self, n): judgements = SecurityJudgements() if n < 0: judgements += SecurityJudgement( JudgementCode.X509Cert_PublicKey_RSA_Modulus_Negative, "Modulus uses incorrect encoding, representation is a negative integer.", commonness=Commonness.HIGHLY_UNUSUAL, compatibility=Compatibility.STANDARDS_DEVIATION) # Fix up n so it's a positive integer for the rest of the tests bitlen = (n.bit_length() + 7) // 8 * 8 mask = (1 << bitlen) - 1 n = n & mask elif n == 0: judgements += SecurityJudgement( JudgementCode.X509Cert_PublicKey_RSA_Modulus_Zero, "Modulus is zero, this is definitely a broken RSA public key.", bits=0, commonness=Commonness.HIGHLY_UNUSUAL, compatibility=Compatibility.STANDARDS_DEVIATION) elif n == 1: judgements += SecurityJudgement( JudgementCode.X509Cert_PublicKey_RSA_Modulus_One, "Modulus is one, this is definitely a broken RSA public key.", bits=0, commonness=Commonness.HIGHLY_UNUSUAL, compatibility=Compatibility.STANDARDS_DEVIATION) if self._test_probable_prime: if NumberTheory.is_probable_prime(n): judgements += SecurityJudgement( JudgementCode.X509Cert_PublicKey_RSA_Modulus_Prime, "Modulus is prime, not a compound integer as we would expect for RSA.", bits=0) if self._pollards_rho_iterations > 0: small_factor = NumberTheory.pollard_rho( n, max_iterations=self._pollards_rho_iterations) if small_factor is not None: judgements += SecurityJudgement( JudgementCode.X509Cert_PublicKey_RSA_Modulus_Factorable, "Modulus has small factor (%d) and is therefore trivially factorable." % (small_factor), bits=0) match = ModulusDB().find(n) if match is not None: judgements += SecurityJudgement( JudgementCode. X509Cert_PublicKey_RSA_Modulus_FactorizationKnown, "Modulus is known to be compromised: %s" % (match.text), bits=0) hweight_analysis = NumberTheory.hamming_weight_analysis(n) if (hweight_analysis is not None) and (not hweight_analysis.plausibly_random): judgements += SecurityJudgement( JudgementCode.X509Cert_PublicKey_RSA_Modulus_BitBiasPresent, "Modulus does not appear to be random. Expected a Hamming weight between %d and %d for a %d bit modulus, but found Hamming weight %d." % (hweight_analysis.rnd_min_hweight, hweight_analysis.rnd_max_hweight, hweight_analysis.bitlen, hweight_analysis.hweight), commonness=Commonness.HIGHLY_UNUSUAL) # We estimate the complexity of factoring the modulus by the asymptotic # complexity of the GNFS. bits_security = NumberTheory.asymtotic_complexity_gnfs_bits(n) judgements += self.algorithm("bits").analyze( JudgementCode.X509Cert_PublicKey_RSA_Modulus_LengthInBits, bits_security) return judgements
def analyze(self, pubkey): judgements = SecurityJudgements() L = pubkey.p.bit_length() N = pubkey.q.bit_length() if not NumberTheory.is_probable_prime(pubkey.p): standard = LiteratureReference( quote="p: a prime modulus", sect="4.1", author="National Institute of Standards and Technology", title="FIPS PUB 186-4: Digital Signature Standard (DSS)", year=2013, month=7, doi="10.6028/NIST.FIPS.186-4") judgements += SecurityJudgement( JudgementCode.X509Cert_PublicKey_DSA_Parameters_P_NotPrime, "DSA parameter p is not prime.", commonness=Commonness.HIGHLY_UNUSUAL, compatibility=Compatibility.STANDARDS_DEVIATION, bits=0, standard=standard) if not NumberTheory.is_probable_prime(pubkey.q): standard = LiteratureReference( quote="q: a prime divisor of (p - 1)", sect="4.1", author="National Institute of Standards and Technology", title="FIPS PUB 186-4: Digital Signature Standard (DSS)", year=2013, month=7, doi="10.6028/NIST.FIPS.186-4") judgements += SecurityJudgement( JudgementCode.X509Cert_PublicKey_DSA_Parameters_Q_NotPrime, "DSA parameter q is not prime.", commonness=Commonness.HIGHLY_UNUSUAL, compatibility=Compatibility.STANDARDS_DEVIATION, bits=0, standard=standard) if ((pubkey.p - 1) % pubkey.q) != 0: standard = LiteratureReference( quote="q: a prime divisor of (p - 1)", sect="4.1", author="National Institute of Standards and Technology", title="FIPS PUB 186-4: Digital Signature Standard (DSS)", year=2013, month=7, doi="10.6028/NIST.FIPS.186-4") judgements += SecurityJudgement( JudgementCode. X509Cert_PublicKey_DSA_Parameters_Q_NoDivisorOfP1, "DSA parameter q is not a divisor of (p - 1).", commonness=Commonness.HIGHLY_UNUSUAL, compatibility=Compatibility.STANDARDS_DEVIATION, bits=0, standard=standard) if pow(pubkey.g, pubkey.q, pubkey.p) != 1: judgements += SecurityJudgement( JudgementCode.X509Cert_PublicKey_DSA_Parameters_G_Invalid, "DSA parameter g is not valid. In particular, g^q mod p != 1.", commonness=Commonness.HIGHLY_UNUSUAL, bits=0) if (pubkey.g <= 1) or (pubkey.g >= pubkey.p): standard = LiteratureReference( quote= "g: a generator of a subgroup of order q in the multiplicative group of GF(p), such that 1 < g < p", sect="4.1", author="National Institute of Standards and Technology", title="FIPS PUB 186-4: Digital Signature Standard (DSS)", year=2013, month=7, doi="10.6028/NIST.FIPS.186-4") judgements += SecurityJudgement( JudgementCode.X509Cert_PublicKey_DSA_Parameters_G_InvalidRange, "DSA parameter g is not inside the valid range (1 < g < p).", commonness=Commonness.HIGHLY_UNUSUAL, compatibility=Compatibility.STANDARDS_DEVIATION, bits=0, standard=standard) hweight_analysis = NumberTheory.hamming_weight_analysis(pubkey.p) if not hweight_analysis.plausibly_random: judgements += SecurityJudgement( JudgementCode. X509Cert_PublicKey_DSA_Parameters_P_BitBiasPresent, "Hamming weight of DSA prime p is %d at bitlength %d, but expected a weight between %d and %d when randomly chosen; this is likely not coincidential." % (hweight_analysis.hweight, hweight_analysis.bitlen, hweight_analysis.rnd_min_hweight, hweight_analysis.rnd_max_hweight), commonness=Commonness.HIGHLY_UNUSUAL) hweight_analysis = NumberTheory.hamming_weight_analysis(pubkey.q) if not hweight_analysis.plausibly_random: judgements += SecurityJudgement( JudgementCode. X509Cert_PublicKey_DSA_Parameters_Q_BitBiasPresent, "Hamming weight of DSA prime q is %d at bitlength %d, but expected a weight between %d and %d when randomly chosen; this is likely not coincidential." % (hweight_analysis.hweight, hweight_analysis.bitlen, hweight_analysis.rnd_min_hweight, hweight_analysis.rnd_max_hweight), commonness=Commonness.HIGHLY_UNUSUAL) if (L in self._TYPICAL_L_N_VALUES) and ( N in self._TYPICAL_L_N_VALUES[L]): # Typical judgements += SecurityJudgement( JudgementCode.X509Cert_PublicKey_DSA_L_N_Common, "DSA parameter values L/N (%d/%d) are common." % (L, N), commonness=Commonness.COMMON) else: # Non-typical judgements += SecurityJudgement( JudgementCode.X509Cert_PublicKey_DSA_L_N_Uncommon, "DSA parameter values L/N (%d/%d) are uncommon." % (L, N), commonness=Commonness.UNUSUAL) L_strength_bits = NumberTheory.asymtotic_complexity_gnfs_bits(pubkey.p) N_strength_bits = math.floor(N / 2) bits_security = min(L_strength_bits, N_strength_bits) judgements += self.algorithm("bits").analyze( JudgementCode.X509Cert_PublicKey_DSA_L_N, bits_security) result = { "cryptosystem": "dsa", "specific": { "L": L, "N": N, }, "security": judgements, } if self._analysis_options.include_raw_data: result["specific"]["p"] = { "value": pubkey.p, } result["specific"]["q"] = { "value": pubkey.q, } result["specific"]["g"] = { "value": pubkey.g, } result["specific"]["pubkey"] = { "value": pubkey.pubkey, } return result