diff --git a/config.json b/config.json index d0d26be0e3..83c94e441e 100644 --- a/config.json +++ b/config.json @@ -834,6 +834,16 @@ "mathematics" ] }, + { + "uuid": "92e2d5f8-7d8a-4e81-a55c-52fa6be80c74", + "slug": "diffie-hellman", + "core": false, + "unlocked_by": "book-store", + "difficulty": 7, + "topics": [ + "algorithms" + ] + }, { "uuid": "8c89f739-05fb-7b80-b5f9-6ad079c750ba8302be8", "slug": "two-bucket", diff --git a/exercises/diffie-hellman/README.md b/exercises/diffie-hellman/README.md new file mode 100644 index 0000000000..0a793540a7 --- /dev/null +++ b/exercises/diffie-hellman/README.md @@ -0,0 +1,58 @@ +# Diffie Hellman + +Diffie-Hellman key exchange. + +Alice and Bob use Diffie-Hellman key exchange to share secrets. They +start with prime numbers, pick private keys, generate and share public +keys, and then generate a shared secret key. + +## Step 0 + +The test program supplies prime numbers p and g. + +## Step 1 + +Alice picks a private key, a, greater than 1 and less than p. Bob does +the same to pick a private key b. + +## Step 2 + +Alice calculates a public key A. + + A = g**a mod p + +Using the same p and g, Bob similarly calculates a public key B from his +private key b. + +## Step 3 + +Alice and Bob exchange public keys. Alice calculates secret key s. + + s = B**a mod p + +Bob calculates + + s = A**b mod p + +The calculations produce the same result! Alice and Bob now share +secret s. + +## Notes + +Python, as of version 3.6, includes two different random modules. The module called `random` is pseudo-random, meaning it does not generate true randomness, but follows and algorithm that simulates randomness. Since random numbers are generated through a known algorithm, they are not truly random. The `random` module is not correctly suited for cryptography and should not be used, because it is pseudo-random. In version 3.6, Python introduced the `secrets` module, which generates cryptographically strong random numbers that provide the greater security required for cryptography. Since this is only an exercise, `random` is fine to use, but note that it would be very insecure if actually used for cryptography. + +### Submitting Exercises + +Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/` directory. + +For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit /python/bob/bob.py`. + + +For more detailed information about running tests, code style and linting, +please see the [help page](http://exercism.io/languages/python). +## Source + +Wikipedia, 1024 bit key from www.cryptopp.com/wiki. [http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange](http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange) + +## Submitting Incomplete Solutions +It's possible to submit an incomplete solution so you can see how others have completed the exercise. diff --git a/exercises/diffie-hellman/diffie_hellman.py b/exercises/diffie-hellman/diffie_hellman.py new file mode 100644 index 0000000000..a12455249f --- /dev/null +++ b/exercises/diffie-hellman/diffie_hellman.py @@ -0,0 +1,10 @@ +def private_key(p): + pass + + +def public_key(p, g, private): + pass + + +def secret(p, public, private): + pass diff --git a/exercises/diffie-hellman/diffie_hellman_test.py b/exercises/diffie-hellman/diffie_hellman_test.py new file mode 100644 index 0000000000..191d865107 --- /dev/null +++ b/exercises/diffie-hellman/diffie_hellman_test.py @@ -0,0 +1,87 @@ +import unittest + +import diffie_hellman + + +class DiffieHellmanTest(unittest.TestCase): + + def test_private_in_range(self): + primes = [5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47] + for i in primes: + self.assertTrue(1 < diffie_hellman.private_key(i) < i) + + # Can fail due to randomness, but most likely will not, + # due to pseudo-randomness and the large number chosen + def test_private_key_randomness(self): + p = 2147483647 + private_keys = [] + for i in range(5): + private_keys.append(diffie_hellman.private_key(p)) + self.assertEqual(len(list(set(private_keys))), len(private_keys)) + + def test_public_key_correct(self): + p = 23 + g = 5 + private = 6 + expected = 8 + + actual = diffie_hellman.public_key(p, g, private) + self.assertEqual(actual, expected) + + def test_secret_key_correct(self): + p = 23 + public = 19 + private = 6 + expected = 2 + + actual = diffie_hellman.secret(p, public, private) + self.assertEqual(actual, expected) + + def test_secret_key_correct_large_nums(self): + p = int("""120227323036150778550155526710966921740030662\ + 69457894729842354923526575959371158734103742634711454153\ + 30066288563005527069961435922404533456428692335628867529\ + 30249953227657883929905072620233073626594386072962776144\ + 69143365881426187411323246174903542571280506720291038940\ + 7991986070558964461330091797026762932543""".replace( + "\n", "").replace(" ", "")) + public = int("""7520544115435791944292554616920871123548\ + 58559049691782063133092992058683123990461493675163366079\ + 66149689640419216591714331722664409474612463910928128055\ + 99415792293044373353565984826436410603792531597409532111\ + 27577117569121441377056137760635413505489115127155125391\ + 86192176020596861210448363099541947258202188""".replace( + "\n", "").replace(" ", "")) + private = int("""248347939362593293991108130435688850515\ + 37971354473275017926961991904690152151776307586179022004\ + 17377685436170904594686456961202706692908603181062371925\ + 882""".replace("\n", "").replace(" ", "")) + expected = int("""70900735223964890815905879227737819348\ + 80851869892044649134650898046120174656773533145582564442\ + 98779465564310958207858354973848497783442169812282262526\ + 39932672153547963980483673419756271345828771971984887453\ + 01448857224581986445413661898091472983952358126388674082\ + 1363010486083940557620831348661126601106717071""".replace( + "\n", "").replace(" ", "")) + + actual = diffie_hellman.secret(p, public, private) + self.assertEqual(actual, expected) + + def test_exchange(self): + p = 23 + g = 5 + + privateA = diffie_hellman.private_key(p) + privateB = diffie_hellman.private_key(p) + + publicA = diffie_hellman.public_key(p, g, privateA) + publicB = diffie_hellman.public_key(p, g, privateB) + + secretA = diffie_hellman.secret(p, publicB, privateA) + secretB = diffie_hellman.secret(p, publicA, privateB) + + self.assertEqual(secretA, secretB) + + +if __name__ == '__main__': + unittest.main() diff --git a/exercises/diffie-hellman/example.py b/exercises/diffie-hellman/example.py new file mode 100644 index 0000000000..3a443b9258 --- /dev/null +++ b/exercises/diffie-hellman/example.py @@ -0,0 +1,13 @@ +import random + + +def private_key(p): + return random.randint(2, p-1) + + +def public_key(p, g, a): + return pow(g, a, p) + + +def secret(p, B, a): + return pow(B, a, p)