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'>#
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.
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:
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.
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:
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 theverify
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:
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
andSign
).>>> 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:
- 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:
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:
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 raiseIOError
, 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
andwycheproof/verify
), and testing both (sign-then-verify
).- Return type:
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 coordinatesQx
andQy
of the public value, or a tuple (d, None, None) containing only the private valued
.- Return type:
tuple[int, int | None, int | None]