diff options
| author | J08nY | 2019-04-22 18:04:44 +0200 |
|---|---|---|
| committer | J08nY | 2019-04-22 18:04:44 +0200 |
| commit | 24b9b3958a54bdf7f18df62ae14199749934e3c2 (patch) | |
| tree | a758d0289337f6aee7540dca4a94ea31ced69217 | |
| parent | 037194fd8cfe50aa2367c2f3c7fae5b41e7b46f9 (diff) | |
| download | pyecsca-24b9b3958a54bdf7f18df62ae14199749934e3c2.tar.gz pyecsca-24b9b3958a54bdf7f18df62ae14199749934e3c2.tar.zst pyecsca-24b9b3958a54bdf7f18df62ae14199749934e3c2.zip | |
| -rw-r--r-- | README.md | 1 | ||||
| -rw-r--r-- | docs/index.rst | 1 | ||||
| -rw-r--r-- | pyecsca/ec/group.py | 10 | ||||
| -rw-r--r-- | pyecsca/ec/key_agreement.py | 69 | ||||
| -rw-r--r-- | pyecsca/ec/point.py | 9 | ||||
| -rw-r--r-- | pyecsca/ec/signature.py | 171 | ||||
| -rw-r--r-- | setup.py | 3 | ||||
| -rw-r--r-- | test/ec/test_key_agreement.py | 25 | ||||
| -rw-r--r-- | test/ec/test_point.py | 20 | ||||
| -rw-r--r-- | test/ec/test_signature.py | 48 | ||||
| -rw-r--r-- | unittest.cfg | 3 |
11 files changed, 351 insertions, 9 deletions
@@ -19,6 +19,7 @@ and ECC simulation in the [*pyecsca.ec*](pyecsca/ec) package. - [matplotlib](https://matplotlib.org/) - [atpublic](https://public.readthedocs.io/) - [fastdtw](https://github.com/slaypni/fastdtw) + - asn1crypto *pyecsca* contains data from the [Explicit-Formulas Database](https://www.hyperelliptic.org/EFD/index.html) by Daniel J. Bernstein and Tanja Lange. The data was partially changed, to make working with it easier. diff --git a/docs/index.rst b/docs/index.rst index fa5916e..525dff6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -36,6 +36,7 @@ Requirements - matplotlib_ - atpublic_ - fastdtw_ + - asn1crypto *pyecsca* contains data from the `Explicit-Formulas Database`_ by Daniel J. Bernstein and Tanja Lange. diff --git a/pyecsca/ec/group.py b/pyecsca/ec/group.py index 4c471cc..af0dae2 100644 --- a/pyecsca/ec/group.py +++ b/pyecsca/ec/group.py @@ -1,5 +1,3 @@ -from typing import Optional - from public import public from .curve import EllipticCurve @@ -11,11 +9,11 @@ class AbelianGroup(object): curve: EllipticCurve generator: Point neutral: Point - order: Optional[int] - cofactor: Optional[int] + order: int + cofactor: int - def __init__(self, curve: EllipticCurve, generator: Point, neutral: Point, order: int = None, - cofactor: int = None): + def __init__(self, curve: EllipticCurve, generator: Point, neutral: Point, order: int, + cofactor: int): self.curve = curve self.generator = generator self.neutral = neutral diff --git a/pyecsca/ec/key_agreement.py b/pyecsca/ec/key_agreement.py new file mode 100644 index 0000000..5bd2edd --- /dev/null +++ b/pyecsca/ec/key_agreement.py @@ -0,0 +1,69 @@ +import hashlib +from typing import Optional, Any + +from public import public + +from .mult import ScalarMultiplier +from .point import Point + + +@public +class KeyAgreement(object): + mult: ScalarMultiplier + pubkey: Point + privkey: int + hash_algo: Optional[Any] + + def __init__(self, mult: ScalarMultiplier, pubkey: Point, privkey: int, + hash_algo: Optional[Any] = None): + self.mult = mult + self.pubkey = pubkey + self.privkey = privkey + self.hash_algo = hash_algo + + def perform(self): + point = self.mult.multiply(self.privkey, self.pubkey) + affine_point = point.to_affine() # TODO: This conversion should be somehow added to the context + x = int(affine_point.x) + p = self.mult.group.curve.prime + n = (p.bit_length() + 7) // 8 + result = x.to_bytes(n, byteorder="big") + if self.hash_algo is not None: + result = self.hash_algo(result).digest() + return result + + +@public +class ECDH_NONE(KeyAgreement): + def __init__(self, mult: ScalarMultiplier, pubkey: Point, privkey: int): + super().__init__(mult, pubkey, privkey) + + +@public +class ECDH_SHA1(KeyAgreement): + def __init__(self, mult: ScalarMultiplier, pubkey: Point, privkey: int): + super().__init__(mult, pubkey, privkey, hashlib.sha1) + + +@public +class ECDH_SHA224(KeyAgreement): + def __init__(self, mult: ScalarMultiplier, pubkey: Point, privkey: int): + super().__init__(mult, pubkey, privkey, hashlib.sha224) + + +@public +class ECDH_SHA256(KeyAgreement): + def __init__(self, mult: ScalarMultiplier, pubkey: Point, privkey: int): + super().__init__(mult, pubkey, privkey, hashlib.sha256) + + +@public +class ECDH_SHA384(KeyAgreement): + def __init__(self, mult: ScalarMultiplier, pubkey: Point, privkey: int): + super().__init__(mult, pubkey, privkey, hashlib.sha384) + + +@public +class ECDH_SHA512(KeyAgreement): + def __init__(self, mult: ScalarMultiplier, pubkey: Point, privkey: int): + super().__init__(mult, pubkey, privkey, hashlib.sha512) diff --git a/pyecsca/ec/point.py b/pyecsca/ec/point.py index b11c3f1..75b4a1e 100644 --- a/pyecsca/ec/point.py +++ b/pyecsca/ec/point.py @@ -1,5 +1,5 @@ from copy import copy -from typing import Mapping +from typing import Mapping,Any from public import public @@ -19,6 +19,13 @@ class Point(object): self.coordinate_model = model self.coords = coords + def __getattribute__(self, name: Any): + if "coords" in super().__getattribute__("__dict__"): + coords = super().__getattribute__("coords") + if name in coords: + return coords[name] + return super().__getattribute__(name) + def to_affine(self): if isinstance(self.coordinate_model, AffineCoordinateModel): return copy(self) diff --git a/pyecsca/ec/signature.py b/pyecsca/ec/signature.py new file mode 100644 index 0000000..147bf27 --- /dev/null +++ b/pyecsca/ec/signature.py @@ -0,0 +1,171 @@ +import hashlib +import secrets +from typing import Optional, Any + +from asn1crypto.core import Sequence, SequenceOf, Integer +from public import public + +from .formula import AdditionFormula +from .mod import Mod +from .mult import ScalarMultiplier +from .point import Point + + +@public +class SignatureResult(object): + r: int + s: int + + def __init__(self, r: int, s: int, data: Optional[bytes] = None, digest: Optional[bytes] = None, + nonce: Optional[int] = None, privkey: Optional[int] = None, + pubkey: Optional[Point] = None): + self.r = r + self.s = s + + @staticmethod + def from_DER(data: bytes): + r, s = Sequence.load(data).native.values() + return SignatureResult(r, s) + + def to_DER(self) -> bytes: + obj = SequenceOf(spec=Integer) + obj.append(self.r) + obj.append(self.s) + return obj.dump() + + def __eq__(self, other): + if not isinstance(other, SignatureResult): + return False + return self.r == other.r and self.s == other.s + + def __ne__(self, other): + return not self == other + + def __str__(self): + return f"(r={self.r}, s={self.s})" + + def __repr__(self): + return f"SignatureResult(r={self.r}, s={self.s})" + + +@public +class Signature(object): + mult: ScalarMultiplier + add: Optional[AdditionFormula] + pubkey: Optional[Point] + privkey: Optional[int] + hash_algo: Optional[Any] + + def __init__(self, mult: ScalarMultiplier, add: Optional[AdditionFormula] = None, + pubkey: Optional[Point] = None, privkey: Optional[int] = None, + hash_algo: Optional[Any] = None): + if pubkey is None and privkey is None: + raise ValueError + if add is None: + if "add" not in mult.formulas: + raise ValueError + else: + add = mult.formulas["add"] + self.mult = mult + self.add = add + self.pubkey = pubkey + self.privkey = privkey + self.hash_algo = hash_algo + + @property + def can_sign(self) -> bool: + return self.privkey is not None + + @property + def can_verify(self) -> bool: + return self.pubkey is not None + + def _get_nonce(self, nonce: Optional[int]) -> Mod: + if nonce is None: + return Mod(secrets.randbelow(self.mult.group.order), self.mult.group.order) + else: + return Mod(nonce, self.mult.group.order) + + def _do_sign(self, nonce: Mod, digest: bytes) -> SignatureResult: + point = self.mult.multiply(int(nonce), self.mult.group.generator) + affine_point = point.to_affine() # TODO: add to context + r = Mod(int(affine_point.x), self.mult.group.order) + s = nonce.inverse() * (Mod(int.from_bytes(digest, byteorder="big"), + self.mult.group.order) + r * self.privkey) + return SignatureResult(int(r), int(s), digest=digest, nonce=int(nonce), + privkey=self.privkey) + + def sign_hash(self, digest: bytes, nonce: Optional[int] = None) -> SignatureResult: + k = self._get_nonce(nonce) + return self._do_sign(k, digest) + + def sign_data(self, data: bytes, nonce: Optional[int] = None) -> SignatureResult: + k = self._get_nonce(nonce) + if self.hash_algo is None: + digest = data + else: + digest = self.hash_algo(data).digest() + return self._do_sign(k, digest) + + def _do_verify(self, signature: SignatureResult, e: int) -> bool: + c = Mod(signature.s, self.mult.group.order).inverse() + u1 = Mod(e, self.mult.group.order) * c + u2 = Mod(signature.r, self.mult.group.order) * c + p1 = self.mult.multiply(int(u1), self.mult.group.generator) + p2 = self.mult.multiply(int(u2), self.pubkey) + p = self.mult.context.execute(self.add, p1, p2, **self.mult.group.curve.parameters)[0] + affine = p.to_affine() # TODO: add to context + v = Mod(int(affine.x), self.mult.group.order) + return signature.r == int(v) + + def verify_hash(self, signature: SignatureResult, digest: bytes) -> bool: + return self._do_verify(signature, int.from_bytes(digest, byteorder="big")) + + def verify_data(self, signature: SignatureResult, data: bytes) -> bool: + if self.hash_algo is None: + digest = data + else: + digest = self.hash_algo(data).digest() + return self._do_verify(signature, int.from_bytes(digest, byteorder="big")) + + +@public +class ECDSA_NONE(Signature): + def __init__(self, mult: ScalarMultiplier, add: Optional[AdditionFormula] = None, + pubkey: Optional[Point] = None, privkey: Optional[int] = None): + super().__init__(mult, add, pubkey, privkey) + + +@public +class ECDSA_SHA1(Signature): + def __init__(self, mult: ScalarMultiplier, add: Optional[AdditionFormula] = None, + pubkey: Optional[Point] = None, privkey: Optional[int] = None): + super().__init__(mult, add, pubkey, privkey, hashlib.sha1) + + +@public +class ECDSA_SHA224(Signature): + def __init__(self, mult: ScalarMultiplier, add: Optional[AdditionFormula] = None, + pubkey: Optional[Point] = None, privkey: Optional[int] = None): + super().__init__(mult, add, pubkey, privkey, hashlib.sha224) + + +@public +class ECDSA_SHA256(Signature): + def __init__(self, mult: ScalarMultiplier, add: Optional[AdditionFormula] = None, + pubkey: Optional[Point] = None, privkey: Optional[int] = None): + super().__init__(mult, add, pubkey, privkey, hashlib.sha256) + + +@public +class ECDSA_SHA384(Signature): + def __init__(self, mult: ScalarMultiplier, add: Optional[AdditionFormula] = None, + pubkey: Optional[Point] = None, privkey: Optional[int] = None): + super().__init__(mult, add, pubkey, privkey, hashlib.sha384) + + +@public +class ECDSA_SHA512(Signature): + def __init__(self, mult: ScalarMultiplier, add: Optional[AdditionFormula] = None, + pubkey: Optional[Point] = None, privkey: Optional[int] = None): + super().__init__(mult, add, pubkey, privkey, hashlib.sha512) @@ -27,7 +27,8 @@ setup( "scipy", "atpublic", "matplotlib", - "fastdtw" + "fastdtw", + "asn1crypto" ], extras_require={ "typecheck": ["mypy"], diff --git a/test/ec/test_key_agreement.py b/test/ec/test_key_agreement.py new file mode 100644 index 0000000..f22ee75 --- /dev/null +++ b/test/ec/test_key_agreement.py @@ -0,0 +1,25 @@ +from unittest import TestCase + +from pyecsca.ec.key_agreement import * +from pyecsca.ec.mult import LTRMultiplier +from .curves import get_secp128r1 + + +class KeyAgreementTests(TestCase): + + def setUp(self): + self.secp128r1 = get_secp128r1() + self.add = self.secp128r1.curve.coordinate_model.formulas["add-2007-bl"] + self.dbl = self.secp128r1.curve.coordinate_model.formulas["dbl-2007-bl"] + self.mult = LTRMultiplier(self.secp128r1, self.add, self.dbl) + self.priv_a = 0xdeadbeef + self.pub_a = self.mult.multiply(self.priv_a, self.secp128r1.generator) + self.priv_b = 0xcafebabe + self.pub_b = self.mult.multiply(self.priv_b, self.secp128r1.generator) + self.algos = [ECDH_NONE, ECDH_SHA1, ECDH_SHA224, ECDH_SHA256, ECDH_SHA384, ECDH_SHA512] + + def test_all(self): + for algo in self.algos: + result_ab = algo(self.mult, self.pub_a, self.priv_b).perform() + result_ba = algo(self.mult, self.pub_b, self.priv_a).perform() + self.assertEqual(result_ab, result_ba) diff --git a/test/ec/test_point.py b/test/ec/test_point.py index a59204b..f6f53c8 100644 --- a/test/ec/test_point.py +++ b/test/ec/test_point.py @@ -25,6 +25,7 @@ class PointTests(TestCase): self.assertSetEqual(set(affine.coords.keys()), set(self.affine.variables)) self.assertEqual(affine.coords["x"], pt.coords["X"]) self.assertEqual(affine.coords["y"], pt.coords["Y"]) + self.assertEqual(affine.to_affine(), affine) affine = InfinityPoint(self.coords).to_affine() self.assertIsInstance(affine, InfinityPoint) @@ -41,6 +42,9 @@ class PointTests(TestCase): self.assertEqual(other.coords["Y"], affine.coords["y"]) self.assertEqual(other.coords["Z"], Mod(1, self.secp128r1.curve.prime)) + with self.assertRaises(NotImplementedError): + InfinityPoint.from_affine(self.coords, affine) + def test_to_from_affine(self): pt = Point(self.coords, X=Mod(0x161ff7528b899b2d0c28607ca52c5b86, self.secp128r1.curve.prime), @@ -60,3 +64,19 @@ class PointTests(TestCase): Z=Mod(1, self.secp128r1.curve.prime)) assert pt.equals(other) self.assertNotEquals(pt, other) + assert not pt.equals(2) + self.assertNotEquals(pt, 2) + + infty_one = InfinityPoint(self.coords) + infty_other = InfinityPoint(self.coords) + assert infty_one.equals(infty_other) + self.assertEquals(infty_one, infty_other) + + def test_repr(self): + self.assertEqual(str(self.base), + "[X=29408993404948928992877151431649155974, Y=275621562871047521857442314737465260675, Z=1]") + self.assertEqual(repr(self.base), + "Point([[X=29408993404948928992877151431649155974, Y=275621562871047521857442314737465260675, Z=1]] in EFDCoordinateModel(\"projective\" on short Weierstrass curves))") + self.assertEqual(str(InfinityPoint(self.coords)), "Infinity") + self.assertEqual(repr(InfinityPoint(self.coords)), + "InfinityPoint(EFDCoordinateModel(\"projective\" on short Weierstrass curves))") diff --git a/test/ec/test_signature.py b/test/ec/test_signature.py new file mode 100644 index 0000000..6dc6d9f --- /dev/null +++ b/test/ec/test_signature.py @@ -0,0 +1,48 @@ +from unittest import TestCase + +from hashlib import sha1 +from pyecsca.ec.signature import * +from pyecsca.ec.mult import LTRMultiplier +from .curves import get_secp128r1 + + +class SignatureTests(TestCase): + + def setUp(self): + self.secp128r1 = get_secp128r1() + self.add = self.secp128r1.curve.coordinate_model.formulas["add-2007-bl"] + self.dbl = self.secp128r1.curve.coordinate_model.formulas["dbl-2007-bl"] + self.mult = LTRMultiplier(self.secp128r1, self.add, self.dbl) + self.msg = 0xcafebabe.to_bytes(4, byteorder="big") + self.priv = 0xdeadbeef + self.pub = self.mult.multiply(self.priv, self.secp128r1.generator) + self.algos = [ECDSA_SHA1, ECDSA_SHA224, ECDSA_SHA256, ECDSA_SHA384, ECDSA_SHA512] + + def test_all(self): + for algo in self.algos: + signer = algo(self.mult, privkey=self.priv) + assert signer.can_sign + sig = signer.sign_data(self.msg) + verifier = algo(self.mult, add=self.add, pubkey=self.pub) + assert verifier.can_verify + assert verifier.verify_data(sig, self.msg) + none = ECDSA_NONE(self.mult, add=self.add, pubkey=self.pub, privkey=self.priv) + digest = sha1(self.msg).digest() + sig = none.sign_hash(digest) + assert none.verify_hash(sig, digest) + sig = none.sign_data(digest) + assert none.verify_data(sig, digest) + + def test_fixed_nonce(self): + for algo in self.algos: + signer = algo(self.mult, privkey=self.priv) + sig_one = signer.sign_data(self.msg, nonce=0xabcdef) + sig_other = signer.sign_data(self.msg, nonce=0xabcdef) + verifier = algo(self.mult, add=self.add, pubkey=self.pub) + assert verifier.verify_data(sig_one, self.msg) + assert verifier.verify_data(sig_other, self.msg) + self.assertEqual(sig_one, sig_other) + + def test_der(self): + sig = SignatureResult(0xaaaaa, 0xbbbbb) + self.assertEqual(sig, SignatureResult.from_DER(sig.to_DER())) diff --git a/unittest.cfg b/unittest.cfg index 391556e..c057996 100644 --- a/unittest.cfg +++ b/unittest.cfg @@ -1,2 +1,3 @@ [unittest] -plugins = nose2.plugins.attrib
\ No newline at end of file +plugins = nose2.plugins.attrib + nose2.plugins.doctests
\ No newline at end of file |
