From b7bd6e8d856d99e83238629c8a8aba83074f22d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Nu=C3=B1ez?= Date: Tue, 30 Jan 2018 12:21:33 +0100 Subject: [PATCH 01/13] Added hash_to_point function --- umbral/utils.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/umbral/utils.py b/umbral/utils.py index a5b79bf..d4ead6b 100644 --- a/umbral/utils.py +++ b/umbral/utils.py @@ -55,6 +55,42 @@ def hash_to_bn(list, params): return res +def hash_to_point(curve, data, constant=None): + """ + Hashes arbitrary data into a valid EC point of the specified curve, + using the try-and-increment method. + It admits an optional constant as an additional input to the hash function. + It uses SHA256 as the internal hash function. + + WARNING: Do not use when the input data is secret, as this implementation is not + in constant time, and hence, it is not safe with respect to timing attacks. + + TODO: Check how to uniformly generate ycoords. Currently, it only outputs points + where ycoord is even (i.e., starting with 0x02 in compressed notation) + """ + if constant is None: + constant = [] + + # We use a 32-bit counter as additional input + i = 1 + while i < 2**32: + ibytes = i.to_bytes(4, byteorder='big') + digest = hashes.Hash(hashes.SHA256(), backend=default_backend()) + digest.update(constant + ibytes + data) + hash = digest.finalize() + + compressed02 = b"\x02"+hash + try: + h = Point.from_bytes(compressed02, curve) + return h + except: + pass + + i+=1 + + # Only happens with probability 2^(-32) + raise ValueError('Could not find %c in %s' % (ch,str)) + def kdf(ecpoint, key_length): data = ecpoint.to_bytes(is_compressed=True) From 201a40132f4e681a14ff18f643247a793c7310c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Nu=C3=B1ez?= Date: Tue, 30 Jan 2018 12:23:58 +0100 Subject: [PATCH 02/13] Test to check compliance of SECP256k1 generator wrt to SEC standard --- tests/test_point.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/test_point.py b/tests/test_point.py index c1a9956..0277f9a 100644 --- a/tests/test_point.py +++ b/tests/test_point.py @@ -31,3 +31,23 @@ def test_invalid_points(): except: pass +def test_generator(): + curve = ec.SECP256K1() + g1 = Point.get_generator_from_curve(curve) + + # http://www.secg.org/SEC2-Ver-1.0.pdf + # Section 2.7.1 + g_compressed = 0x0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798 + g_compressed = g_compressed.to_bytes(32+1, byteorder='big') + + g_uncompressed = 0x0479BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8 + g_uncompressed = g_uncompressed.to_bytes(64+1, byteorder='big') + + g2 = Point.from_bytes(g_compressed, curve) + assert g1 == g2 + + g3 = Point.from_bytes(g_uncompressed, curve) + assert g1 == g3 + + + assert g2 == g3 \ No newline at end of file From 18e596abdaf3a99670142331809226c1ee6b574d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Nu=C3=B1ez?= Date: Tue, 30 Jan 2018 12:51:06 +0100 Subject: [PATCH 03/13] Cleaned old prints --- tests/test_bignum.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/test_bignum.py b/tests/test_bignum.py index 165ab9c..05c4135 100644 --- a/tests/test_bignum.py +++ b/tests/test_bignum.py @@ -6,10 +6,7 @@ def test_from_to_int(): x = BigNum.gen_rand(curve) xint = x.__int__() - print() - print("xint: ", xint) y = BigNum.from_int(xint, curve) - print("yint: ", y.__int__()) assert x == y \ No newline at end of file From f9cf9ddbf3d853db830eb4cee47e9a04ab61a54d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Nu=C3=B1ez?= Date: Tue, 30 Jan 2018 13:03:41 +0100 Subject: [PATCH 04/13] Deterministic generation of parameters h and g h and u are now deterministically generated as hashes of g plus a per-param label, using the Utils.hash_to_point method --- umbral/umbral.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/umbral/umbral.py b/umbral/umbral.py index 25f3b08..cd8985d 100644 --- a/umbral/umbral.py +++ b/umbral/umbral.py @@ -6,7 +6,7 @@ from umbral.bignum import BigNum from umbral.point import Point from umbral.keys import UmbralPrivateKey, UmbralPublicKey from umbral.dem import UmbralDEM -from umbral.utils import poly_eval, lambda_coeff, hash_to_bn, kdf +from umbral.utils import poly_eval, lambda_coeff, hash_to_bn, kdf, hash_to_point class UmbralParameters(object): @@ -14,9 +14,14 @@ class UmbralParameters(object): self.curve = ec.SECP256K1() self.g = Point.get_generator_from_curve(self.curve) self.order = Point.get_order_from_curve(self.curve) - self.h = self.g - self.u = self.g + + g_bytes = self.g.to_bytes(is_compressed=True) + constant_u = b'NuCypherKMS/UmbralParameters/u' + constant_h = b'NuCypherKMS/UmbralParameters/h' + + self.h = hash_to_point(self.curve, g_bytes, constant_h) + self.u = hash_to_point(self.curve, g_bytes, constant_u) class KFrag(object): def __init__(self, id_, key, x, u1, z1, z2): From cd22679c44a59141ef3317e1d20fcfb814e813b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Nu=C3=B1ez?= Date: Tue, 30 Jan 2018 13:08:30 +0100 Subject: [PATCH 05/13] Corrected error message --- umbral/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/umbral/utils.py b/umbral/utils.py index d4ead6b..1dd261c 100644 --- a/umbral/utils.py +++ b/umbral/utils.py @@ -89,7 +89,7 @@ def hash_to_point(curve, data, constant=None): i+=1 # Only happens with probability 2^(-32) - raise ValueError('Could not find %c in %s' % (ch,str)) + raise ValueError('Could not hash input into the curve') def kdf(ecpoint, key_length): data = ecpoint.to_bytes(is_compressed=True) From e81718140f4235312e85ad3595489ac949337fec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Nu=C3=B1ez?= Date: Wed, 31 Jan 2018 09:22:55 +0100 Subject: [PATCH 06/13] Fixing conflicts --- umbral/umbral.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/umbral/umbral.py b/umbral/umbral.py index cd8985d..d9d573d 100644 --- a/umbral/umbral.py +++ b/umbral/umbral.py @@ -1,11 +1,10 @@ -from nacl.secret import SecretBox -from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import ec +from nacl.secret import SecretBox from umbral.bignum import BigNum -from umbral.point import Point -from umbral.keys import UmbralPrivateKey, UmbralPublicKey from umbral.dem import UmbralDEM +from umbral.keys import UmbralPrivateKey, UmbralPublicKey +from umbral.point import Point from umbral.utils import poly_eval, lambda_coeff, hash_to_bn, kdf, hash_to_point From 2f5f473c1037cc851e0ecb140b95d06f745461ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Nu=C3=B1ez?= Date: Wed, 31 Jan 2018 09:24:37 +0100 Subject: [PATCH 07/13] Fixing conflicts --- umbral/umbral.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/umbral/umbral.py b/umbral/umbral.py index d9d573d..285313a 100644 --- a/umbral/umbral.py +++ b/umbral/umbral.py @@ -5,7 +5,7 @@ from umbral.bignum import BigNum from umbral.dem import UmbralDEM from umbral.keys import UmbralPrivateKey, UmbralPublicKey from umbral.point import Point -from umbral.utils import poly_eval, lambda_coeff, hash_to_bn, kdf, hash_to_point +from umbral.utils import poly_eval, lambda_coeff, hash_to_bn, kdf class UmbralParameters(object): From 78bc6fffad3fab21dd168ab17bd0321b245a9aae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Nu=C3=B1ez?= Date: Wed, 31 Jan 2018 09:30:12 +0100 Subject: [PATCH 08/13] Renamed hash_to_fn to unsafe_hash_to_bn Just in case someone tries to use it with secret data --- umbral/umbral.py | 6 +++--- umbral/utils.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/umbral/umbral.py b/umbral/umbral.py index 285313a..c5fc696 100644 --- a/umbral/umbral.py +++ b/umbral/umbral.py @@ -5,7 +5,7 @@ from umbral.bignum import BigNum from umbral.dem import UmbralDEM from umbral.keys import UmbralPrivateKey, UmbralPublicKey from umbral.point import Point -from umbral.utils import poly_eval, lambda_coeff, hash_to_bn, kdf +from umbral.utils import poly_eval, lambda_coeff, hash_to_bn, kdf, unsafe_hash_to_point class UmbralParameters(object): @@ -19,8 +19,8 @@ class UmbralParameters(object): constant_u = b'NuCypherKMS/UmbralParameters/u' constant_h = b'NuCypherKMS/UmbralParameters/h' - self.h = hash_to_point(self.curve, g_bytes, constant_h) - self.u = hash_to_point(self.curve, g_bytes, constant_u) + self.h = unsafe_hash_to_point(self.curve, g_bytes, constant_h) + self.u = unsafe_hash_to_point(self.curve, g_bytes, constant_u) class KFrag(object): def __init__(self, id_, key, x, u1, z1, z2): diff --git a/umbral/utils.py b/umbral/utils.py index 1dd261c..cfc1e1b 100644 --- a/umbral/utils.py +++ b/umbral/utils.py @@ -55,7 +55,7 @@ def hash_to_bn(list, params): return res -def hash_to_point(curve, data, constant=None): +def unsafe_hash_to_point(curve, data, constant=None): """ Hashes arbitrary data into a valid EC point of the specified curve, using the try-and-increment method. From 1c300992eb132569c97dc46961f866c03b9a761e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Nu=C3=B1ez?= Date: Wed, 31 Jan 2018 13:23:47 +0100 Subject: [PATCH 09/13] Test for catching more specific exception wrt to invalid EC points --- tests/test_point.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/test_point.py b/tests/test_point.py index 0277f9a..50cbc56 100644 --- a/tests/test_point.py +++ b/tests/test_point.py @@ -1,6 +1,7 @@ from umbral.point import Point from umbral.bignum import BigNum from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.exceptions import InternalError def test_from_to_bytes(): curve = ec.SECP256K1() @@ -27,9 +28,16 @@ def test_invalid_points(): try: q = Point.from_bytes(pbytes, curve) - assert False - except: - pass + except InternalError as e: + # We want to catch a specific InternalException: point not in the curve + # That's reason 107 in OpenSSL + # https://github.com/openssl/openssl/blob/master/include/openssl/ecerr.h#L228 + if e.err_code[0].reason == 107: + pass + else: + assert False + else: + assert False def test_generator(): curve = ec.SECP256K1() From 84036b99f6feebbd209044fe21948393d5a88bad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Nu=C3=B1ez?= Date: Wed, 31 Jan 2018 16:36:26 +0100 Subject: [PATCH 10/13] Extended previous test with another exception subtype --- tests/test_point.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/test_point.py b/tests/test_point.py index 50cbc56..ee65f64 100644 --- a/tests/test_point.py +++ b/tests/test_point.py @@ -29,10 +29,11 @@ def test_invalid_points(): try: q = Point.from_bytes(pbytes, curve) except InternalError as e: - # We want to catch a specific InternalException: point not in the curve - # That's reason 107 in OpenSSL + # We want to catch specific InternalExceptions: + # - Point not in the curve (code 107) + # - Invalid compressed point (code 110) # https://github.com/openssl/openssl/blob/master/include/openssl/ecerr.h#L228 - if e.err_code[0].reason == 107: + if e.err_code[0].reason in (107, 110): pass else: assert False From ba2a93e9bfa180a20a6afd234ed3d584453686d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Nu=C3=B1ez?= Date: Wed, 31 Jan 2018 16:37:17 +0100 Subject: [PATCH 11/13] Remove the try-except-pass pattern using more specific exceptions --- umbral/utils.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/umbral/utils.py b/umbral/utils.py index cfc1e1b..ba6c8a7 100644 --- a/umbral/utils.py +++ b/umbral/utils.py @@ -1,6 +1,7 @@ from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.hkdf import HKDF +from cryptography.exceptions import InternalError from umbral.bignum import BigNum from umbral.point import Point @@ -80,13 +81,22 @@ def unsafe_hash_to_point(curve, data, constant=None): hash = digest.finalize() compressed02 = b"\x02"+hash + try: h = Point.from_bytes(compressed02, curve) return h - except: - pass - - i+=1 + except InternalError as e: + # We want to catch specific InternalExceptions: + # - Point not in the curve (code 107) + # - Invalid compressed point (code 110) + # https://github.com/openssl/openssl/blob/master/include/openssl/ecerr.h#L228 + if e.err_code[0].reason in (107, 110): + pass + else: + # Any other exception, we raise it + raise e + + i += 1 # Only happens with probability 2^(-32) raise ValueError('Could not hash input into the curve') From c79b537e01bfe32fb14158a8982e9d54cfc94aa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Nu=C3=B1ez?= Date: Wed, 31 Jan 2018 16:39:49 +0100 Subject: [PATCH 12/13] Changed "constant" to "label" in unsafe_hash_to_point --- umbral/utils.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/umbral/utils.py b/umbral/utils.py index ba6c8a7..1c5c3bd 100644 --- a/umbral/utils.py +++ b/umbral/utils.py @@ -56,34 +56,35 @@ def hash_to_bn(list, params): return res -def unsafe_hash_to_point(curve, data, constant=None): +def unsafe_hash_to_point(curve, data, label=None): """ Hashes arbitrary data into a valid EC point of the specified curve, using the try-and-increment method. - It admits an optional constant as an additional input to the hash function. + It admits an optional label as an additional input to the hash function. It uses SHA256 as the internal hash function. WARNING: Do not use when the input data is secret, as this implementation is not - in constant time, and hence, it is not safe with respect to timing attacks. + in label time, and hence, it is not safe with respect to timing attacks. TODO: Check how to uniformly generate ycoords. Currently, it only outputs points where ycoord is even (i.e., starting with 0x02 in compressed notation) """ - if constant is None: - constant = [] + if label is None: + label = [] # We use a 32-bit counter as additional input i = 1 while i < 2**32: ibytes = i.to_bytes(4, byteorder='big') digest = hashes.Hash(hashes.SHA256(), backend=default_backend()) - digest.update(constant + ibytes + data) + digest.update(label + ibytes + data) hash = digest.finalize() compressed02 = b"\x02"+hash try: h = Point.from_bytes(compressed02, curve) + print(i) return h except InternalError as e: # We want to catch specific InternalExceptions: From 8761d7828ae330825e6dcabf21d173f5209380c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Nu=C3=B1ez?= Date: Wed, 31 Jan 2018 16:46:31 +0100 Subject: [PATCH 13/13] Using a common domain_seed --- umbral/umbral.py | 7 +++---- umbral/utils.py | 1 - 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/umbral/umbral.py b/umbral/umbral.py index c0e3d4d..61fad7f 100644 --- a/umbral/umbral.py +++ b/umbral/umbral.py @@ -16,11 +16,10 @@ class UmbralParameters(object): g_bytes = self.g.to_bytes(is_compressed=True) - constant_u = b'NuCypherKMS/UmbralParameters/u' - constant_h = b'NuCypherKMS/UmbralParameters/h' + domain_seed = b'NuCypherKMS/UmbralParameters/' - self.h = unsafe_hash_to_point(self.curve, g_bytes, constant_h) - self.u = unsafe_hash_to_point(self.curve, g_bytes, constant_u) + self.h = unsafe_hash_to_point(self.curve, g_bytes, domain_seed + b'h') + self.u = unsafe_hash_to_point(self.curve, g_bytes, domain_seed + b'u') class KFrag(object): def __init__(self, id_, key, x, u1, z1, z2): diff --git a/umbral/utils.py b/umbral/utils.py index 1c5c3bd..f7dd12e 100644 --- a/umbral/utils.py +++ b/umbral/utils.py @@ -84,7 +84,6 @@ def unsafe_hash_to_point(curve, data, label=None): try: h = Point.from_bytes(compressed02, curve) - print(i) return h except InternalError as e: # We want to catch specific InternalExceptions: