ECDSA#

How to use the crypto_condor.primitives.ECDSA module to test implementations of ECDSA.

Supported parameters#

To test ECDSA implementations you must choose an elliptic curve and a hash function. We use enums to define the supported parameters: Curve and Hash.

Some functions require an indication of which key encoding to use: refer to KeyEncoding and PubKeyEncoding.

enum crypto_condor.primitives.ECDSA.Curve(value)#

Defines all supported curves.

Member Type:

str

Valid values are as follows:

SECP192R1 = <Curve.SECP192R1: 'secp192r1'>#
SECP224R1 = <Curve.SECP224R1: 'secp224r1'>#
SECP256R1 = <Curve.SECP256R1: 'secp256r1'>#
SECP384R1 = <Curve.SECP384R1: 'secp384r1'>#
SECP521R1 = <Curve.SECP521R1: 'secp521r1'>#
SECP256K1 = <Curve.SECP256K1: 'secp256k1'>#
BRAINPOOLP224R1 = <Curve.BRAINPOOLP224R1: 'brainpoolP224r1'>#
BRAINPOOLP256R1 = <Curve.BRAINPOOLP256R1: 'brainpoolP256r1'>#
SECT283R1 = <Curve.SECT283R1: 'sect283r1'>#
SECT409R1 = <Curve.SECT409R1: 'sect409r1'>#
SECT571R1 = <Curve.SECT571R1: 'sect571r1'>#
enum crypto_condor.primitives.ECDSA.Hash(value)#

Defines all supported hash functions.

Member Type:

str

Valid values are as follows:

SHA_256 = <Hash.SHA_256: 'SHA-256'>#
SHA_384 = <Hash.SHA_384: 'SHA-384'>#
SHA_512 = <Hash.SHA_512: 'SHA-512'>#
SHA3_256 = <Hash.SHA3_256: 'SHA3-256'>#
SHA3_384 = <Hash.SHA3_384: 'SHA3-384'>#
SHA3_512 = <Hash.SHA3_512: 'SHA3-512'>#
enum crypto_condor.primitives.ECDSA.KeyEncoding(value)#

Supported key encodings.

Member Type:

str

Valid values are as follows:

PEM = <KeyEncoding.PEM: 'PEM'>#
DER = <KeyEncoding.DER: 'DER'>#
INT = <KeyEncoding.INT: 'INT'>#
enum crypto_condor.primitives.ECDSA.PubKeyEncoding(value)#

Supported public key encodings.

Member Type:

str

Valid values are as follows:

PEM = <PubKeyEncoding.PEM: 'PEM'>#
DER = <PubKeyEncoding.DER: 'DER'>#
UNCOMPRESSED = <PubKeyEncoding.UNCOMPRESSED: 'UNCOMPRESSED'>#

Test a signature verification function#

The following table describes whether a given curve is recommended by the ANSSI or the NIST, and whether the combination of a curve and a hash function has NIST (N) and/or Wycheproof (W) test vectors available.

Test vectors for signature verification#

Curve

ANSSI

NIST

SHA-256

SHA-384

SHA-512

SHA-3 256

SHA-3 384

SHA-3 512

P-224

No

Yes

N+W

N

N+W

W

x

W

P-256

Yes

Yes

N+W

N

N+W

W

x

W

P-384

Yes

Yes

N

N+W

N+W

x

W

W

P-521

Yes

Yes

N

N

N+W

x

x

W

B-283

Yes

No

N

N

N

x

x

x

B-409

Yes

No

N

N

N

x

x

x

B-571

Yes

No

N

N

N

x

x

x

FRP256v1

Yes

No

x

x

x

x

x

x

crypto_condor.primitives.ECDSA.test_verify(
verify_function,
curve,
hash_function,
pubkey_encoding,
*,
pre_hashed=False,
compliance=True,
resilience=True,
)#

Tests a function that verifies ECDSA signatures.

It runs the function with a set of test vectors selected depending on the curve, hash function, and compliance and resilience options.

The function to test must conform to the Verify protocol.

The documentation has a table describing the available sources of test vectors depending on the curve and hash function.

Parameters:
  • verify_function (Verify) – The function to test, see Verify.

  • curve (Curve) – The elliptic curve to use.

  • hash_function (Hash) – The hash function to use.

  • pubkey_encoding (PubKeyEncoding) – A public key encoding accepted by the function.

