From 3a0142cd1f2b66d174436a510044fbeabed7f66b Mon Sep 17 00:00:00 2001 From: Gian-Luca Frei Date: Sun, 6 Jan 2019 22:30:46 +0100 Subject: [PATCH 1/8] Added recover_public_keys() to Signature --- src/ecdsa/ecdsa.py | 21 ++++++++++++++++++++- src/ecdsa/test_ecdsa.py | 27 +++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/ecdsa/ecdsa.py b/src/ecdsa/ecdsa.py index 755918d9..1aec4108 100644 --- a/src/ecdsa/ecdsa.py +++ b/src/ecdsa/ecdsa.py @@ -57,7 +57,6 @@ from . import ellipticcurve from . import numbertheory - class RSZeroError(RuntimeError): pass @@ -69,6 +68,26 @@ def __init__(self, r, s): self.r = r self.s = s + def recover_public_keys(self, hash, curve, g, n): + r = self.r + s = self.s + e = hash + x = r + + # Compute the curve point with x as x-coordinate + alpha = ((x * x * x) + (curve.a() * x) + curve.b()) % curve.p() + beta = numbertheory.square_root_mod_prime(alpha, curve.p()) + y = beta if beta % 2 == 0 else curve.p() - beta + + # Compute the public key + R1 = ellipticcurve.Point(curve, x, y, n) + Q1 = numbertheory.inverse_mod(r, n) * (s * R1 + (-e % n) * g) + + + R2 = ellipticcurve.Point(curve, x, -y, n) + Q2 = numbertheory.inverse_mod(r, n) * (s * R2 + (-e % n) * g) + + return [Q1, Q2] class Public_key(object): """Public key for ECDSA. diff --git a/src/ecdsa/test_ecdsa.py b/src/ecdsa/test_ecdsa.py index d5a24558..561471e7 100644 --- a/src/ecdsa/test_ecdsa.py +++ b/src/ecdsa/test_ecdsa.py @@ -30,6 +30,31 @@ def test_signature_validity(Msg, Qx, Qy, R, S, expected): raise TestFailure("*** Signature test failed: got %s, expected %s." % \ (got, expected)) + def test_pk_recovery(Msg, R, S, Qx, Qy): + + sign = Signature(R,S) + pks = sign.recover_public_keys(digest_integer(Msg), curve_192, generator_192, generator_192.order()) + + print_("Test pk recover") + + if len(pks) > 0: + + # Test if the signature is valid for all found public keys + for pk in pks: + print_("Recovered pk: %s" % pk) + test_signature_validity(Msg, pk.x(), pk.y(), R, S, True) + + # Test if the original public key is in the set of found keys + original_pk = ellipticcurve.Point(curve_192, Qx, Qy) + if original_pk in pks: + print_("Original pk was found") + else: + raise TestFailure("Original public key was not recovered") + + else: + raise TestFailure("*** NO valid public key returned") + + print_("NIST Curve P-192:") p192 = generator_192 @@ -165,6 +190,7 @@ def test_signature_validity(Msg, Qx, Qy, R, S, expected): R = 0x64dca58a20787c488d11d6dd96313f1b766f2d8efe122916 S = 0x1ecba28141e84ab4ecad92f56720e2cc83eb3d22dec72479 test_signature_validity(Msg, Qx, Qy, R, S, True) + test_pk_recovery(Msg, R, S, Qx, Qy) Msg = 0x94bb5bacd5f8ea765810024db87f4224ad71362a3c28284b2b9f39fab86db12e8beb94aae899768229be8fdb6c4f12f28912bb604703a79ccff769c1607f5a91450f30ba0460d359d9126cbd6296be6d9c4bb96c0ee74cbb44197c207f6db326ab6f5a659113a9034e54be7b041ced9dcf6458d7fb9cbfb2744d999f7dfd63f4 Qx = 0x3e53ef8d3112af3285c0e74842090712cd324832d4277ae7 @@ -172,6 +198,7 @@ def test_signature_validity(Msg, Qx, Qy, R, S, expected): R = 0x8285261607283ba18f335026130bab31840dcfd9c3e555af S = 0x356d89e1b04541afc9704a45e9c535ce4a50929e33d7e06c test_signature_validity(Msg, Qx, Qy, R, S, True) + test_pk_recovery(Msg, R, S, Qx, Qy) Msg = 0xf6227a8eeb34afed1621dcc89a91d72ea212cb2f476839d9b4243c66877911b37b4ad6f4448792a7bbba76c63bdd63414b6facab7dc71c3396a73bd7ee14cdd41a659c61c99b779cecf07bc51ab391aa3252386242b9853ea7da67fd768d303f1b9b513d401565b6f1eb722dfdb96b519fe4f9bd5de67ae131e64b40e78c42dd Qx = 0x16335dbe95f8e8254a4e04575d736befb258b8657f773cb7 From 90f691f75dd843ca533563a642f7be6ca7d563b2 Mon Sep 17 00:00:00 2001 From: Gian-Luca Frei Date: Tue, 8 Jan 2019 16:50:57 +0100 Subject: [PATCH 2/8] Added pk recovery support in wrapper --- src/ecdsa/ecdsa.py | 20 ++++++++++++++------ src/ecdsa/keys.py | 24 ++++++++++++++++++++++++ src/ecdsa/test_ecdsa.py | 16 +++++++++------- src/ecdsa/test_pyecdsa.py | 28 ++++++++++++++++++++++++++++ 4 files changed, 75 insertions(+), 13 deletions(-) diff --git a/src/ecdsa/ecdsa.py b/src/ecdsa/ecdsa.py index 1aec4108..9035a10d 100644 --- a/src/ecdsa/ecdsa.py +++ b/src/ecdsa/ecdsa.py @@ -68,26 +68,34 @@ def __init__(self, r, s): self.r = r self.s = s - def recover_public_keys(self, hash, curve, g, n): + def recover_public_keys(self, hash, generator): + """Returns two public keys for which the signature is valid + hash is signed hash + generator is the used generator of the signature + """ + curve = generator.curve() + n = generator.order() r = self.r s = self.s e = hash x = r # Compute the curve point with x as x-coordinate - alpha = ((x * x * x) + (curve.a() * x) + curve.b()) % curve.p() + alpha = (pow(x, 3, curve.p()) + (curve.a() * x) + curve.b()) % curve.p() beta = numbertheory.square_root_mod_prime(alpha, curve.p()) y = beta if beta % 2 == 0 else curve.p() - beta # Compute the public key R1 = ellipticcurve.Point(curve, x, y, n) - Q1 = numbertheory.inverse_mod(r, n) * (s * R1 + (-e % n) * g) - + Q1 = numbertheory.inverse_mod(r, n) * (s * R1 + (-e % n) * generator) + Pk1 = Public_key(generator, Q1) + # And the second solution R2 = ellipticcurve.Point(curve, x, -y, n) - Q2 = numbertheory.inverse_mod(r, n) * (s * R2 + (-e % n) * g) + Q2 = numbertheory.inverse_mod(r, n) * (s * R2 + (-e % n) * generator) + Pk2 = Public_key(generator, Q2) - return [Q1, Q2] + return [Pk1, Pk2] class Public_key(object): """Public key for ECDSA. diff --git a/src/ecdsa/keys.py b/src/ecdsa/keys.py index 8ae2169c..9f17c1d2 100644 --- a/src/ecdsa/keys.py +++ b/src/ecdsa/keys.py @@ -79,6 +79,30 @@ def from_der(klass, string): assert point_str.startswith(b("\x00\x04")) return klass.from_string(point_str[2:], curve) + @classmethod + def from_public_key_recovery(klass, signature, data, curve, hashfunc=sha1, sigdecode=sigdecode_string): + # Given a signature and correponding message this function + # returns a list of verifiying keys for this signature and message + + digest = hashfunc(data).digest() + return klass.from_public_key_recovery_with_digest(signature, digest, curve, hashfunc=sha1, sigdecode=sigdecode_string) + + @classmethod + def from_public_key_recovery_with_digest(klass, signature, digest, curve, hashfunc=sha1, sigdecode=sigdecode_string): + # Given a signature and correponding digest this function + # returns a list of verifiying keys for this signature and message + + generator = curve.generator + r, s = sigdecode(signature, generator.order()) + sig = ecdsa.Signature(r, s) + + digest_as_number = string_to_number(digest) + pks = sig.recover_public_keys(digest_as_number, generator) + + # Transform the ecdsa.Public_key object into a VerifyingKey + verifying_keys = [klass.from_public_point(pk.point, curve, hashfunc) for pk in pks] + return verifying_keys + def to_string(self): # VerifyingKey.from_string(vk.to_string()) == vk as long as the # curves are the same: the curve itself is not included in the diff --git a/src/ecdsa/test_ecdsa.py b/src/ecdsa/test_ecdsa.py index 561471e7..00b6d1ed 100644 --- a/src/ecdsa/test_ecdsa.py +++ b/src/ecdsa/test_ecdsa.py @@ -33,7 +33,7 @@ def test_signature_validity(Msg, Qx, Qy, R, S, expected): def test_pk_recovery(Msg, R, S, Qx, Qy): sign = Signature(R,S) - pks = sign.recover_public_keys(digest_integer(Msg), curve_192, generator_192, generator_192.order()) + pks = sign.recover_public_keys(digest_integer(Msg), generator_192) print_("Test pk recover") @@ -41,15 +41,17 @@ def test_pk_recovery(Msg, R, S, Qx, Qy): # Test if the signature is valid for all found public keys for pk in pks: - print_("Recovered pk: %s" % pk) - test_signature_validity(Msg, pk.x(), pk.y(), R, S, True) + q = pk.point + print_("Recovered q: %s" % q) + test_signature_validity(Msg, q.x(), q.y(), R, S, True) # Test if the original public key is in the set of found keys - original_pk = ellipticcurve.Point(curve_192, Qx, Qy) - if original_pk in pks: - print_("Original pk was found") + original_q = ellipticcurve.Point(curve_192, Qx, Qy) + points = map(lambda pk: pk.point ,pks) + if original_q in points: + print_("Original q was found") else: - raise TestFailure("Original public key was not recovered") + raise TestFailure("Original q is not in the list of recovered public keys") else: raise TestFailure("*** NO valid public key returned") diff --git a/src/ecdsa/test_pyecdsa.py b/src/ecdsa/test_pyecdsa.py index 4fcbba83..aba6839b 100644 --- a/src/ecdsa/test_pyecdsa.py +++ b/src/ecdsa/test_pyecdsa.py @@ -311,6 +311,34 @@ def test_hashfunc(self): curve=NIST256p) self.assertTrue(vk3.verify(sig, data, hashfunc=sha256)) + def test_public_key_recovery(self): + # Create keys + curve = NIST256p + + sk = SigningKey.generate(curve=curve) + vk = sk.get_verifying_key() + + # Sign a message + data = b("blahblah") + signature = sk.sign(data) + + # Recover verifying keys + recovered_vks = VerifyingKey.from_public_key_recovery(signature, data, curve) + + # Test if each pk is valid + for recovered_vk in recovered_vks: + # Test if recovered vk is valid for the data + self.assertTrue(recovered_vk.verify(signature, data)) + + # Test if properties are equal + self.assertEqual(vk.curve, recovered_vk.curve) + self.assertEqual(vk.default_hashfunc, recovered_vk.default_hashfunc) + + # Test if original vk is the list of recovered keys + self.assertTrue( + vk.pubkey.point in + map(lambda recovered_vk: recovered_vk.pubkey.point, recovered_vks)) + class OpenSSL(unittest.TestCase): # test interoperability with OpenSSL tools. Note that openssl's ECDSA From 5c07bb3d4f41375c5b98a40f20179aaafb4c6950 Mon Sep 17 00:00:00 2001 From: Gian-Luca Frei Date: Tue, 8 Jan 2019 17:15:45 +0100 Subject: [PATCH 3/8] typo in comment --- src/ecdsa/keys.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ecdsa/keys.py b/src/ecdsa/keys.py index 9f17c1d2..cae023bf 100644 --- a/src/ecdsa/keys.py +++ b/src/ecdsa/keys.py @@ -81,16 +81,16 @@ def from_der(klass, string): @classmethod def from_public_key_recovery(klass, signature, data, curve, hashfunc=sha1, sigdecode=sigdecode_string): - # Given a signature and correponding message this function - # returns a list of verifiying keys for this signature and message + # Given a signature and corresponding message this function + # returns a list of verifying keys for this signature and message digest = hashfunc(data).digest() return klass.from_public_key_recovery_with_digest(signature, digest, curve, hashfunc=sha1, sigdecode=sigdecode_string) @classmethod def from_public_key_recovery_with_digest(klass, signature, digest, curve, hashfunc=sha1, sigdecode=sigdecode_string): - # Given a signature and correponding digest this function - # returns a list of verifiying keys for this signature and message + # Given a signature and corresponding digest this function + # returns a list of verifying keys for this signature and message generator = curve.generator r, s = sigdecode(signature, generator.order()) @@ -99,7 +99,7 @@ def from_public_key_recovery_with_digest(klass, signature, digest, curve, hashfu digest_as_number = string_to_number(digest) pks = sig.recover_public_keys(digest_as_number, generator) - # Transform the ecdsa.Public_key object into a VerifyingKey + # Transforms the ecdsa.Public_key object into a VerifyingKey verifying_keys = [klass.from_public_point(pk.point, curve, hashfunc) for pk in pks] return verifying_keys From c2f3ace792467743289872fb59bab09933d42c07 Mon Sep 17 00:00:00 2001 From: Gian-Luca Frei Date: Tue, 15 Jan 2019 14:39:55 +0100 Subject: [PATCH 4/8] Fixed PEP-8 mistake --- src/ecdsa/ecdsa.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ecdsa/ecdsa.py b/src/ecdsa/ecdsa.py index 9035a10d..a7b93614 100644 --- a/src/ecdsa/ecdsa.py +++ b/src/ecdsa/ecdsa.py @@ -57,6 +57,7 @@ from . import ellipticcurve from . import numbertheory + class RSZeroError(RuntimeError): pass From b5d4199f2dc4079756def4b6090f0b363d276ede Mon Sep 17 00:00:00 2001 From: Gian-Luca Frei Date: Tue, 15 Jan 2019 14:44:46 +0100 Subject: [PATCH 5/8] Changed if len(pks)==0 to if pks --- src/ecdsa/test_ecdsa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ecdsa/test_ecdsa.py b/src/ecdsa/test_ecdsa.py index 00b6d1ed..8e7b3050 100644 --- a/src/ecdsa/test_ecdsa.py +++ b/src/ecdsa/test_ecdsa.py @@ -37,7 +37,7 @@ def test_pk_recovery(Msg, R, S, Qx, Qy): print_("Test pk recover") - if len(pks) > 0: + if pks: # Test if the signature is valid for all found public keys for pk in pks: From a65e0818f4ce7fba0575cbf29e502626d3b41548 Mon Sep 17 00:00:00 2001 From: Gian-Luca Frei Date: Tue, 15 Jan 2019 14:45:39 +0100 Subject: [PATCH 6/8] Removed illegal character --- src/ecdsa/test_pyecdsa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ecdsa/test_pyecdsa.py b/src/ecdsa/test_pyecdsa.py index aba6839b..f1eb0e51 100644 --- a/src/ecdsa/test_pyecdsa.py +++ b/src/ecdsa/test_pyecdsa.py @@ -330,7 +330,7 @@ def test_public_key_recovery(self): # Test if recovered vk is valid for the data self.assertTrue(recovered_vk.verify(signature, data)) - # Test if properties are equal + # Test if properties are equal self.assertEqual(vk.curve, recovered_vk.curve) self.assertEqual(vk.default_hashfunc, recovered_vk.default_hashfunc) From a7b2400be0702f3e0221f30638924ff4e0df3324 Mon Sep 17 00:00:00 2001 From: Gian-Luca Frei Date: Tue, 15 Jan 2019 14:51:08 +0100 Subject: [PATCH 7/8] changed map() to list comprehension --- src/ecdsa/test_ecdsa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ecdsa/test_ecdsa.py b/src/ecdsa/test_ecdsa.py index 8e7b3050..cb7d837f 100644 --- a/src/ecdsa/test_ecdsa.py +++ b/src/ecdsa/test_ecdsa.py @@ -47,7 +47,7 @@ def test_pk_recovery(Msg, R, S, Qx, Qy): # Test if the original public key is in the set of found keys original_q = ellipticcurve.Point(curve_192, Qx, Qy) - points = map(lambda pk: pk.point ,pks) + points = [pk.point for pk in pks] if original_q in points: print_("Original q was found") else: From c63057a7d423c1cb29330b503a4542fd7db9f20e Mon Sep 17 00:00:00 2001 From: Gian-Luca Frei Date: Tue, 15 Jan 2019 14:53:16 +0100 Subject: [PATCH 8/8] changed another map() to list comprehension --- src/ecdsa/test_pyecdsa.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ecdsa/test_pyecdsa.py b/src/ecdsa/test_pyecdsa.py index f1eb0e51..7dbc33b8 100644 --- a/src/ecdsa/test_pyecdsa.py +++ b/src/ecdsa/test_pyecdsa.py @@ -336,8 +336,7 @@ def test_public_key_recovery(self): # Test if original vk is the list of recovered keys self.assertTrue( - vk.pubkey.point in - map(lambda recovered_vk: recovered_vk.pubkey.point, recovered_vks)) + vk.pubkey.point in [recovered_vk.pubkey.point for recovered_vk in recovered_vks]) class OpenSSL(unittest.TestCase):