mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-07-01 03:22:48 -05:00
Refactor generation of candidate integers in primegen.
I've replaced the random number generation and small delta-finding loop in primegen() with a much more elaborate system in its own source file, with unit tests and everything. Immediate benefits: - fixes a theoretical possibility of overflowing the target number of bits, if the random number was so close to the top of the range that the addition of delta * factor pushed it over. However, this only happened with negligible probability. - fixes a directional bias in delta-finding. The previous code incremented the number repeatedly until it found a value coprime to all the right things, which meant that a prime preceded by a particularly long sequence of numbers with tiny factors was more likely to be chosen. Now we select candidate delta values at random, that bias should be eliminated. - changes the semantics of the outermost primegen() function to make them easier to use, because now the caller specifies the 'bits' and 'firstbits' values for the actual returned prime, rather than having to account for the factor you're multiplying it by in DSA. DSA client code is correspondingly adjusted. Future benefits: - having the candidate generation in a separate function makes it easy to reuse in alternative prime generation strategies - the available constraints support applications such as Maurer's algorithm for generating provable primes, or strong primes for RSA in which both p-1 and p+1 have a large factor. So those become things we could experiment with in future.
This commit is contained in:
@ -130,6 +130,9 @@ def mac_str(alg, key, message, cipher=None):
|
||||
ssh2_mac_update(m, message)
|
||||
return ssh2_mac_genresult(m)
|
||||
|
||||
def lcm(a, b):
|
||||
return a * b // gcd(a, b)
|
||||
|
||||
class MyTestBase(unittest.TestCase):
|
||||
"Intermediate class that adds useful helper methods."
|
||||
def assertEqualBin(self, x, y):
|
||||
@ -864,6 +867,78 @@ class ecc(MyTestBase):
|
||||
self.assertEqual(int(x), int(rGi.x))
|
||||
self.assertEqual(int(y), int(rGi.y))
|
||||
|
||||
class keygen(MyTestBase):
|
||||
def testPrimeCandidateSource(self):
|
||||
def inspect(pcs):
|
||||
# Returns (pcs->limit, pcs->factor, pcs->addend) as Python integers
|
||||
return tuple(map(int, pcs_inspect(pcs)))
|
||||
|
||||
# Test accumulating modular congruence requirements, by
|
||||
# inspecting the internal values computed during
|
||||
# require_residue. We ensure that the addend satisfies all our
|
||||
# congruences and the factor is the lcm of all the moduli
|
||||
# (hence, the arithmetic progression defined by those
|
||||
# parameters is precisely the set of integers satisfying the
|
||||
# requirements); we also ensure that the limiting values
|
||||
# (addend itself at the low end, and addend + (limit-1) *
|
||||
# factor at the high end) are the maximal subsequence of that
|
||||
# progression that are within the originally specified range.
|
||||
|
||||
def check(pcs, lo, hi, mod_res_pairs):
|
||||
limit, factor, addend = inspect(pcs)
|
||||
|
||||
for mod, res in mod_res_pairs:
|
||||
self.assertEqual(addend % mod, res % mod)
|
||||
|
||||
self.assertEqual(factor, functools.reduce(
|
||||
lcm, [mod for mod, res in mod_res_pairs]))
|
||||
|
||||
self.assertFalse(lo <= addend + (-1) * factor < hi)
|
||||
self.assertTrue (lo <= addend < hi)
|
||||
self.assertTrue (lo <= addend + (limit-1) * factor < hi)
|
||||
self.assertFalse(lo <= addend + limit * factor < hi)
|
||||
|
||||
pcs = pcs_new(64, 1, 1)
|
||||
check(pcs, 2**63, 2**64, [(2, 1)])
|
||||
pcs_require_residue(pcs, 3, 2)
|
||||
check(pcs, 2**63, 2**64, [(2, 1), (3, 2)])
|
||||
pcs_require_residue_1(pcs, 7)
|
||||
check(pcs, 2**63, 2**64, [(2, 1), (3, 2), (7, 1)])
|
||||
pcs_require_residue(pcs, 16, 7)
|
||||
check(pcs, 2**63, 2**64, [(2, 1), (3, 2), (7, 1), (16, 7)])
|
||||
pcs_require_residue(pcs, 49, 8)
|
||||
check(pcs, 2**63, 2**64, [(2, 1), (3, 2), (7, 1), (16, 7), (49, 8)])
|
||||
|
||||
# Now test-generate some actual values, and ensure they
|
||||
# satisfy all the congruences, and also avoid one residue mod
|
||||
# 5 that we told them to. Also, give a nontrivial range.
|
||||
pcs = pcs_new(64, 0xAB, 8)
|
||||
pcs_require_residue(pcs, 0x100, 0xCD)
|
||||
pcs_require_residue_1(pcs, 65537)
|
||||
pcs_avoid_residue_small(pcs, 5, 3)
|
||||
pcs_ready(pcs)
|
||||
with random_prng("test seed"):
|
||||
for i in range(100):
|
||||
n = int(pcs_generate(pcs))
|
||||
self.assertTrue((0xAB<<56) < n < (0xAC<<56))
|
||||
self.assertEqual(n % 0x100, 0xCD)
|
||||
self.assertEqual(n % 65537, 1)
|
||||
self.assertNotEqual(n % 5, 3)
|
||||
|
||||
# I'm not actually testing here that the outputs of
|
||||
# pcs_generate are non-multiples of _all_ primes up to
|
||||
# 2^16. But checking this many for 100 turns is enough
|
||||
# to be pretty sure. (If you take the product of
|
||||
# (1-1/p) over all p in the list below, you find that
|
||||
# a given random number has about a 13% chance of
|
||||
# avoiding being a multiple of any of them. So 100
|
||||
# trials without a mistake gives you 0.13^100 < 10^-88
|
||||
# as the probability of it happening by chance. More
|
||||
# likely the code is actually working :-)
|
||||
|
||||
for p in [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61]:
|
||||
self.assertNotEqual(n % p, 0)
|
||||
|
||||
class crypt(MyTestBase):
|
||||
def testSSH1Fingerprint(self):
|
||||
# Example key and reference fingerprint value generated by
|
||||
|
Reference in New Issue
Block a user