Keyword Arguments:
  • pre_hashed – Whether the message should be hashed before passing it to verify_function.

  • compliance – Whether to use compliance test vectors.

  • resilience – Whether to use resilience test vectors.

Returns:

A dictionary of results. Can contain the results of testing NIST (nist) and/or Wycheproof (wycheproof) test vectors, if there are any with the given parameters.

Return type:

ResultsDict

Example

Let’s test crypto-condor’s internal verifier. First import the ECDSA module.

>>> from crypto_condor.primitives import ECDSA

We define the parameters we want to test (curve, hash, encoding).

>>> curve = ECDSA.Curve.SECP256R1
>>> hash_function = ECDSA.Hash.SHA_256
>>> encoding = ECDSA.PubKeyEncoding.DER

Then wrap the internal verifier to match the expected signature, defined by Verify.

>>> def my_verify(public_key: bytes, message: bytes, signature: bytes) -> bool:
...     return ECDSA._verify(public_key, hash_function, message, signature)

And test this function.

>>> group = ECDSA.test_verify(my_verify, curve, hash_function, encoding)
[NIST] Verifying signatures ...
>>> assert group["nist"].check()
>>> assert group["wycheproof"].check()

Test a signing function#

The table below indicates whether a given curve is recommended by the ANSSI or the NIST, and whether the combination of a curve and a hash function has NIST (N) or Wycheproof (W) test vectors available.

Test vectors for signature generation#

Curve

ANSSI

NIST

SHA-256

SHA-384

SHA-512

SHA-3 256

SHA-3 384

SHA-3 512

P-224

No

Yes

N

N

N

x

x

x

P-256

Yes

Yes

N

N

N

x

x

x

P-384

Yes

Yes

N

N

N

x

x

x

P-521

Yes

Yes

N

N

N

x

x

x

B-283

Yes

No

N

N

N

x

x

x

B-409

Yes

No

N

N

N

x

x

x

B-571

Yes

No

N

N

N

x

x

x

FRP256v1

Yes

No

x

x

x

x

x

x

crypto_condor.primitives.ECDSA.test_sign(
sign_function,
curve,
hash_function,
key_encoding,
*,
pre_hashed=False,
compliance=True,
)#

Tests a function that signs with ECDSA.

It runs the function with a set of test vectors selected depending on the curve, hash function, and compliance option.

The function to test must conform to the Sign protocol.

The documentation has a table describing the available sources of test vectors depending on the curve and hash function.

Parameters:
  • sign_function (Sign) – The function to test, see Sign.

  • curve (Curve) – The elliptic curve to use.

  • hash_function (Hash) – The hash function to use.

  • key_encoding (KeyEncoding) – The key encoding accepted by the signing function.

Keyword Arguments:
  • pre_hashed – If True, the messages are hashed before passing them to the signing function.

  • compliance – Whether to use compliance test vectors.

Returns:

The results of testing with NIST test vectors if there are test vectors for the given curve and hash function.

Return type:

Results | None

Example

Let’s test crypto-condor’s internal signing function. First, import the ECDSA module.

>>> from crypto_condor.primitives import ECDSA

We define the parameters we want to test (curve, hash, encoding).

>>> curve = ECDSA.Curve.SECP256R1
>>> hash_function = ECDSA.Hash.SHA_256
>>> encoding = ECDSA.KeyEncoding.DER

Then wrap the function to match the expected signature, defined by Sign.

>>> def my_sign(private_key: bytes, message: bytes) -> bytes:
...     return ECDSA._sign(private_key, hash_function, message)

And test the function.

>>> results = ECDSA.test_sign(my_sign, curve, hash_function, encoding)
[NIST] Signing ...
>>> assert results.check()

Test signing then verifying#

crypto_condor.primitives.ECDSA.test_sign_then_verify(
sign,
verify,
curve,
key_encoding,
pubkey_encoding,
hash_function=None,
)#

Tests both functions.

A single random key is generated and encoded. Random messages are generated, signed with the sign function, and the signatures verifies with the verify function. A test is passed is the signing function correctly generated a signature and the verifying function considers this signature valid.

