Skip to content

Commit cf57b04

Browse files
committed
add secp160r1; fix handling curves with order > p
the secret multiplier is limited by the order of the base point, that also informs the size of elements for the signature (as they are calculated modulo order), but the public point is a point, so its elements are modulo prime from the curve. The same thing applies to the shared secret: it's just one coordinate of the point, so it's modulo p of the curve, not modulo order of generator. for all curves up till now the size of order and size of the prime was the same so it worked fine, but secp160r1 is different, so it showed the bugs so fix this bug and add secp160r1 as the test coverage for it
1 parent 3219259 commit cf57b04

File tree

7 files changed

+77
-29
lines changed

7 files changed

+77
-29
lines changed

src/ecdsa/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
SECP112r1,
2323
SECP112r2,
2424
SECP128r1,
25+
SECP160r1,
2526
)
2627
from .ecdh import (
2728
ECDH,
@@ -78,5 +79,6 @@
7879
SECP112r1,
7980
SECP112r2,
8081
SECP128r1,
82+
SECP160r1,
8183
]
8284
del _hush_pyflakes

src/ecdsa/curves.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"SECP112r1",
1414
"SECP112r2",
1515
"SECP128r1",
16+
"SECP160r1",
1617
"NIST192p",
1718
"NIST224p",
1819
"NIST256p",
@@ -43,7 +44,7 @@ def __init__(self, name, curve, generator, oid, openssl_name=None):
4344
self.generator = generator
4445
self.order = generator.order()
4546
self.baselen = orderlen(self.order)
46-
self.verifying_key_length = 2 * self.baselen
47+
self.verifying_key_length = 2 * orderlen(curve.p())
4748
self.signature_length = 2 * self.baselen
4849
self.oid = oid
4950
self.encoded_oid = der.encode_oid(*oid)
@@ -80,6 +81,15 @@ def __repr__(self):
8081
)
8182

8283

84+
SECP160r1 = Curve(
85+
"SECP160r1",
86+
ecdsa.curve_160r1,
87+
ecdsa.generator_160r1,
88+
(1, 3, 132, 0, 8),
89+
"secp160r1",
90+
)
91+
92+
8393
# the NIST curves
8494
NIST192p = Curve(
8595
"NIST192p",
@@ -216,6 +226,7 @@ def __repr__(self):
216226
SECP112r1,
217227
SECP112r2,
218228
SECP128r1,
229+
SECP160r1,
219230
]
220231

221232

