Skip to content

Commit 055a671

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 eb24b23 commit 055a671

File tree

3 files changed

+70
-39
lines changed

3 files changed

+70
-39
lines changed

src/ecdsa/keys.py

Lines changed: 61 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=False,
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,9 @@ 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, the
552+
extra bits (at the end of the digest) will be truncated.
504553
505554
:return: Initialised VerifyingKey object
506555
:rtype: VerifyingKey
@@ -510,7 +559,9 @@ def from_public_key_recovery_with_digest(
510559
sig = ecdsa.Signature(r, s)
511560

512561
digest = normalise_bytes(digest)
513-
digest_as_number = string_to_number(digest)
562+
digest_as_number = _truncate_and_convert_digest(
563+
digest, curve, allow_truncate
564+
)
514565
pks = sig.recover_public_keys(digest_as_number, generator)
515566

516567
# Transforms the ecdsa.Public_key object into a VerifyingKey
@@ -717,27 +768,9 @@ def verify_digest(
717768
# signature doesn't have to be a bytes-like-object so don't normalise
718769
# it, the decoders will do that
719770
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)
771+
number = _truncate_and_convert_digest(
772+
digest, self.curve, allow_truncate,
773+
)
741774

742775
try:
743776
r, s = sigdecode(signature, self.pubkey.order)
@@ -1409,14 +1442,9 @@ def sign_digest(
14091442
:rtype: bytes or sigencode function dependant type
14101443
"""
14111444
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)
1445+
number = _truncate_and_convert_digest(
1446+
digest, self.curve, allow_truncate,
1447+
)
14201448
r, s = self.sign_number(number, entropy, k)
14211449
return sigencode(r, s, self.privkey.order)
14221450

src/ecdsa/test_jacobi.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import pickle
22
import sys
3+
import pytest
34

45
try:
56
import unittest2 as unittest
@@ -218,7 +219,8 @@ def test_multiplications(self, mul):
218219
@example(0)
219220
@example(int(generator_brainpoolp160r1.order()))
220221
def test_precompute(self, mul):
221-
precomp = PointJacobi.from_affine(generator_brainpoolp160r1, True)
222+
precomp = generator_brainpoolp160r1
223+
self.assertTrue(precomp._PointJacobi__precompute)
222224
pj = PointJacobi.from_affine(generator_brainpoolp160r1)
223225

224226
a = precomp * mul
@@ -440,15 +442,15 @@ def test_mul_add_same(self):
440442
self.assertEqual(j_g * 2, j_g.mul_add(1, j_g, 1))
441443

442444
def test_mul_add_precompute(self):
443-
j_g = PointJacobi.from_affine(generator_256, True)
445+
j_g = PointJacobi.from_affine(generator_brainpoolp160r1, True)
444446
b = PointJacobi.from_affine(j_g * 255, True)
445447

446448
self.assertEqual(j_g * 256, j_g + b)
447449
self.assertEqual(j_g * (5 + 255 * 7), j_g * 5 + b * 7)
448450
self.assertEqual(j_g * (5 + 255 * 7), j_g.mul_add(5, b, 7))
449451

450452
def test_mul_add_precompute_large(self):
451-
j_g = PointJacobi.from_affine(generator_256, True)
453+
j_g = PointJacobi.from_affine(generator_brainpoolp160r1, True)
452454
b = PointJacobi.from_affine(j_g * 255, True)
453455

454456
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
@@ -598,7 +598,7 @@ def test_hashfunc(self):
598598

599599
def test_public_key_recovery(self):
600600
# Create keys
601-
curve = NIST256p
601+
curve = BRAINPOOLP160r1
602602

603603
sk = SigningKey.generate(curve=curve)
604604
vk = sk.get_verifying_key()
@@ -631,7 +631,7 @@ def test_public_key_recovery(self):
631631

632632
def test_public_key_recovery_with_custom_hash(self):
633633
# Create keys
634-
curve = NIST256p
634+
curve = BRAINPOOLP160r1
635635

636636
sk = SigningKey.generate(curve=curve, hashfunc=sha256)
637637
vk = sk.get_verifying_key()
@@ -642,7 +642,7 @@ def test_public_key_recovery_with_custom_hash(self):
642642

643643
# Recover verifying keys
644644
recovered_vks = VerifyingKey.from_public_key_recovery(
645-
signature, data, curve, hashfunc=sha256
645+
signature, data, curve, hashfunc=sha256, allow_truncate=True
646646
)
647647

648648
# Test if each pk is valid
@@ -816,6 +816,7 @@ def test_VerifyingKey_encode_decode(curve, encoding):
816816
else:
817817
params = curves
818818

819+
819820
@pytest.mark.parametrize("curve", params)
820821
def test_lengths(curve):
821822
priv = SigningKey.generate(curve=curve)

0 commit comments

Comments
 (0)