Parameters:
  • sign (Sign) – The signing function to test.

  • verify (Verify) – The verifying function to test.

  • curve (Curve) – The elliptic curve to use.

  • key_encoding (KeyEncoding) – The private key encoding used by the signing function.

  • pubkey_encoding (PubKeyEncoding) – The public key encoding used by the verifying function.

  • hash_function (Hash | None) – Optional. The given hash function is used to hash the message before passing it to the functions. If None, the message is passed as is.

Returns:

The results. A test is considered a pass if the produced signature is valid for the corresponding message according to the verifying function.

Return type:

Results

Example

Let’s test crypto-condor’s internal functions. First import the ECDSA module.

>>> from crypto_condor.primitives import ECDSA

Define the test parameters.

>>> curve = ECDSA.Curve.SECP256R1
>>> key_encoding = ECDSA.KeyEncoding.DER
>>> pubkey_encoding = ECDSA.PubKeyEncoding.DER

Wrap both functions to match the corresponding protocols (Verify and Sign).

>>> def my_verify(public_key: bytes, message: bytes, signature: bytes) -> bool:
...     return ECDSA._verify(public_key, hash_function, message, signature)
>>> def my_sign(private_key: bytes, message: bytes) -> bytes:
...     return ECDSA._sign(private_key, hash_function, message)

Then test both functions.

>>> results = ECDSA.test_sign_then_verify(my_sign, my_verify, curve, key_encoding, pubkey_encoding)
Signing and verifying ...
>>> assert results.check()

Test a function generating key pairs#

crypto_condor.primitives.ECDSA.test_key_pair_gen(keygen, curve)#

Tests a function that generates ECDSA key pairs.

It uses the given function to generate 5000 keys pairs, in the format defined by KeyPair. The private value is used to derive a private key. If the coordinates of the public value are included it checks that these represent the correct public key. A test passes if the private value could be used to derive the private key and if the public value matches, if applicable.

A second test is performed, which consists in concatenating the private values in a single stream, and testing it with TestU01.

Parameters:
  • keygen (KeyGen) – The function that generates ECDSA key pairs. See KeyGen for the expected signature of this function.

  • curve (Curve) – The elliptic curve to use.

Returns:

A dictionary of results containing the results of generating the key pairs (keygen) and the result of testing the private values with TestU01 (testu01).

Return type:

ResultsDict

Notes

5000 keys gives us at least 1 million bits on secp224r1.

Example

Let’s test PyCryptodome’s key generation. We import the ECDSA module.

>>> from crypto_condor.primitives import ECDSA

We pick the curve secp224r1.

>>> curve = ECDSA.Curve.SECP224R1

Then wrap the implementation to match the signature defined by KeyGen.

>>> from Crypto.PublicKey import ECC
>>> def my_key_gen() -> tuple[int, int|None, int|None]:
...     key = ECC.generate(curve=str(curve))
...     return (int(key.d), key.pointQ.x, key.pointQ.y)

And test it.

>>> results_dict = ECDSA.test_key_pair_gen(my_key_gen, curve)
Generating keys ...
>>> assert results_dict["keygen"].check()
>>> assert results_dict["testu01"].check()  

Verify a file of signatures#

crypto_condor.primitives.ECDSA.verify_file(filename, pubkey_encoding, hash_function)#

Verifies signatures contained in a file.

To test ECDSA signatures, the file must follow the format described below.

Format:
  • One set of arguments per line.

  • Lines are separated by newlines (\n).

  • Lines that start with ‘#’ are counted as comments and ignored.

  • Arguments are written in hexadecimal and separated by slashes.

  • The keys may be different for each line but they must be encoded in the same format.

  • The order of the arguments is:

key/message/signature
Parameters:
  • filename (str) – The name of the path containing the signatures to verify.

  • pubkey_encoding (PubKeyEncoding) – The encoding of the public keys used. Only DER- and PEM-encoded keys are supported.

  • hash_function (Hash) – The hash function used to generate the signatures.

Returns:

The results of verifying each signature with an internal implementation. Errors, including parsing ones, are counted as failures and do not raise exceptions, except for the IOError indicated below.

For parsing errors, the line numbering starts at 1.

Raises:

IOError – If the file could not be read.

Return type:

Results

Example

We start by importing the ECDSA module.

