aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJ08nY2019-04-22 18:04:44 +0200
committerJ08nY2019-04-22 18:04:44 +0200
commit24b9b3958a54bdf7f18df62ae14199749934e3c2 (patch)
treea758d0289337f6aee7540dca4a94ea31ced69217
parent037194fd8cfe50aa2367c2f3c7fae5b41e7b46f9 (diff)
downloadpyecsca-24b9b3958a54bdf7f18df62ae14199749934e3c2.tar.gz
pyecsca-24b9b3958a54bdf7f18df62ae14199749934e3c2.tar.zst
pyecsca-24b9b3958a54bdf7f18df62ae14199749934e3c2.zip
-rw-r--r--README.md1
-rw-r--r--docs/index.rst1
-rw-r--r--pyecsca/ec/group.py10
-rw-r--r--pyecsca/ec/key_agreement.py69
-rw-r--r--pyecsca/ec/point.py9
-rw-r--r--pyecsca/ec/signature.py171
-rw-r--r--setup.py3
-rw-r--r--test/ec/test_key_agreement.py25
-rw-r--r--test/ec/test_point.py20
-rw-r--r--test/ec/test_signature.py48
-rw-r--r--unittest.cfg3
11 files changed, 351 insertions, 9 deletions
diff --git a/README.md b/README.md
index 7a4decf..147fa00 100644
--- a/README.md
+++ b/README.md
@@ -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)
diff --git a/setup.py b/setup.py
index 4d61bdd..5c4f5c8 100644
--- a/setup.py
+++ b/setup.py
@@ -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