diff --git a/ECL/__init__.py b/ECL/__init__.py index 478c657..38bebc6 100644 --- a/ECL/__init__.py +++ b/ECL/__init__.py @@ -18,18 +18,18 @@ from ECL import point, curve, std_curves, point_with_order, koblitz, elgamal, diffie_hellman, ecdsa from ECL.curve import Curve from ECL.point import Point -from ECL.utility import EclException +from ECL.utility import EclError from ECL.diffie_hellman import DiffieHellman from ECL.point_with_order import PointWOrder __author__ = 'ivansarno' -__version__ = 'V.5.2' -__all__ = ["Curve", "Point", "PointWOrder", "EclException"] -__doc__ = """ ECL: library includes basic operations on Elliptic curves, 2 cipher and a digital signature protocol. +__version__ = 'V.5.5' +__all__ = ["Curve", "Point", "PointWOrder", "EclError"] +__doc__ = """ ECL: library includes basic operations on Elliptic Curves, 2 ciphers and a digital signature protocol. includes: class: Point, PointWOrder, Curve algorithm: Diffie-Hellman, ElGamal, Koblitz, ECDSA built-in: Nist's standard curves and points -exception: EclException, DiffieHellmanError, KoblitzFailError, ECDSAError, ElGamalError +exception: EclError, DiffieHellmanError, KoblitzFailError, ECDSAError, ElGamalError """ \ No newline at end of file diff --git a/ECL/curve.py b/ECL/curve.py index e7ed637..c12fb88 100644 --- a/ECL/curve.py +++ b/ECL/curve.py @@ -16,7 +16,7 @@ limitations under the License. """ __author__ = 'ivansarno' -__version__ = 'V.5.2' +__version__ = 'V.5.5' __doc__ = """Implementation of Prime Elliptic Curves""" @@ -62,8 +62,7 @@ def copy(self): :return: Curve copy of self :rtype: Curve """ - ris = Curve(self.__a, self.__b, self.__prime) - return ris + return Curve(self.__a, self.__b, self.__prime) def __str__(self): return "a: %x\nb: 0x%x\nprime: 0x%x\n" % (self.__a, self.__b, self.__prime) diff --git a/ECL/diffie_hellman.py b/ECL/diffie_hellman.py index aa8af63..1b18768 100644 --- a/ECL/diffie_hellman.py +++ b/ECL/diffie_hellman.py @@ -16,16 +16,18 @@ limitations under the License. """ from typing import Callable -from ECL.utility import EclException + +import sys + +from ECL.utility import EclError from ECL.point import Point from ECL.point_with_order import PointWOrder __author__ = 'ivansarno' -__version__ = 'V.5.2' +__version__ = 'V.5.5' __doc__ = """Diffie-Hellman's public key system. class: DiffieHellman - exception: DiffieHellmanError """ @@ -39,7 +41,7 @@ class DiffieHellman: def __init__(self, base_point: PointWOrder, generator: Callable[[int], int]): """ :param base_point: Point used as base, can be used a standard point from ECL.std_curves - :param generator: random number generator, return a random int of size passed by parameter + :param generator: random number generator, return a random int of size in bits passed by parameter """ self.__point = base_point @@ -54,11 +56,13 @@ def step1(self) -> Point: :return: Point to sand to partner """ self.__secret = self.__gen(self.__point.order.bit_length()) % self.__point.order + while self.__secret < sys.maxsize: + self.__secret = self.__gen(self.__point.order.bit_length()) % self.__point.order self.__sync = True return self.__point * self.__secret def step2(self, partner_point: Point) -> Point: - """Take result of partner step1 and return the key as Point + """Take result of partner's step1 and return the key as Point :param partner_point: Point received by partner :return: the key @@ -79,10 +83,6 @@ def key(self) -> Point: return self.__key -class DiffieHellmanError(EclException): +class DiffieHellmanError(EclError): """DiffieHellman algorithm fail.""" - def __init__(self, value): - self.value = value - - def __str__(self): - return self.value.__repr__() + pass diff --git a/ECL/ecdsa.py b/ECL/ecdsa.py index 5c82339..4c1ec5f 100644 --- a/ECL/ecdsa.py +++ b/ECL/ecdsa.py @@ -17,12 +17,15 @@ """ import hashlib from typing import Callable, Tuple + +import sys + from ECL.point import Point from ECL.point_with_order import PointWOrder -from ECL.utility import inverse, EclException +from ECL.utility import inverse, EclError __author__ = 'ivansarno' -__version__ = 'V.5.2' +__version__ = 'V.5.5' __doc__ = """ECDSA digital signature algorithm classes: PrivateKey, PublicKey, Signature @@ -31,9 +34,18 @@ """ +def _standard_hash(message: bytearray) -> Tuple[int, int]: + """Default hash fun, sha512, little endian, unsigned""" + sha = hashlib.sha512() + sha.update(message) + message_hash = int.from_bytes(sha.digest(), byteorder='little', signed=False) + return message_hash, 512 + + class Signature: def __init__(self, first: int, second: int): - """This constructor is for internal use, user must resume a message from a representation string""" + """This constructor is for internal use, user must resume a message from + a representation string or use deserialization""" self.__first = first self.__second = second @@ -55,7 +67,7 @@ def __repr__(self): class PublicKey: def __init__(self, base_point: PointWOrder, key_point: Point): """This constructor is for internal use, user must generates the public key from a PrivateKey object - or resume it from a representation string""" + or resume it from a representation string or use deserialization""" self.__key = key_point self.__base = base_point @@ -85,12 +97,28 @@ def check(self, message: bytearray, signature: Signature, hash_fun=_standard_has p = (self.__base * u1) + (self.__key * u2) return signature.first == p.x % self.__base.order + def try_unlock_key(self, secret: int): + """Try to recovers the private key associated to this public key using a possible secret number, + return None if fails. + + :return: a PrivateKey or None + :param secret: a great positive number, it must be the original secret number + :rtype: PrivateKey + """ + secret %= self.__base.order + if secret < 2: + return None + if self.__base * secret == self.__key: + return PrivateKey(self.__base, secret) + else: + return None + class PrivateKey: - def __init__(self, base_point: PointWOrder, key: int): + def __init__(self, base_point: PointWOrder, secret: int): """This constructor is for internal use, user must generates the private key with keygen method - or resume it from a representation string""" - self.__key = key + or resume it from a representation string or use deserialization""" + self.__key = secret self.__base = base_point @staticmethod @@ -103,10 +131,26 @@ def keygen(base_point: PointWOrder, generator: Callable[[int], int]): if base_point.order.bit_length() > 512: raise ECDSAError("bit length of order of base point > 512") secret = generator(base_point.order.bit_length()) % base_point.order - while secret < 2: + while secret < sys.maxsize: secret = generator(base_point.order.bit_length()) % base_point.order return PrivateKey(base_point, secret) + @staticmethod + def keycreate(base_point: PointWOrder, secret: int): + """Create a PrivateKey from a secret number. + + :param secret: secret number, it must be a great positive number + :return: a Private Key + :raise ECDSAError: bit length of order of base point > 512, or key too small + :rtype: PrivateKey + """ + if base_point.order.bit_length() > 512: + raise ECDSAError("bit length of order of base point > 512") + secret %= base_point.order + if secret < sys.maxsize: + raise ECDSAError("the secret number not pass the security tests") + return PrivateKey(base_point, secret) + @property def public_key(self) -> PublicKey: return PublicKey(self.__base, self.__base * self.__key) @@ -136,18 +180,7 @@ def __repr__(self): return "ECL.ecdsa.PrivateKey( %s, 0x%x)" % (self.__base.__repr__(), self.__key) -class ECDSAError(EclException): +class ECDSAError(EclError): """ECDSAError algorithm fail.""" - def __init__(self, value): - self.value = value - - def __str__(self): - return self.value.__repr__() + pass - -def _standard_hash(message: bytearray) -> Tuple[int, int]: - """Default hash fun, sha512, little endian, unsigned""" - sha = hashlib.sha512() - sha.update(message) - message_hash = int.from_bytes(sha.digest(), byteorder='little', signed=False) - return message_hash, 512 diff --git a/ECL/elgamal.py b/ECL/elgamal.py index 125bd3f..d7b884c 100644 --- a/ECL/elgamal.py +++ b/ECL/elgamal.py @@ -15,15 +15,17 @@ See the License for the specific language governing permissions and limitations under the License. """ -from typing import Callable +from typing import Callable, Tuple + +import sys from ECL import koblitz -from ECL.utility import EclException +from ECL.utility import EclError from ECL.point import Point from ECL.point_with_order import PointWOrder __author__ = 'ivansarno' -__version__ = 'V.5.2' +__version__ = 'V.5.5' __doc__ = """El Gamal's cipher. classes: ElGamalMessage, PublicKey, PrivateKey @@ -34,7 +36,8 @@ class ElGamalMessage: def __init__(self, first: Point, second: Point, padding: int): - """This constructor is for internal use, user must resume a message from a representation string""" + """This constructor is for internal use, user must resume a message from a + representation string or use deserialization""" self.__first = first self.__second = second self.__padding = padding @@ -61,7 +64,7 @@ def __repr__(self): class PublicKey: def __init__(self, base_point: PointWOrder, key_point: Point): """This constructor is for internal use, user must generates the public key from a PrivateKey object - or resume it from a representation string""" + or resume it from a representation string or use deserialization""" self.__key = key_point self.__base = base_point @@ -74,10 +77,26 @@ def encrypt(self, message: int, generator: Callable[[int], int]) -> ElGamalMessa message, padding = koblitz.iterative_encode(message, self.__base.curve) fact = generator(self.__base.order.bit_length()) % self.__base.order - while fact < 2: + while fact < sys.maxsize: fact = generator(self.__base.order.bit_length()) % self.__base.order return ElGamalMessage(self.__base * fact, message + self.__key * fact, padding) + def try_unlock_key(self, secret: int): + """Try to recovers the private key associated to this public key using a possible secret number, + return None if fails. + + :return: a PrivateKey or None + :param secret: a great positive number, it must be the original secret number + :rtype: PrivateKey + """ + secret %= self.__base.order + if secret < sys.maxsize: + return None + if self.__base * secret == self.__key: + return PrivateKey(self.__base, secret) + else: + return None + def __repr__(self): return "ECL.elgamal.PublicKey( %s, %s)" % (self.__base.__repr__(), self.__key.__repr__()) @@ -85,7 +104,7 @@ def __repr__(self): class PrivateKey: def __init__(self, base_point: PointWOrder, key: int): """This constructor is for internal use, user must generates the private key with keygen method - or resume it from a representation string""" + or resume it from a representation string or use deserialization""" self.__key = key self.__base = base_point @@ -95,10 +114,24 @@ def keygen(base_point: PointWOrder, generator: Callable[[int], int]): :param generator: random number generator, return a positive integer with bit length passed as parameter """ secret = generator(base_point.order.bit_length()) % base_point.order - while secret < 2: + while secret < sys.maxsize: secret = generator(base_point.order.bit_length()) % base_point.order return PrivateKey(base_point, secret) + @staticmethod + def keycreate(base_point: PointWOrder, key: int): + """Create a PrivateKey from a secret number. + + :param key: secret number, it must be a great positive number + :return: a Private Key + :raise ElGamalError: bit length of order of base point > 512, or key too small + :rtype: PrivateKey + """ + key %= base_point.order + if key < sys.maxsize: + raise ElGamalError("key, the secret number, not pass the security tests") + return PrivateKey(base_point, key) + @property def public_key(self) -> PublicKey: return PublicKey(self.__base, self.__base * self.__key) @@ -112,10 +145,6 @@ def __repr__(self): return "ECL.elgamal.PrivateKey( %s, 0x%x)" % (self.__base.__repr__(), self.__key) -class ElGamalError(EclException): +class ElGamalError(EclError): """ElGamal algorithm fail.""" - def __init__(self, value): - self.value = value - - def __str__(self): - return self.value.__repr__() + pass diff --git a/ECL/koblitz.py b/ECL/koblitz.py index 687bac3..4489194 100644 --- a/ECL/koblitz.py +++ b/ECL/koblitz.py @@ -17,11 +17,11 @@ """ from typing import Tuple from ECL.curve import Curve -from ECL.utility import is_square, EclException, square_root +from ECL.utility import is_square, EclError, square_root from ECL.point import Point __author__ = 'ivansarno' -__version__ = 'V.5.2' +__version__ = 'V.5.5' __doc__ = """Implementation of Koblitz algorithm. functions: encode, decode, iterative_encode @@ -35,11 +35,11 @@ def encode(message: int, padding: int, curve: Curve) -> Point: :param curve: Curve of point returned :return: Point of curve - :raise: KoblitzFailError + :raise KoblitzFailError: All curves are supported but performances depends on prime number """ - if message * (padding + 1) < curve.prime: + if message * (padding + 1) < curve.prime and padding > 0: message *= padding i = 0 x = message @@ -83,10 +83,6 @@ def iterative_encode(message: int, curve: Curve) -> Tuple[Point, int]: return point, padding -class KoblitzFailError(EclException): +class KoblitzFailError(EclError): """Koblitz algorithm fail, point not found.""" - def __init__(self, value): - self.value = value - - def __str__(self): - return self.value.__repr__() + pass diff --git a/ECL/point.py b/ECL/point.py index b78e2bc..d109f70 100644 --- a/ECL/point.py +++ b/ECL/point.py @@ -19,7 +19,7 @@ from ECL.utility import inverse __author__ = 'ivansarno' -__version__ = 'V.5.2' +__version__ = 'V.5.5' __doc__ = """Implementation of Point of Elliptic Curve classes: Point @@ -120,13 +120,13 @@ def copy(self): return ris def __neg__(self): - """Arithmetic _negation of a Point. + """Arithmetic negation of a Point. :return: -self :rtype: Point """ if self.__infinite: - return self.copy() + return self.infinitepoint(self.curve) return Point(self.__curve, self.__x, -self.__y % self.__curve.prime) def __add__(self, other): @@ -139,7 +139,7 @@ def __add__(self, other): if self.__infinite: return other.copy() elif other.__infinite: - return self.copy + return self.copy() elif self == other: return self * 2 elif are_opposites(self, other): @@ -160,7 +160,7 @@ def __sub__(self, other): if self.__infinite: return other.__neg__() elif other.__infinite: - return self.copy + return self.copy() elif are_opposites(self, other): return self * 2 elif self == other: @@ -211,7 +211,7 @@ def _mul(self, other: int): self.__infinite = temp.__infinite def check(self, curve: Curve) -> bool: - """Check if self is a valid point of __curve. + """Check if self is a valid point of curve. :param curve: curve whose membership tested point :return: True if self is a valid point of curve diff --git a/ECL/point_with_order.py b/ECL/point_with_order.py index 324b428..d7dddc5 100644 --- a/ECL/point_with_order.py +++ b/ECL/point_with_order.py @@ -18,12 +18,12 @@ from ECL.curve import Curve from ECL.point import Point __author__ = 'ivansarno' -__version__ = 'V.5.2' +__version__ = 'V.5.5' __doc__ = """Point with extra member, the order""" -class PointWOrder (Point): - """EC Point with order parameter. +class PointWOrder(Point): + """EC Point with order member. order == -1 is garbage value """ diff --git a/ECL/std_curves.py b/ECL/std_curves.py index 31c507a..bdfc25c 100644 --- a/ECL/std_curves.py +++ b/ECL/std_curves.py @@ -19,7 +19,7 @@ from ECL.point_with_order import PointWOrder __author__ = 'ivansarno' -__version__ = 'V.5.2' +__version__ = 'V.5.5' __doc__ = """Nist's standard curves and points (the number in the name is the bit of order) diff --git a/ECL/utility.py b/ECL/utility.py index 9afec98..19153ff 100644 --- a/ECL/utility.py +++ b/ECL/utility.py @@ -19,8 +19,10 @@ import os from typing import Tuple +import sys + __author__ = 'ivansarno' -__version__ = 'V.5.2' +__version__ = 'V.5.5' __doc__ = """ built-in random number generator, root exception, inverse calculation and modular square root. This functions are used by other modules""" @@ -29,6 +31,7 @@ def inverse(number: int, modulus: int) -> int: """Inverse operation in modular arithmetic. :return: inverse of number mod module + use iterative version of extended Euclide's algorithm """ if modulus == 0: return 0 @@ -36,7 +39,7 @@ def inverse(number: int, modulus: int) -> int: while buffer[-1] != 0: buffer.append(buffer[-2] % buffer[-1]) - buffer.pop() + buffer.pop() # remove unused temporary value temp = 0 intermediate = 1 result = 1 @@ -53,55 +56,56 @@ def inverse(number: int, modulus: int) -> int: def is_square(num: int, module: int) -> bool: - """Return if num is a square mod module. + """Return if num is a square mod module when module is a prime number != 2. :return: exist y ** 2 mod module == num """ - e = module // 2 - r = pow(num, e, module) - return r == 1 - + return pow(num, module >> 1, module) == 1 -def generator(size: int) -> int: +if sys.version_info.minor < 6: + def generator(size: int) -> int: """ Random Number generator for test, is not safe. - :param size: number of bit of random number - :return: random int of size bit - """ - temp = os.urandom(size // 8) + :param size: number of bit of random number + :return: random int of size bit + """ + temp = os.urandom(size // 8 + 1) return int.from_bytes(temp, 'little') +else: + def generator(size: int) -> int: + """ Random Number generator, use Python's secrets module. + :param size: number of bit of random number + :return: random int of size bit + """ + import secrets + return abs(secrets.randbits(size)) -class EclException(Exception): - """Generic ECL exception.""" - def __init__(self, value): - self.value = value - def __str__(self): - return self.value.__repr__() +class EclError(Exception): + """Generic ECL exception.""" + pass def square_root(number: int, prime: int) -> int: """Return square roots of number mod prime - the cases in which prime == 2 is not supported because not occurs in this program + the cases in which prime == 2 is not supported because not occurs in this library """ if number == 0: return 0 - if (prime % 4) == 3: - return pow(number, (prime + 1) // 4, prime) - if (prime % 8) == 5: - sign = pow(number, (prime - 1) // 4, prime) - if sign == 1: - return pow(number, (prime + 3) // 8, prime) - else: - return (2 * number) * pow((4 * number), (prime - 5) // 8, prime) + if (prime & 3) == 3: + return pow(number, (prime + 1) >> 2, prime) + if (prime & 7) == 5: + v = int(pow(number << 1, (prime-5) >> 3)) + i = ((number * (v ** 2)) << 1) % prime + return (number * v * (i - 1)) % prime r = shanks(number, prime) return r[1] def legendre_symbol(number: int, prime: int) -> int: - ls = pow(number, (prime - 1) // 2, prime) + ls = pow(number, prime >> 1, prime) if ls == prime - 1: return -1 return ls @@ -111,16 +115,16 @@ def shanks(number: int, prime: int) -> Tuple[int, int]: """Return square roots of number mod prime.""" q = prime - 1 s = 0 - while q % 2 == 0: + while q & 1 == 0: s += 1 - q //= 2 + q >>= 1 z = 1 while legendre_symbol(z, prime) != -1: z += 1 c = pow(z, q, prime) - x = pow(number, (q + 1) // 2, prime) + x = pow(number, (q + 1) >> 1, prime) t = pow(number, q, prime) m = s @@ -130,9 +134,9 @@ def shanks(number: int, prime: int) -> Tuple[int, int]: for i in range(1, m): if pow(t, e, prime) == 1: break - e *= 2 + e <<= 1 - b = pow(c, 2 ** (m - i - 1), prime) + b = pow(c, 1 << (m - i - 1), prime) x = (x * b) % prime t = (t * b * b) % prime c = (b * b) % prime diff --git a/changelog.txt b/changelog.txt index fdc7c3f..ab364eb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,20 @@ +Version 5.5 +Various Fixes + +Version 5.4: +1) better tests +2) test module moved in different package +3) old tests refactored as parametric_tests +4) ElGamal and ECDSA now support private key creation and resume from user's secret number +5) (Python 3.6+ only) Built-in random number generator use new Python's secrets module, for secure source of randomness +6) bug fixes + +Version 5.3: +1)several optimizations and fixes +2)rename EclException in EclError + +Version 5.3 is not backward compatible + Version 5.2: ECDSA parametric hash function diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..45fdf38 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,25 @@ +""" + Elliptic Curve Library + + Copyright 2017 Ivan Sarno + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +from tests import diffie_hellman_test, ecdsa_test, elgamal_test, koblitz_test, point_test, utility_test +__author__ = 'ivansarno' +__version__ = 'V.1.0' + + +def ecl_test(): + return diffie_hellman_test.test_dh() and ecdsa_test.test_ecdsa() and elgamal_test.test_elgamal() and koblitz_test.test_koblitz() \ + and point_test.test_point() and utility_test.test_utility() diff --git a/tests/diffie_hellman_test.py b/tests/diffie_hellman_test.py new file mode 100644 index 0000000..8260f6a --- /dev/null +++ b/tests/diffie_hellman_test.py @@ -0,0 +1,52 @@ +""" + Elliptic Curve Library + + Copyright 2017 Ivan Sarno + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +import ECL +from ECL import utility + +__author__ = 'ivansarno' +__version__ = 'V.1.0' + + +def test_functionality() -> bool: + user1 = ECL.DiffieHellman(ECL.std_curves.PointP192(), utility.generator) + user2 = ECL.DiffieHellman(ECL.std_curves.PointP192(), utility.generator) + message1 = user1.step1() + message2 = user2.step1() + user2.step2(message1) + user1.step2(message2) + return user1.key == user2.key + + +def test_synch_dh() -> bool: + dh = ECL.DiffieHellman(ECL.std_curves.PointP192(), utility.generator) + try: + k = dh.key + return False + except ECL.diffie_hellman.DiffieHellmanError: + pass + + try: + dh.step2(ECL.std_curves.PointP192()._doubles()) + return False + except ECL.diffie_hellman.DiffieHellmanError: + pass + return True + + +def test_dh() -> bool: + return test_functionality() and test_synch_dh() diff --git a/tests/ecdsa_test.py b/tests/ecdsa_test.py new file mode 100644 index 0000000..baef95b --- /dev/null +++ b/tests/ecdsa_test.py @@ -0,0 +1,101 @@ +""" + Elliptic Curve Library + + Copyright 2017 Ivan Sarno + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +import random + +import ECL +from ECL import ecdsa +from ECL import std_curves +from ECL import utility +from ECL.ecdsa import * + +__author__ = 'ivansarno' +__version__ = 'V.1.0' + + +def test_functionality() -> bool: + base = std_curves.PointP192() + private = PrivateKey.keygen(base, utility.generator) + public = private.public_key + message = bytearray(random.randrange(1)) + signature = private.sign(message, utility.generator) + # true signature check + if not public.check(message, signature): + return False + # fake signatures check, should not be recognized + fake_signature = Signature(signature.first + 1, signature.second) + if public.check(message, fake_signature): + return False + fake_signature = Signature(signature.first, signature.second - 1) + if public.check(message, fake_signature): + return False + fake_signature = Signature(signature.first, 0) + if public.check(message, fake_signature): + return False + fake_signature = Signature(signature.first, base.order + 1) + if public.check(message, fake_signature): + return False + fake_signature = Signature(0, signature.second) + if public.check(message, fake_signature): + return False + fake_signature = Signature(base.order + 1, signature.second) + if public.check(message, fake_signature): + return False + return True + + +def test_rapresentation() -> bool: + private = PrivateKey.keygen(std_curves.PointP192(), utility.generator) + public = private.public_key + message = bytearray(random.randrange(1)) + signature = private.sign(message, utility.generator) + if not vars(signature) == vars(eval(signature.__repr__())): + return False + if not vars(public) == vars(eval(public.__repr__())): + return False + if not vars(private) == vars(eval(private.__repr__())): + return False + return True + + +def test_key_creation() -> bool: + try: + key1 = ecdsa.PrivateKey.keycreate(ECL.std_curves.PointP192(), -342) + key2 = ecdsa.PrivateKey.keycreate(ECL.std_curves.PointP192(), 1) + return False + except ECDSAError: + pass + secret = utility.generator(192) + private = ecdsa.PrivateKey.keycreate(ECL.std_curves.PointP192(), secret) + public = private.public_key + rprivate = public.try_unlock_key(-3523) + if not rprivate is None: + return False + rprivate = public.try_unlock_key(1) + if not rprivate is None: + return False + rprivate = public.try_unlock_key(secret + 1) + if not rprivate is None: + return False + rprivate = public.try_unlock_key(secret) + if rprivate == private: + return False + return True + + +def test_ecdsa() -> bool: + return test_functionality() and test_functionality() and test_key_creation() diff --git a/tests/elgamal_test.py b/tests/elgamal_test.py new file mode 100644 index 0000000..5bbe7e0 --- /dev/null +++ b/tests/elgamal_test.py @@ -0,0 +1,82 @@ +""" + Elliptic Curve Library + + Copyright 2017 Ivan Sarno + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +import random +import ECL +from ECL import elgamal, utility +from ECL.elgamal import ElGamalError + +__author__ = 'ivansarno' +__version__ = 'V.1.0' + + +def test_functionality() -> bool: + private = elgamal.PrivateKey.keygen(ECL.std_curves.PointP192(), utility.generator) + public = private.public_key + message = random.randrange(1) + cipher = public.encrypt(message, utility.generator) + cipher = private.decrypt(cipher) + return cipher == message + + +def test_out_of_range() -> bool: + public = elgamal.PrivateKey.keygen(ECL.std_curves.PointP192(), utility.generator).public_key + message = ECL.std_curves.PointP192().order + 200 + try: + cipher = public.encrypt(message, utility.generator) + return False + except ElGamalError: + return True + + +def test_rapresentation() -> bool: + private = elgamal.PrivateKey.keygen(ECL.std_curves.PointP192(), utility.generator) + public = private.public_key + message = public.encrypt(random.randrange(1), utility.generator) + rmessage = eval(message.__repr__()) + rpublic = eval(public.__repr__()) + rprivate = eval(private.__repr__()) + return vars(private) == vars(rprivate) and vars(message) == vars(rmessage) and vars(public) == vars(rpublic) + + +def test_key_creation() -> bool: + try: + key1 = elgamal.PrivateKey.keycreate(ECL.std_curves.PointP192(), -342) + key2 = elgamal.PrivateKey.keycreate(ECL.std_curves.PointP192(), 1) + return False + except ElGamalError: + pass + secret = utility.generator(192) + private = elgamal.PrivateKey.keycreate(ECL.std_curves.PointP192(), secret) + public = private.public_key + rprivate = public.try_unlock_key(-3523) + if not rprivate is None: + return False + rprivate = public.try_unlock_key(1) + if not rprivate is None: + return False + rprivate = public.try_unlock_key(secret + 1) + if not rprivate is None: + return False + rprivate = public.try_unlock_key(secret) + if rprivate == private: + return False + return True + + +def test_elgamal() -> bool: + return test_functionality() and test_out_of_range() and test_rapresentation() and test_key_creation() diff --git a/tests/koblitz_test.py b/tests/koblitz_test.py new file mode 100644 index 0000000..163db02 --- /dev/null +++ b/tests/koblitz_test.py @@ -0,0 +1,28 @@ +""" + Elliptic Curve Library + + Copyright 2017 Ivan Sarno + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +import ECL +import random + +__author__ = 'ivansarno' +__version__ = 'V.1.0' + + +def test_koblitz() -> bool: + n = random.randrange(20000) + point, padding = ECL.koblitz.iterative_encode(n, ECL.std_curves.CurveP192()) + return ECL.koblitz.decode(point, padding) == n and point.check(ECL.std_curves.CurveP192()) diff --git a/ECL/test.py b/tests/parametric_tests.py similarity index 56% rename from ECL/test.py rename to tests/parametric_tests.py index d0a7fb6..86ef432 100644 --- a/ECL/test.py +++ b/tests/parametric_tests.py @@ -17,20 +17,22 @@ """ import random import ECL +from ECL import Point +from ECL import PointWOrder from ECL import utility, ecdsa, elgamal __author__ = 'ivansarno' -__version__ = 'V.5.2' +__version__ = 'V.1.0' __doc__ = """Test for package's features""" -def test(curve=ECL.std_curves.CurveP192, point=ECL.std_curves.PointP192, generator=utility.generator) -> bool: +def test(point: PointWOrder=ECL.std_curves.PointP192(), generator=utility.generator) -> bool: try: - result = test_arithmetic(curve, point) - result &= test_diffie_hellman(point, generator) - result &= test_el_gamal_koblitz(point, generator) + result = test_arithmetic(point.copy()) + result &= test_diffie_hellman(point.copy(), generator) + result &= test_el_gamal_koblitz(point.copy(), generator) result &= test_ecdsa(generator=generator) - except ECL.EclException: + except ECL.EclError: print("test ERROR: EclException Raised") return False except Exception: @@ -39,9 +41,9 @@ def test(curve=ECL.std_curves.CurveP192, point=ECL.std_curves.PointP192, generat return result -def test_diffie_hellman(point=ECL.std_curves.PointP192, generator=utility.generator) -> bool: - user1 = ECL.DiffieHellman(point(), generator) - user2 = ECL.DiffieHellman(point(), generator) +def test_diffie_hellman(point: PointWOrder=ECL.std_curves.PointP192(), generator=utility.generator) -> bool: + user1 = ECL.DiffieHellman(point, generator) + user2 = ECL.DiffieHellman(point, generator) message1 = user1.step1() message2 = user2.step1() user2.step2(message1) @@ -54,8 +56,8 @@ def test_diffie_hellman(point=ECL.std_curves.PointP192, generator=utility.genera return False -def test_el_gamal_koblitz(point=ECL.std_curves.PointP192, generator=utility.generator) -> bool: - private = elgamal.PrivateKey.keygen(point(), generator) +def test_el_gamal_koblitz(point: PointWOrder=ECL.std_curves.PointP192(), generator=utility.generator) -> bool: + private = elgamal.PrivateKey.keygen(point, generator) public = private.public_key message = random.randint(1, 2**32) cipher = public.encrypt(message, generator) @@ -68,24 +70,40 @@ def test_el_gamal_koblitz(point=ECL.std_curves.PointP192, generator=utility.gene return False -def test_arithmetic(curve=ECL.std_curves.CurveP192, point=ECL.std_curves.PointP192) -> bool: - inf = ECL.point.Point.infinitepoint(curve()) - p = point() - cond1 = (p * 3) == ((p + p + p + p + p) - (p * 2)) - cond2 = (-p) == (inf - p) - if not inf and cond1 and cond2: - print("test arithmetic OK") - return True - else: +def test_arithmetic(point: Point) -> bool: + inf = point.infinitepoint(point.curve) + if point != (point + inf) or point != (point - inf): + print("test arithmetic ERROR") + return False + p = point.copy() + p._doubles() + inf2 = inf.copy() + inf2._doubles() + if point + point != p or inf2 != inf: + print("test arithmetic ERROR") + return False + if -point != inf - point: + print("test arithmetic ERROR") + return False + a = random.randrange(500) + b = random.randrange(a // 2) + p = inf.copy() + for i in range(0, a): + p += point + if point * a != p: print("test arithmetic ERROR") return False + if point * (a - b) != (point * a) - (point * b): + return False + print("test arithmetic OK") + return True -def test_ecdsa(message: bytearray=None, point=ECL.std_curves.PointP192, generator=utility.generator) -> bool: +def test_ecdsa(message: bytearray=None, point: PointWOrder=ECL.std_curves.PointP192(), generator=utility.generator) -> bool: if message is None: message = generator(2000) message = message.to_bytes(message.bit_length()//8 + 1, 'little') - priv = ecdsa.PrivateKey.keygen(point(), generator) + priv = ecdsa.PrivateKey.keygen(point, generator) pub = priv.public_key sign = priv.sign(message, generator) if not pub.check(message, sign): diff --git a/tests/point_test.py b/tests/point_test.py new file mode 100644 index 0000000..848ab1f --- /dev/null +++ b/tests/point_test.py @@ -0,0 +1,58 @@ +""" + Elliptic Curve Library + + Copyright 2017 Ivan Sarno + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +import ECL +from ECL import * +import random + +__author__ = 'ivansarno' +__version__ = 'V.1.0' + + +def test_rapresentation() -> bool: + point = ECL.std_curves.PointP192() + dpoint = point._doubles() + return point == eval(point.__repr__()) and dpoint == eval(dpoint.__repr__()) + + +def test_arithmetic() -> bool: + point = ECL.std_curves.PointP192() + inf = point.infinitepoint(point.curve) + if point != (point + inf) or point != (point - inf): + return False + p = point.copy() + p._doubles() + inf2 = inf.copy() + inf2._doubles() + if point + point != p or inf2 != inf: + return False + if -point != inf - point: + return False + a = random.randrange(100) + b = random.randrange(a // 2) + p = inf.copy() + for i in range(0, a): + p += point + if point * a != p: + return False + if point * (a - b) != (point * a) - (point * b): + return False + return True + + +def test_point(): + return test_rapresentation() and test_arithmetic() diff --git a/tests/utility_test.py b/tests/utility_test.py new file mode 100644 index 0000000..b89f253 --- /dev/null +++ b/tests/utility_test.py @@ -0,0 +1,59 @@ +""" + Elliptic Curve Library + + Copyright 2017 Ivan Sarno + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +from ECL.utility import * +import random + +__author__ = 'ivansarno' +__version__ = 'V.1.0' + +primes = [17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, + 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, + 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, + 383, 389, 397, 401, 409] + + +def test_inverse() -> bool: + p = random.choice(primes) + t = random.randint(2, p-1) + return (inverse(t, p) * t) % p == 1 + + +def test_is_square() -> bool: + p = random.choice(primes) + t = random.randint(2, p - 1) + s = t*t % p + return is_square(s, p) + + +def test_square_root() -> bool: + t = random.randint(1, 7) + s = square_root(t * t % 7, 7) + r1 = s == t or 7 - t == s + + t = random.randint(1, 13) + s = square_root(t * t % 13, 13) + r2 = s == t or 13 - t == s + + t = random.randint(1, 16) + s = square_root(t * t % 17, 17) + r3 = s == t or 17 - t == s + return r1 and r2 and r3 + + +def test_utility() -> bool: + return test_inverse() and test_is_square() and test_square_root()