Skip to content

Commit e0d1c05

Browse files
committed
fix from_public_key_recovery() and sign() for large hashes
the public key recovery needs to truncate large hashes just like the signature verification and creation, so reuse the code that does that
1 parent bbe3679 commit e0d1c05

File tree

3 files changed

+70
-39
lines changed

3 files changed

+70
-39
lines changed

src/ecdsa/keys.py

Lines changed: 62 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,38 @@ class MalformedPointError(AssertionError):
124124
pass
125125

126126

127+
def _truncate_and_convert_digest(digest, curve, allow_truncate):
128+
"""Truncates and converts digest to an integer."""
129+
if not allow_truncate:
130+
if len(digest) > curve.baselen:
131+
raise BadDigestError(
132+
"this curve ({0}) is too short "
133+
"for the length of your digest ({1})".format(
134+
curve.name, 8 * len(digest)
135+
)
136+
)
137+
else:
138+
digest = digest[: curve.baselen]
139+
number = string_to_number(digest)
140+
if allow_truncate:
141+
max_length = bit_length(curve.order)
142+
# we don't use bit_length(number) as that truncates leading zeros
143+
length = len(digest) * 8
144+
145+
# See NIST FIPS 186-4:
146+
#
147+
# When the length of the output of the hash function is greater
148+
# than N (i.e., the bit length of q), then the leftmost N bits of
149+
# the hash function output block shall be used in any calculation
150+
# using the hash function output during the generation or
151+
# verification of a digital signature.
152+
#
153+
# as such, we need to shift-out the low-order bits:
154+
number >>= max(0, length - max_length)
155+
156+
return number
157+
158+
127159
class VerifyingKey(object):
128160
"""
129161
Class for handling keys that can verify signatures (public keys).
@@ -435,7 +467,13 @@ def from_der(cls, string, hashfunc=sha1):
435467

436468
@classmethod
437469
def from_public_key_recovery(
438-
cls, signature, data, curve, hashfunc=sha1, sigdecode=sigdecode_string
470+
cls,
471+
signature,
472+
data,
473+
curve,
474+
hashfunc=sha1,
475+
sigdecode=sigdecode_string,
476+
allow_truncate=True,
439477
):
440478
"""
441479
Return keys that can be used as verifiers of the provided signature.
@@ -458,6 +496,9 @@ def from_public_key_recovery(
458496
a tuple with two integers, "r" as the first one and "s" as the
459497
second one. See :func:`ecdsa.util.sigdecode_string` and
460498
:func:`ecdsa.util.sigdecode_der` for examples.
499+
:param bool allow_truncate: if True, the provided hashfunc can generate
500+
values larger than the bit size of the order of the curve, the
501+
extra bits (at the end of the digest) will be truncated.
461502
:type sigdecode: callable
462503
463504
:return: Initialised VerifyingKey objects
@@ -466,7 +507,12 @@ def from_public_key_recovery(
466507
data = normalise_bytes(data)
467508
digest = hashfunc(data).digest()
468509
return cls.from_public_key_recovery_with_digest(
469-
signature, digest, curve, hashfunc=hashfunc, sigdecode=sigdecode
510+
signature,
511+
digest,
512+
curve,
513+
hashfunc=hashfunc,
514+
sigdecode=sigdecode,
515+
allow_truncate=allow_truncate,
470516
)
471517

472518
@classmethod
@@ -477,6 +523,7 @@ def from_public_key_recovery_with_digest(
477523
curve,
478524
hashfunc=sha1,
479525
sigdecode=sigdecode_string,
526+
allow_truncate=False,
480527
):
481528
"""
482529
Return keys that can be used as verifiers of the provided signature.
@@ -500,7 +547,10 @@ def from_public_key_recovery_with_digest(
500547
second one. See :func:`ecdsa.util.sigdecode_string` and
501548
:func:`ecdsa.util.sigdecode_der` for examples.
502549
:type sigdecode: callable
503-
550+
:param bool allow_truncate: if True, the provided hashfunc can generate
551+
values larger than the bit size of the order of the curve (and
552+
the length of provided `digest`), the extra bits (at the end of the
553+
digest) will be truncated.
504554
505555
:return: Initialised VerifyingKey object
506556
:rtype: VerifyingKey
@@ -510,7 +560,9 @@ def from_public_key_recovery_with_digest(
510560
sig = ecdsa.Signature(r, s)
511561

512562
digest = normalise_bytes(digest)
513-
digest_as_number = string_to_number(digest)
563+
digest_as_number = _truncate_and_convert_digest(
564+
digest, curve, allow_truncate
565+
)
514566
pks = sig.recover_public_keys(digest_as_number, generator)
515567

516568
# Transforms the ecdsa.Public_key object into a VerifyingKey
@@ -717,27 +769,9 @@ def verify_digest(
717769
# signature doesn't have to be a bytes-like-object so don't normalise
718770
# it, the decoders will do that
719771
digest = normalise_bytes(digest)
720-
if not allow_truncate and len(digest) > self.curve.baselen:
721-
raise BadDigestError(
722-
"this curve (%s) is too short "
723-
"for your digest (%d)" % (self.curve.name, 8 * len(digest))
724-
)
725-
number = string_to_number(digest)
726-
if allow_truncate:
727-
max_length = bit_length(self.curve.order)
728-
# we don't use bit_length(number) as that truncates leading zeros
729-
length = len(digest) * 8
730-
731-
# See NIST FIPS 186-4:
732-
#
733-
# When the length of the output of the hash function is greater
734-
# than N (i.e., the bit length of q), then the leftmost N bits of
735-
# the hash function output block shall be used in any calculation
736-
# using the hash function output during the generation or
737-
# verification of a digital signature.
738-
#
739-
# as such, we need to shift-out the low-order bits:
740-
number >>= max(0, length - max_length)
772+
number = _truncate_and_convert_digest(
773+
digest, self.curve, allow_truncate,
774+
)
741775

742776
try:
743777
r, s = sigdecode(signature, self.pubkey.order)
@@ -1409,14 +1443,9 @@ def sign_digest(
14091443
:rtype: bytes or sigencode function dependant type
14101444
"""
14111445
digest = normalise_bytes(digest)
1412-
if allow_truncate:
1413-
digest = digest[: self.curve.baselen]
1414-
if len(digest) > self.curve.baselen:
1415-
raise BadDigestError(
1416-
"this curve (%s) is too short "
1417-
"for your digest (%d)" % (self.curve.name, 8 * len(digest))
1418-
)
1419-
number = string_to_number(digest)
1446+
number = _truncate_and_convert_digest(
1447+
digest, self.curve, allow_truncate,
1448+
)
14201449
r, s = self.sign_number(number, entropy, k)
14211450
return sigencode(r, s, self.privkey.order)
14221451

src/ecdsa/test_jacobi.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,8 @@ def test_multiplications(self, mul):
210210
@example(0)
211211
@example(int(generator_brainpoolp160r1.order()))
212212
def test_precompute(self, mul):
213-
precomp = PointJacobi.from_affine(generator_brainpoolp160r1, True)
213+
precomp = generator_brainpoolp160r1
214+
self.assertTrue(precomp._PointJacobi__precompute)
214215
pj = PointJacobi.from_affine(generator_brainpoolp160r1)
215216

216217
a = precomp * mul
@@ -383,15 +384,15 @@ def test_mul_add_same(self):
383384
self.assertEqual(j_g * 2, j_g.mul_add(1, j_g, 1))
384385

385386
def test_mul_add_precompute(self):
386-
j_g = PointJacobi.from_affine(generator_256, True)
387+
j_g = PointJacobi.from_affine(generator_brainpoolp160r1, True)
387388
b = PointJacobi.from_affine(j_g * 255, True)
388389

389390
self.assertEqual(j_g * 256, j_g + b)
390391
self.assertEqual(j_g * (5 + 255 * 7), j_g * 5 + b * 7)
391392
self.assertEqual(j_g * (5 + 255 * 7), j_g.mul_add(5, b, 7))
392393

393394
def test_mul_add_precompute_large(self):
394-
j_g = PointJacobi.from_affine(generator_256, True)
395+
j_g = PointJacobi.from_affine(generator_brainpoolp160r1, True)
395396
b = PointJacobi.from_affine(j_g * 255, True)
396397

397398
self.assertEqual(j_g * 256, j_g + b)

src/ecdsa/test_pyecdsa.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -616,7 +616,7 @@ def test_hashfunc(self):
616616

617617
def test_public_key_recovery(self):
618618
# Create keys
619-
curve = NIST256p
619+
curve = BRAINPOOLP160r1
620620

621621
sk = SigningKey.generate(curve=curve)
622622
vk = sk.get_verifying_key()
@@ -649,7 +649,7 @@ def test_public_key_recovery(self):
649649

650650
def test_public_key_recovery_with_custom_hash(self):
651651
# Create keys
652-
curve = NIST256p
652+
curve = BRAINPOOLP160r1
653653

654654
sk = SigningKey.generate(curve=curve, hashfunc=sha256)
655655
vk = sk.get_verifying_key()
@@ -660,7 +660,7 @@ def test_public_key_recovery_with_custom_hash(self):
660660

661661
# Recover verifying keys
662662
recovered_vks = VerifyingKey.from_public_key_recovery(
663-
signature, data, curve, hashfunc=sha256
663+
signature, data, curve, hashfunc=sha256, allow_truncate=True
664664
)
665665

666666
# Test if each pk is valid
@@ -829,6 +829,7 @@ def test_VerifyingKey_encode_decode(curve, encoding):
829829
assert vk.pubkey.point == from_enc.pubkey.point
830830

831831

832+
832833
class OpenSSL(unittest.TestCase):
833834
# test interoperability with OpenSSL tools. Note that openssl's ECDSA
834835
# sign/verify arguments changed between 0.9.8 and 1.0.0: the early

0 commit comments

Comments
 (0)