src/ecdsa/ecdh.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ def generate_sharedsecret_bytes(self):
304304
:rtype: byte string
305305
"""
306306
return number_to_string(
307-
self.generate_sharedsecret(), self.private_key.curve.order
307+
self.generate_sharedsecret(), self.private_key.curve.curve.p()
308308
)
309309

310310
def generate_sharedsecret(self):

src/ecdsa/ecdsa.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,27 @@ def point_is_valid(generator, x, y):
339339
)
340340

341341

342+
# secp160r1
343+
_p = int(remove_whitespace("FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 7FFFFFFF"), 16)
344+
# S = 1053CDE4 2C14D696 E6768756 1517533B F3F83345
345+
# _a = -3
346+
_b = int(remove_whitespace("1C97BEFC 54BD7A8B 65ACF89F 81D4D4AD C565FA45"), 16)
347+
_Gx = int(
348+
remove_whitespace("4A96B568 8EF57328 46646989 68C38BB9 13CBFC82"), 16,
349+
)
350+
_Gy = int(
351+
remove_whitespace("23A62855 3168947D 59DCC912 04235137 7AC5FB32"), 16,
352+
)
353+
_r = int(
354+
remove_whitespace("01 00000000 00000000 0001F4C8 F927AED3 CA752257"), 16,
355+
)
356+
_h = 1
357+
curve_160r1 = ellipticcurve.CurveFp(_p, -3, _b, _h)
358+
generator_160r1 = ellipticcurve.PointJacobi(
359+
curve_160r1, _Gx, _Gy, 1, _r, generator=True
360+
)
361+
362+
342363
# NIST Curve P-192:
343364
_p = 6277101735386680763835789423207666416083908700390324961279
344365
_r = 6277101735386680763835789423176059013767194773182842284081

src/ecdsa/keys.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -272,12 +272,12 @@ def _from_raw_encoding(string, curve):
272272
order = curve.order
273273
# real assert, from_string() should not call us with different length
274274
assert len(string) == curve.verifying_key_length
275-
xs = string[: curve.baselen]
276-
ys = string[curve.baselen :]
277-
if len(xs) != curve.baselen:
278-
raise MalformedPointError("Unexpected length of encoded x")
279-
if len(ys) != curve.baselen:
280-
raise MalformedPointError("Unexpected length of encoded y")
275+
xs = string[: curve.verifying_key_length // 2]
276+
ys = string[curve.verifying_key_length // 2 :]
277+
# real assert, verifying_key_length is calculated by multiplying an
278+
# integer by two so it will always be even
279+
assert len(xs) == curve.verifying_key_length // 2
280+
assert len(ys) == curve.verifying_key_length // 2
281281
x = string_to_number(xs)
282282
y = string_to_number(ys)
283283

@@ -371,7 +371,7 @@ def from_string(
371371
raise MalformedPointError(
372372
"Invalid X9.62 encoding of the public point"
373373
)
374-
elif sig_len == curve.baselen + 1:
374+
elif sig_len == curve.verifying_key_length // 2 + 1:
375375
point = cls._from_compressed(string, curve)
376376
else:
377377
raise MalformedPointError(
@@ -573,14 +573,14 @@ def from_public_key_recovery_with_digest(
573573

574574
def _raw_encode(self):
575575
"""Convert the public key to the :term:`raw encoding`."""
576-
order = self.pubkey.order
576+
order = self.curve.curve.p()
577577
x_str = number_to_string(self.pubkey.point.x(), order)
578578
y_str = number_to_string(self.pubkey.point.y(), order)
579579
return x_str + y_str
580580

581581
def _compressed_encode(self):
582582
"""Encode the public point into the compressed form."""
583-
order = self.pubkey.order
583+
order = self.curve.curve.p()
584584
x_str = number_to_string(self.pubkey.point.x(), order)
585585
if self.pubkey.point.y() & 1:
586586
return b("\x03") + x_str

src/ecdsa/test_ecdh.py

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ def test_ecdh_with_openssl(vcurve):
375375
if hlp.find("-derive") == 0: # pragma: no cover
376376
pytest.skip("system openssl does not support `pkeyutl -derive`")
377377
except RunOpenSslError: # pragma: no cover
378-
pytest.skip("system openssl does not support `pkeyutl -derive`")
378+
pytest.skip("system openssl could not be executed")
379379

380380
if os.path.isdir("t"): # pragma: no branch
381381
shutil.rmtree("t")
@@ -412,25 +412,20 @@ def test_ecdh_with_openssl(vcurve):
412412

413413
assert secret1 == secret2
414414

415-
try:
416-
run_openssl(
417-
"pkeyutl -derive -inkey t/privkey1.pem -peerkey t/pubkey2.pem -out t/secret1"
418-
)
419-
run_openssl(
420-
"pkeyutl -derive -inkey t/privkey2.pem -peerkey t/pubkey1.pem -out t/secret2"
421-
)
422-
except RunOpenSslError: # pragma: no cover
423-
pytest.skip("system openssl does not support `pkeyutl -derive`")
424-
return
415+
run_openssl(
416+
"pkeyutl -derive -inkey t/privkey1.pem -peerkey t/pubkey2.pem -out t/secret1"
417+
)
418+
run_openssl(
419+
"pkeyutl -derive -inkey t/privkey2.pem -peerkey t/pubkey1.pem -out t/secret2"
420+
)
425421

426422
with open("t/secret1", "rb") as e:
427423
ssl_secret1 = e.read()
428424
with open("t/secret1", "rb") as e:
429425
ssl_secret2 = e.read()
430426

431-
if len(ssl_secret1) != vk1.curve.baselen: # pragma: no cover
432-
pytest.skip("system openssl does not support `pkeyutl -derive`")
433-
return
427+
assert len(ssl_secret1) == vk1.curve.verifying_key_length // 2
428+
assert len(secret1) == vk1.curve.verifying_key_length // 2
434429

435430
assert ssl_secret1 == ssl_secret2
436431
assert secret1 == ssl_secret1

src/ecdsa/test_pyecdsa.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
SECP112r1,
3030
SECP112r2,
3131
SECP128r1,
32+
SECP160r1,
3233
NIST192p,
3334
NIST224p,
3435
NIST256p,
@@ -313,8 +314,15 @@ class FakeGenerator:
313314
def order(self):
314315
return 123456789
315316

317+
class FakeCurveFp:
318+
def p(self):
319+
return int(
320+
"6525534529039240705020950546962731340"
321+
"4541085228058844382513856749047873406763"
322+
)
323+
316324
badcurve = Curve(
317-
"unknown", None, FakeGenerator(), (1, 2, 3, 4, 5, 6), None
325+
"unknown", FakeCurveFp(), FakeGenerator(), (1, 2, 3, 4, 5, 6), None
318326
)
319327
badpub.curve = badcurve
320328
badder = badpub.to_der()
@@ -832,7 +840,6 @@ def test_VerifyingKey_encode_decode(curve, encoding):
832840
assert vk.pubkey.point == from_enc.pubkey.point
833841

834842

835-
836843
class OpenSSL(unittest.TestCase):
837844
# test interoperability with OpenSSL tools. Note that openssl's ECDSA
838845
# sign/verify arguments changed between 0.9.8 and 1.0.0: the early
@@ -890,7 +897,13 @@ def test_from_openssl_secp112r2(self):
890897
def test_from_openssl_secp128r1(self):
891898
return self.do_test_from_openssl(SECP128r1)
892899

893-
@pytest.mark.slow
900+
@pytest.mark.skipif(
901+
"secp160r1" not in OPENSSL_SUPPORTED_CURVES,
902+
reason="system openssl does not support secp160r1",
903+
)
904+
def test_from_openssl_secp160r1(self):
905+
return self.do_test_from_openssl(SECP160r1)
906+
894907
@pytest.mark.skipif(
895908
"prime192v1" not in OPENSSL_SUPPORTED_CURVES,
896909
reason="system openssl does not support prime192v1",
@@ -1076,7 +1089,13 @@ def test_to_openssl_secp112r2(self):
10761089
def test_to_openssl_secp128r1(self):
10771090
self.do_test_to_openssl(SECP128r1)
10781091

1079-
@pytest.mark.slow
1092+
@pytest.mark.skipif(
1093+
"secp160r1" not in OPENSSL_SUPPORTED_CURVES,
1094+
reason="system openssl does not support secp160r1",
1095+
)
1096+
def test_to_openssl_secp160r1(self):
1097+
self.do_test_to_openssl(SECP160r1)
1098+
10801099
@pytest.mark.skipif(
10811100
"prime192v1" not in OPENSSL_SUPPORTED_CURVES,
10821101
reason="system openssl does not support prime192v1",

0 commit comments

Comments
 (0)