>>> from crypto_condor.primitives import ECDSA

For this example we already have a correctly formatted: let’s print the first line to show the format. The output is a bit long but we can see the three expected arguments: key, message, and signature.

>>> filename = "/tmp/ecdsa-p256-sha256-signatures.txt"
>>> with open(filename, "r") as fd:
...     print(fd.readline())
3059301306072a8648ce3d020106082a8648ce3d030107034200042927b10512bae3eddcfe467828128bad2903269919f7086069c8c4df6c732838c7787964eaac00e5921fb1498a60f4606766b3d9685001558d1a974e7341513e/313233343030/304402202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e1802204cd60b855d442f5b3c7b11eb6c4e0ae7525fe710fab9aa7c77a67f79e6fadd76

We use verify_file() to test this file. In this case the keys are DER-encoded and we used SHA-256 to hash the messages. Since we know that it can raise IOError, we wrap it in a try/except statement to print the error without crashing.

>>> try:
...     result = ECDSA.verify_file(filename, ECDSA.PubKeyEncoding.DER, ECDSA.Hash("SHA-256"))
... except IOError as error:
...     print(error)
Testing ...

Run a wrapper#

Note

Available wrappers are defined by Wrapper.

crypto_condor.primitives.ECDSA.run_wrapper(
language,
curve,
hash_function,
pre_hashed,
test_sign,
key_encoding,
test_verify,
pubkey_encoding,
test_sign_then_verify,
compliance,
resilience,
)#

Runs a wrapper.

Parameters:
  • language (Wrapper) – The language of the wrapper.

  • curve (Curve) – The elliptic curve to use.

  • hash_function (Hash) – The hash function to use.

  • pre_hashed (bool) – Whether the message passed should be hashed beforehand.

  • test_sign (bool) – Whether to test the signing function.

  • key_encoding (KeyEncoding | None) – The private key encoding used by the signing function. Set to None only when not testing the signing function.

  • test_verify (bool) – Whether to test the signature verification function.

  • pubkey_encoding (PubKeyEncoding | None) – The public key encoding used by the verifying function. Set to None only when not testing the verifying function.

  • test_sign_then_verify (bool) – If True, both functions are tested by generating a random key and random messages, signing them, and verifying the signature.

  • compliance (bool) – Whether to use compliance test vectors.

  • resilience (bool) – Whether to use resilience test vectors.

Returns:

A dictionary of results. Depending on the options used and test vectors available, it contains results of testing the signing function (sign), testing the verifying function (nist/verify and wycheproof/verify), and testing both (sign-then-verify).

Return type:

ResultsDict

enum crypto_condor.primitives.ECDSA.Wrapper(value)#

Supported languages for wrappers.

Member Type:

str

Valid values are as follows:

PYTHON = <Wrapper.PYTHON: 'Python'>#

Protocols#

protocol crypto_condor.primitives.ECDSA.Verify#

Represents a function that verifies ECDSA signatures.

Classes that implement this protocol must have the following methods / attributes:

__call__(public_key, message, signature)#

Verifies an ECDSA signature.

Parameters:
  • public_key (bytes) – The public elliptic curve key. Either PEM-encoded, DER-encoded, or as serialized int.

  • message (bytes) – The signed message.

  • signature (bytes) – The resulting signature.

Returns:

True if the signature is valid, False otherwise.

Return type:

bool

protocol crypto_condor.primitives.ECDSA.Sign#

Represents a function that signs a message with ECDSA.

Classes that implement this protocol must have the following methods / attributes:

__call__(private_key, message)#

Signs a message with ECDSA.

Parameters:
  • private_key (bytes) – The private elliptic curve key. Either PEM-encoded, DER-encoded, or as serialized int.

  • message (bytes) – The message to sign.

Returns:

The signed message.

Return type:

bytes

protocol crypto_condor.primitives.ECDSA.KeyGen#

Represents a function that generates ECDSA key pairs.

Classes that implement this protocol must have the following methods / attributes:

__call__()#

Generates an ECDSA key pair.

Returns:

A tuple (d, Qx, Qy) containing the private value d and the coordinates Qx and Qy of the public value, or a tuple (d, None, None) containing only the private value d.

Return type:

tuple[int, int | None, int | None]