mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-07-04 13:02:47 -05:00
Implement OpenSSH 9.x's NTRU Prime / Curve25519 kex.
This consists of DJB's 'Streamlined NTRU Prime' quantum-resistant cryptosystem, currently in round 3 of the NIST post-quantum key exchange competition; it's run in parallel with ordinary Curve25519, and generates a shared secret combining the output of both systems. (Hence, even if you don't trust this newfangled NTRU Prime thing at all, it's at least no _less_ secure than the kex you were using already.) As the OpenSSH developers point out, key exchange is the most urgent thing to make quantum-resistant, even before working quantum computers big enough to break crypto become available, because a break of the kex algorithm can be applied retroactively to recordings of your past sessions. By contrast, authentication is a real-time protocol, and can only be broken by a quantum computer if there's one available to attack you _already_. I've implemented both sides of the mechanism, so that PuTTY and Uppity both support it. In my initial testing, the two sides can both interoperate with the appropriate half of OpenSSH, and also (of course, but it would be embarrassing to mess it up) with each other.
This commit is contained in:
@ -1274,6 +1274,107 @@ class keygen(MyTestBase):
|
||||
mr = miller_rabin_new(n)
|
||||
self.assertEqual(miller_rabin_test(mr, 0x251), "failed")
|
||||
|
||||
class ntru(MyTestBase):
|
||||
def testMultiply(self):
|
||||
self.assertEqual(
|
||||
ntru_ring_multiply([1,1,1,1,1,1], [1,1,1,1,1,1], 11, 59),
|
||||
[1,2,3,4,5,6,5,4,3,2,1])
|
||||
self.assertEqual(ntru_ring_multiply(
|
||||
[1,0,1,2,0,0,1,2,0,1,2], [2,0,0,1,0,1,2,2,2,0,2], 11, 3),
|
||||
[1,0,0,0,0,0,0,0,0,0,0])
|
||||
|
||||
def testInvert(self):
|
||||
# Over GF(3), x^11-x-1 factorises as
|
||||
# (x^3+x^2+2) * (x^8+2*x^7+x^6+2*x^4+2*x^3+x^2+x+1)
|
||||
# so we expect that 2,0,1,1 has no inverse, being one of those factors.
|
||||
self.assertEqual(ntru_ring_invert([0], 11, 3), None)
|
||||
self.assertEqual(ntru_ring_invert([1], 11, 3),
|
||||
[1,0,0,0,0,0,0,0,0,0,0])
|
||||
self.assertEqual(ntru_ring_invert([2,0,1,1], 11, 3), None)
|
||||
self.assertEqual(ntru_ring_invert([1,0,1,2,0,0,1,2,0,1,2], 11, 3),
|
||||
[2,0,0,1,0,1,2,2,2,0,2])
|
||||
|
||||
self.assertEqual(ntru_ring_invert([1,0,1,2,0,0,1,2,0,1,2], 11, 59),
|
||||
[1,26,10,1,38,48,34,37,53,3,53])
|
||||
|
||||
def testMod3Round3(self):
|
||||
# Try a prime congruent to 1 mod 3
|
||||
self.assertEqual(ntru_mod3([4,5,6,0,1,2,3], 7, 7),
|
||||
[0,1,-1,0,1,-1,0])
|
||||
self.assertEqual(ntru_round3([4,5,6,0,1,2,3], 7, 7),
|
||||
[-3,-3,0,0,0,3,3])
|
||||
|
||||
# And one congruent to 2 mod 3
|
||||
self.assertEqual(ntru_mod3([6,7,8,9,10,0,1,2,3,4,5], 11, 11),
|
||||
[1,-1,0,1,-1,0,1,-1,0,1,-1])
|
||||
self.assertEqual(ntru_round3([6,7,8,9,10,0,1,2,3,4,5], 11, 11),
|
||||
[-6,-3,-3,-3,0,0,0,3,3,3,6])
|
||||
|
||||
def testBiasScale(self):
|
||||
self.assertEqual(ntru_bias([0,1,2,3,4,5,6,7,8,9,10], 4, 11, 11),
|
||||
[4,5,6,7,8,9,10,0,1,2,3])
|
||||
self.assertEqual(ntru_scale([0,1,2,3,4,5,6,7,8,9,10], 4, 11, 11),
|
||||
[0,4,8,1,5,9,2,6,10,3,7])
|
||||
|
||||
def testEncode(self):
|
||||
# Test a small case. Worked through in detail:
|
||||
#
|
||||
# Pass 1:
|
||||
# Input list is (89:123, 90:234, 344:345, 432:456, 222:567)
|
||||
# (89:123, 90:234) -> (89+123*90 : 123*234) = (11159:28782)
|
||||
# Emit low byte of 11159 = 0x97, and get (43:113)
|
||||
# (344:345, 432:456) -> (344+345*432 : 345*456) = (149384:157320)
|
||||
# Emit low byte of 149384 = 0x88, and get (583:615)
|
||||
# Odd pair (222:567) is copied to end of new list
|
||||
# Final list is (43:113, 583:615, 222:567)
|
||||
# Pass 2:
|
||||
# Input list is (43:113, 583:615, 222:567)
|
||||
# (43:113, 583:615) -> (43+113*583, 113*615) = (65922:69495)
|
||||
# Emit low byte of 65922 = 0x82, and get (257:272)
|
||||
# Odd pair (222:567) is copied to end of new list
|
||||
# Final list is (257:272, 222:567)
|
||||
# Pass 3:
|
||||
# Input list is (257:272, 222:567)
|
||||
# (257:272, 222:567) -> (257+272*222, 272*567) = (60641:154224)
|
||||
# Emit low byte of 60641 = 0xe1, and get (236:603)
|
||||
# Final list is (236:603)
|
||||
# Cleanup:
|
||||
# Emit low byte of 236 = 0xec, and get (0:3)
|
||||
# Emit low byte of 0 = 0x00, and get (0:1)
|
||||
|
||||
ms = [123,234,345,456,567]
|
||||
rs = [89,90,344,432,222]
|
||||
encoding = unhex('978882e1ec00')
|
||||
sched = ntru_encode_schedule(ms)
|
||||
self.assertEqual(sched.encode(rs), encoding)
|
||||
self.assertEqual(sched.decode(encoding), rs)
|
||||
|
||||
# Encode schedules for sntrup761 public keys and ciphertexts
|
||||
pubsched = ntru_encode_schedule([4591]*761)
|
||||
self.assertEqual(pubsched.length(), 1158)
|
||||
ciphersched = ntru_encode_schedule([1531]*761)
|
||||
self.assertEqual(ciphersched.length(), 1007)
|
||||
|
||||
# Test round-trip encoding using those schedules
|
||||
testlist = list(range(761))
|
||||
pubtext = pubsched.encode(testlist)
|
||||
self.assertEqual(pubsched.decode(pubtext), testlist)
|
||||
ciphertext = ciphersched.encode(testlist)
|
||||
self.assertEqual(ciphersched.decode(ciphertext), testlist)
|
||||
|
||||
def testCore(self):
|
||||
# My own set of NTRU Prime parameters, satisfying all the
|
||||
# requirements and tiny enough for convenient testing
|
||||
p, q, w = 11, 59, 3
|
||||
|
||||
with random_prng('ntru keygen seed'):
|
||||
keypair = ntru_keygen(p, q, w)
|
||||
plaintext = ntru_gen_short(p, w)
|
||||
|
||||
ciphertext = ntru_encrypt(plaintext, ntru_pubkey(keypair), p, q)
|
||||
recovered = ntru_decrypt(ciphertext, keypair)
|
||||
self.assertEqual(plaintext, recovered)
|
||||
|
||||
class crypt(MyTestBase):
|
||||
def testSSH1Fingerprint(self):
|
||||
# Example key and reference fingerprint value generated by
|
||||
|
Reference in New Issue
Block a user