def _judge_curve_embedding_degree(self, curve): d = 1 for k in range(1, 50 + 1): d = (d * curve.p) % curve.n if d == 1: if (k == 1) or (NumberTheory.is_probable_prime(curve.n)): fail_text = "k = %d" % (k) else: fail_text = "k <= %d" % (k) literature = LiteratureReference( author=[ "Alfred Menezes", "Scott Vanstone", "Tatsuaki Okamoto" ], title= "Reducing Elliptic Curve Logarithms to Logarithms in a Finite Field", year=1991, source="ACM") return SecurityJudgement( JudgementCode. X509Cert_PublicKey_ECC_DomainParameters_CurveProperty_LowEmbeddingDegree, "This curve has low embedding degree (%s), it fails the MOV condition. It can be compromised using the probabilistic polynomial-time MOV attack." % (fail_text), bits=0, commonness=Commonness.HIGHLY_UNUSUAL, literature=literature) return None
def test_probable_prime(self): self.assertTrue(NumberTheory.is_probable_prime(2)) self.assertTrue(NumberTheory.is_probable_prime(3)) self.assertTrue(NumberTheory.is_probable_prime(5)) self.assertTrue(NumberTheory.is_probable_prime(101)) self.assertFalse(NumberTheory.is_probable_prime(0)) self.assertFalse(NumberTheory.is_probable_prime(1)) self.assertFalse(NumberTheory.is_probable_prime(4))
def _select_q(self, p): if not self._args.close_q: while True: yield self._prime_db.get(bitlen=self._q_bitlen, primetype=self._primetype) else: q = p while True: q += 2 * random.randint(1, self._args.q_stepping) if NumberTheory.is_probable_prime(q): yield q
def _judge_binary_field_curve(self, curve): judgements = SecurityJudgements() literature = LiteratureReference( author=["Steven D. Galbraith", "Shishay W. Gebregiyorgis"], title= "Summation polynomial algorithms for elliptic curves in characteristic two", year=2014, source="Progress in Cryptology -- INDOCRYPT 2014; LNCS 8885") judgements += SecurityJudgement( JudgementCode.X509Cert_PublicKey_ECC_DomainParameters_BinaryField, "Binary finite field elliptic curve is used. Recent advances in cryptography show there might be efficient attacks on such curves, hence it is recommended to use prime-field curves instead.", commonness=Commonness.UNUSUAL, literature=literature) EFpm = curve.n * curve.h if (EFpm % 2) == 1: # Supersingular: #E(F_p^m) = 1 mod p literature = LiteratureReference( author=[ "Alfred Menezes", "Scott Vanstone", "Tatsuaki Okamoto" ], title= "Reducing Elliptic Curve Logarithms to Logarithms in a Finite Field", year=1991, source="ACM") judgements += SecurityJudgement( JudgementCode. X509Cert_PublicKey_ECC_DomainParameters_CurveProperty_SupersingularCurve, "This curve is supersingular, #E(F_p^m) = 1 mod p. The curve can be attacked using an probabilistic polynomial-time MOV attack.", bits=0, commonness=Commonness.HIGHLY_UNUSUAL, literature=literature) if not NumberTheory.is_probable_prime(curve.m): literature = LiteratureReference( author=[ "Jeffrey Hoffstein", "Jill Pipher", "Joseph Silverman" ], title="An Introduction to Mathematical Cryptography", year=2008, source="Springer") judgements += SecurityJudgement( JudgementCode. X509Cert_PublicKey_ECC_DomainParameters_CurveProperty_WeilDescent, "Binary finite field elliptic curve has a field size that is non-primem, F(2^%d). Weil Descent attacks could be successful.", bits=0, commonness=Commonness.HIGHLY_UNUSUAL, literature=literature) return judgements
def __init__(self, cmdname, args): BaseAction.__init__(self, cmdname, args) if (not self._args.force) and os.path.exists(self._args.outfile): raise UnfulfilledPrerequisitesException( "File/directory %s already exists. Remove it first or use --force." % (self._args.outfile)) self._prime_db = PrimeDB(self._args.prime_db, generator_program=self._args.generator) q = self._prime_db.get(args.N_bits) if self._args.verbose >= 1: print("Chosen q = 0x%x" % (q)) bit_diff = args.L_bits - q.bit_length() while True: r = NumberTheory.randint_bits(bit_diff, two_msb_set=True) p = (r * q) + 1 if NumberTheory.is_probable_prime(p): break if self._args.verbose >= 1: print("Chosen p = 0x%x" % (p)) assert (q.bit_length() == args.N_bits) assert (p.bit_length() == args.L_bits) assert ((p - 1) % q == 0) # Non-verifiable method of generating g, see A.2.1 of FIPS 186-4, pg. 41 e = (p - 1) // q while True: h = random.randint(2, p - 2) g = pow(h, e, p) if g == 1: continue break if self._args.verbose >= 1: print("Chosen g = 0x%x" % (g)) dsa_parameters = DSAParameters.create(p=p, q=q, g=g) dsa_parameters.write_pemfile(self._args.outfile)
def __init__(self, cmdname, args): BaseAction.__init__(self, cmdname, args) if (not self._args.force) and os.path.exists(self._args.outfile): raise UnfulfilledPrerequisitesException( "File/directory %s already exists. Remove it first or use --force." % (self._args.outfile)) if not self._args.gcd_n_phi_n: self._primetype = "2msb" self._p_bitlen = self._args.bitlen // 2 self._q_bitlen = self._args.bitlen - self._p_bitlen else: self._primetype = "3msb" self._p_bitlen = self._args.bitlen // 3 self._q_bitlen = self._args.bitlen - (2 * self._p_bitlen) - 1 if (self._args.close_q) and (self._p_bitlen != self._q_bitlen): raise UnfulfilledPrerequisitesException( "Generating a close-q keypair with a %d modulus does't work, because p would have to be %d bit and q %d bit. Choose an even modulus bitlength." % (self._args.bitlen, self._p_bitlen, self._q_bitlen)) if self._args.q_stepping < 1: raise InvalidInputException( "q-stepping value must be greater or equal to 1, was %d." % (self._args.q_stepping)) self._log.debug("Selecting %s primes with p = %d bit and q = %d bit.", self._primetype, self._p_bitlen, self._q_bitlen) self._prime_db = PrimeDB(self._args.prime_db, generator_program=self._args.generator) p = None q = None while True: if p is None: p = self._prime_db.get(bitlen=self._p_bitlen, primetype=self._primetype) q_generator = self._select_q(p) if q is None: q = next(q_generator) if self._args.gcd_n_phi_n: # q = (2 * r * p) + 1 r = q q = 2 * r * p + 1 if not NumberTheory.is_probable_prime(q): q = None continue # Always make p the smaller factor if p > q: (p, q) = (q, p) n = p * q if self._args.public_exponent == -1: e = random.randint(2, n - 1) else: e = self._args.public_exponent if self._args.carmichael_totient: totient = NumberTheory.lcm(p - 1, q - 1) else: totient = (p - 1) * (q - 1) gcd = NumberTheory.gcd(totient, e) if self._args.accept_unusable_key or (gcd == 1): break else: # Pair (phi(n), e) wasn't acceptable. self._log.debug("gcd(totient, e) was %d, retrying.", gcd) if self._args.public_exponent != -1: # Public exponent e is fixed, need to choose another q. if p.bit_length() == q.bit_length(): # Can re-use q as next p (p, q) = (q, None) q_generator = self._select_q(p) else: # When they differ in length, need to re-choose both values (p, q) = (None, None) rsa_keypair = RSAPrivateKey.create( p=p, q=q, e=e, swap_e_d=self._args.switch_e_d, valid_only=not self._args.accept_unusable_key, carmichael_totient=self._args.carmichael_totient) rsa_keypair.write_pemfile(self._args.outfile) if self._args.verbose >= 1: diff = q - p print("Generated %d bit RSA key:" % (rsa_keypair.n.bit_length())) print("p = 0x%x" % (rsa_keypair.p)) if not self._args.gcd_n_phi_n: print("q = 0x%x" % (rsa_keypair.q)) else: print("q = 2 * r * p + 1 = 0x%x" % (rsa_keypair.q)) print("r = 0x%x" % (r)) print("phi(n) = 0x%x" % (rsa_keypair.phi_n)) print("lambda(n) = 0x%x" % (rsa_keypair.lambda_n)) print("phi(n) / lambda(n) = gcd(p - 1, q - 1) = %d" % (rsa_keypair.phi_n // rsa_keypair.lambda_n)) gcd_n_phin = NumberTheory.gcd(rsa_keypair.n, rsa_keypair.phi_n) if gcd_n_phin == rsa_keypair.p: print("gcd(n, phi(n)) = p") else: print("gcd(n, phi(n)) = 0x%x" % (gcd_n_phin)) if self._args.close_q: print("q - p = %d (%d bit)" % (diff, diff.bit_length())) print("n = 0x%x" % (rsa_keypair.n)) print("d = 0x%x" % (rsa_keypair.d)) print("e = 0x%x" % (rsa_keypair.e))
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