ECDSA¶
How to use the crypto_condor.primitives.ECDSA
module to test implementations of
ECDSA.
Test signing¶
- crypto_condor.primitives.ECDSA.test_sign(
- sign,
- curve,
- hash_function,
- key_encoding,
- *,
- pre_hashed=False,
- compliance=True,
- resilience=False,
Tests a function that signs with ECDSA.
The
sign
function is called to sign messages. Since ECDSA is non-deterministic (except when using RFC 6979), crypto-condor cannot compare the generated signatures with a reference value. Instead, it verifies the signatures with a reference implementation. The test passes if the signature is considered valid.Note that this method allows to test the RFC 6979 deterministic version of ECDSA, as the signatures remain compatible. However, it only tests that the signatures produced are valid ECDSA signatures: they may be invalid for RFC 6979. Test vectors for deterministic ECDSA will be added.
Available test vectors depend on the given curve and hash function, see here.
- Parameters:
sign (Sign) – The function to test.
curve (Curve) – The elliptic curve to use.
hash_function (Hash) – The hash function used to generate the signatures.
key_encoding (KeyEncoding) – A private key encoding accepted by the function.
- Keyword Arguments:
pre_hashed – If True, the messages are hashed before passing them to
sign
.compliance – Whether to use compliance test vectors.
resilience – Whether to use resilience test vectors.
- Returns:
A dictionary of
Results
, one for each test vectors file used. May be empty if there are no test vectors for the given curve and hash function.- Return type:
Example
Let’s test crypto-condor’s internal signer. 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.P256 >>> hash_function = ECDSA.Hash.SHA256 >>> 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) [P-256][SHA-256][NIST CAVP] Test signing ... >>> assert results.check()
Test verifying¶
- crypto_condor.primitives.ECDSA.test_verify(
- verify,
- curve,
- hash_function,
- pubkey_encoding,
- *,
- pre_hashed=False,
- compliance=True,
- resilience=False,
Tests a function that verifies ECDSA signatures.
The
verify
function is called to verify messages. The test passes if valid signatures are correctly verified, and invalid signatures are rejected.The available tests use valid keys and messages, invalid tests only modify the signature. Following
Verify
, implementations are expected to only return True or False, exceptions are treated as errors regardless of test vector and counted as failures.This function can be used to test implementations of the RFC 6979 deterministic version of ECDSA.
Available test vectors depend on the given curve and hash function, see here.
- Parameters:
verify (Verify) – The function to test.
curve (Curve) – The elliptic curve to use.
hash_function (Hash) – The hash function used to generate the signatures.
pubkey_encoding (PubKeyEncoding) – A public key encoding accepted by the function.
- Keyword Arguments:
pre_hashed – If True, the messages are hashed before passing them to
verify
.compliance – Whether to use compliance test vectors.
resilience – Whether to use resilience test vectors.
- Returns:
A dictionary of
Results
, one for each test vectors file used. May be empty if there are no test vectors for the given curve and hash function.- Return type:
Example
Let’s test ECDSA verification with
cryptography
.>>> from cryptography.exceptions import InvalidSignature >>> from cryptography.hazmat.primitives import serialization >>> from cryptography.hazmat.primitives.asymmetric import ec
We also import crypto-condor’s ECSDA module to run the tests.
>>> from crypto_condor.primitives import ECDSA
We define the parameters we want to test (curve, hash, encoding).
>>> curve = ECDSA.Curve.P256 >>> hash_function = ECDSA.Hash.SHA256 >>> encoding = ECDSA.PubKeyEncoding.DER
Then wrap the call to the verifier to match the
Verify
protocol.>>> def my_verify(pk: bytes, msg: bytes, sig: bytes) -> bool: ... key = serialization.load_der_public_key(pk) ... try: ... key.verify(sig, msg, ec.ECDSA(hash_function.get_hash_instance())) ... except InvalidSignature: ... return False ... else: ... return True
And test this function.
>>> res = ECDSA.test_verify(my_verify, curve, hash_function, encoding) [P-256][SHA-256][NIST CAVP] Test verifying ...
>>> assert res.check()
Test signing and verifying¶
- crypto_condor.primitives.ECDSA.test_sign_verify_invariant(
- sign,
- verify,
- curve,
- hash_function,
- key_encoding,
- pubkey_encoding,
- *,
- prehash=False,
Tests signing and verifying.
A signature generated by an implementation should be valid for its own verifier. To test this, available signature generation test vectors are used. The
sign
function is called to compute the signature, then theverify
function is called to verify this signature. The test passes if the signature is considered valid.Note that this only verifies that the signer and verifier agree: for example, a verifier that returns True no matter the input will pass this test. Consider also testing both functions with
test_sign()
andtest_verify()
.Available test vectors depend on the given curve and hash function, see here.
- Parameters:
sign (Sign) – The signing function to test.
verify (Verify) – The verifying function to test.
curve (Curve) – The elliptic curve to use.
hash_function (Hash) – This argument is used to select the test vectors to use. If the
prehash
argument is True, it also selects the hash function used to hash the messages before passing them to both functions.key_encoding (KeyEncoding) – The private key encoding used by the signing function.
pubkey_encoding (PubKeyEncoding) – The public key encoding used by the verifying function.
- Keyword Arguments:
prehash – If True, messages are hashed before passing them to
sign
andverify
, otherwise messages are given as-is.- Returns:
A dictionary of results, containing one instance of
Results
per test vectors file used.- Return type:
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.P256 >>> hash_function = ECDSA.Hash.SHA256 >>> key_encoding = ECDSA.KeyEncoding.DER >>> pubkey_encoding = ECDSA.PubKeyEncoding.DER
Wrap both functions to match the corresponding protocols (
Verify
andSign
).>>> def my_verify(pk: bytes, msg: bytes, sig: bytes) -> bool: ... return ECDSA._verify(pk, hash_function, msg, sig) >>> def my_sign(sk: bytes, msg: bytes) -> bytes: ... return ECDSA._sign(sk, hash_function, msg)
Then test both functions.
>>> results = ECDSA.test_sign_verify_invariant( ... my_sign, my_verify, curve, hash_function, key_encoding, pubkey_encoding ... ) [P-256][SHA-256][NIST CAVP] Test sign-verify ... >>> assert results.check()
Test key pair generation¶
- crypto_condor.primitives.ECDSA.test_key_pair_gen(keygen, curve, num_keys=5000)¶
Tests a function that generates ECDSA key pairs.
Calls keygen to generate num_keys keys in the format defined by
KeyPair
. There are three checks in this test. First, the private key is derived from the private value to verify it works for the given curve. Then, if the public key is included, it must match the private key. Finally, all private keys are concatenated and tested withcrypto_condor.primitives.TestU01
to check for potential biases.- Parameters:
- Keyword Arguments:
num_keys – The number of keys to generate. The more data is available for TestU01, the more chances of catching any bias present. A minimum of 5000 keys, which results in roughly 1 million bits on
P-224
, is enforced.- Returns:
A dictionary containing two
Results
, one for the results of generating key pairs and one for the results of the TestU01 battery.- Raises:
ValueError – If
num_keys
is less than 5000.- Return type:
Example
Let’s test PyCryptodome’s key generation. We import the ECDSA module.
>>> from crypto_condor.primitives import ECDSA
We pick the curve P-224.
>>> curve = ECDSA.Curve.P224
Then wrap the implementation to match the signature defined by
KeyGen
.>>> from Crypto.PublicKey import ECC >>> def my_key_gen() -> tuple[int, int, int]: ... key = ECC.generate(curve=str(curve)) ... return (int(key.d), int(key.pointQ.x), int(key.pointQ.y))
And test it.
>>> results_dict = ECDSA.test_key_pair_gen(my_key_gen, curve) Generating keys ... >>> assert results_dict.check()
Test signatures from a file¶
- crypto_condor.primitives.ECDSA.test_output_sign(filename, curve, hash_function, pubkey_encoding)¶
Verifies signatures contained in a file.
Signatures are read from the file and then verified with a reference implementation. The file must have the following format:
- 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:
pub_key/message/signature
- Parameters:
filename (str) – The name of the path containing the signatures to verify.
curve (Curve | None) – The elliptic curve of the keys. This argument is required when the public key encoding is UNCOMPRESSED, otherwise it is ignored.
hash_function (Hash) – The hash function used to generate the signatures.
pubkey_encoding (PubKeyEncoding) – The encoding of the public keys used.
- Returns:
A dictionary of results. 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.
- Return type:
Example
We start by importing the ECDSA module.
>>> from crypto_condor.primitives import ECDSA
For this example we already have a correctly formatted file. We print the first line to show the format, then split the arguments for clarity.
>>> filename = "/tmp/ecdsa-p256-sha256-signatures.txt" >>> with open(filename, "r") as fd: ... line = fd.readline().strip() ... args = line.split("/") ... print(line) ... print(f"key = {args[0]}") ... print(f"msg = {args[1]}") ... print(f"sig = {args[2]}") 04e424dc61d4bb3cb7ef4344a7f8957a0c5134e16f7a67c074f82e6e12f49abf3c970eed7aa2bc48651545949de1dddaf0127e5965ac85d1243d6f60e7dfaee927/e1130af6a38ccb412a9c8d13e15dbfc9e69a16385af3c3f1e5da954fd5e7c45fd75e2b8c36699228e92840c0562fbf3772f07e17f1add56588dd45f7450e1217ad239922dd9c32695dc71ff2424ca0dec1321aa47064a044b7fe3c2b97d03ce470a592304c5ef21eed9f93da56bb232d1eeb0035f9bf0dfafdcc4606272b20a3/3045022100bf96b99aa49c705c910be33142017c642ff540c76349b9dab72f981fd9347f4f022017c55095819089c2e03b9cd415abdf12444e323075d98f31920b9e0f57ec871c key = 04e424dc61d4bb3cb7ef4344a7f8957a0c5134e16f7a67c074f82e6e12f49abf3c970eed7aa2bc48651545949de1dddaf0127e5965ac85d1243d6f60e7dfaee927 msg = e1130af6a38ccb412a9c8d13e15dbfc9e69a16385af3c3f1e5da954fd5e7c45fd75e2b8c36699228e92840c0562fbf3772f07e17f1add56588dd45f7450e1217ad239922dd9c32695dc71ff2424ca0dec1321aa47064a044b7fe3c2b97d03ce470a592304c5ef21eed9f93da56bb232d1eeb0035f9bf0dfafdcc4606272b20a3 sig = 3045022100bf96b99aa49c705c910be33142017c642ff540c76349b9dab72f981fd9347f4f022017c55095819089c2e03b9cd415abdf12444e323075d98f31920b9e0f57ec871c
We use
test_output_sign()
to test this file. In this case the keys are DER-encoded and we used SHA-256 to hash the messages.>>> result = ECDSA.test_output_sign( ... filename, ... ECDSA.Curve.P256, ... ECDSA.Hash.SHA256, ... ECDSA.PubKeyEncoding.UNCOMPRESSED, ... ) [P-256][SHA-256] Test signatures from file ...
Added in version TODO(version): Replaces
verify_file()
.
- crypto_condor.primitives.ECDSA.verify_file(filename, pubkey_encoding, hash_function, curve)¶
Verifies signatures contained in a file.
Signatures are read from the file and then verified with a reference implementation. The file must have the following format:
- 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:
pub_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.
hash_function (Hash) – The hash function used to generate the signatures.
curve (Curve | None) – The elliptic curve of the keys. This argument is required when the public key encoding is UNCOMPRESSED, otherwise it is ignored.
- Returns:
A dictionary of results.
- Return type:
Deprecated since version TODO(version): Will be removed in a future version, use
test_output_sign()
instead.
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)¶
The supported elliptic curves.
- Member Type:
str
Valid values are as follows:
- P224 = <Curve.P224: 'P-224'>¶
- P256 = <Curve.P256: 'P-256'>¶
- P384 = <Curve.P384: 'P-384'>¶
- P521 = <Curve.P521: 'P-521'>¶
- B283 = <Curve.B283: 'B-283'>¶
- B409 = <Curve.B409: 'B-409'>¶
- B571 = <Curve.B571: 'B-571'>¶
- SECP256K1 = <Curve.SECP256K1: 'secp256k1'>¶
- BRAINPOOLP256R1 = <Curve.BRAINPOOLP256R1: 'brainpoolP256r1'>¶
- BRAINPOOLP384R1 = <Curve.BRAINPOOLP384R1: 'brainpoolP384r1'>¶
- BRAINPOOLP512R1 = <Curve.BRAINPOOLP512R1: 'brainpoolP512r1'>¶
- enum crypto_condor.primitives.ECDSA.Hash(value)¶
The supported hash functions.
- Member Type:
str
Valid values are as follows:
- SHA224 = <Hash.SHA224: 'SHA-224'>¶
- SHA256 = <Hash.SHA256: 'SHA-256'>¶
- SHA384 = <Hash.SHA384: 'SHA-384'>¶
- SHA512 = <Hash.SHA512: 'SHA-512'>¶
- SHA512_224 = <Hash.SHA512_224: 'SHA-512/224'>¶
- SHA512_256 = <Hash.SHA512_256: 'SHA-512/256'>¶
- SHA3_224 = <Hash.SHA3_224: 'SHA3-224'>¶
- SHA3_256 = <Hash.SHA3_256: 'SHA3-256'>¶
- SHA3_384 = <Hash.SHA3_384: 'SHA3-384'>¶
- SHA3_512 = <Hash.SHA3_512: 'SHA3-512'>¶
Protocols¶
- 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__(sk, msg)¶
Signs a message with ECDSA.
- Parameters:
sk (bytes) – The private elliptic curve key. Can be encoded as PEM, DER, or an integer in bytes depending on
KeyEncoding
.msg (bytes) – The message to sign.
- Returns:
A DER-encoded signature.
- Return type:
bytes
- 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__(pk, msg, sig)¶
Verifies an ECDSA signature.
- Parameters:
pk (bytes) – The public elliptic curve key. Can be encoded as PEM, DER, or an integer in bytes depending on
PubKeyEncoding
.msg (bytes) – The signed message.
sig (bytes) – The signature to verify.
- Returns:
True if the signature is valid, False otherwise.
- Return type:
bool
Test vectors¶
The following tables describe the test vectors available for each combination of elliptic curve and hash function. C denotes compliance, R resilience.
SHA-224 |
SHA-256 |
SHA-384 |
SHA-512 |
SHA-512/224 |
SHA-512/256 |
|
---|---|---|---|---|---|---|
P-224 |
C |
C |
C |
C |
C |
C |
P-256 |
C |
C |
C |
C |
C |
C |
P-384 |
C |
C |
C |
C |
C |
C |
P-521 |
C |
C |
C |
C |
C |
C |
B-283 |
C |
C |
C |
C |
x |
x |
B-409 |
C |
C |
C |
C |
x |
x |
B-571 |
C |
C |
C |
C |
x |
x |
SHA-224 |
SHA-256 |
SHA-384 |
SHA-512 |
SHA-512/224 |
SHA-512/256 |
SHA3-224 |
SHA3-256 |
SHA3-384 |
SHA3-512 |
|
---|---|---|---|---|---|---|---|---|---|---|
P-224 |
C + R |
C + R |
C |
C + R |
C |
C |
R |
R |
x |
R |
P-256 |
C |
C + R |
C |
C + R |
C |
C |
x |
R |
x |
R |
P-384 |
C |
C |
C + R |
C + R |
C |
C |
x |
x |
R |
R |
P-521 |
C |
C |
C |
C + R |
C |
C |
x |
x |
x |
R |
B-283 |
C |
C |
C |
C |
x |
x |
x |
x |
x |
x |
B-409 |
C |
C |
C |
C |
x |
x |
x |
x |
x |
x |
B-571 |
C |
C |
C |
C |
x |
x |
x |
x |
x |
x |
secp256k1 |
x |
R |
x |
R |
x |
x |
x |
R |
x |
R |
brainpoolP256r1 |
x |
R |
x |
x |
x |
x |
x |
x |
x |
x |
brainpoolP384r1 |
x |
x |
R |
x |
x |
x |
x |
x |
x |
x |