ECDSA on python explained: Elliptic Curve Digital Signature Algorithm

#StandWithUkraine
Today, 4th December 2022, Ukraine is still bravely fighting for democratic values, human rights and peace in whole world. Russians ruthlessly kill all civilians in Ukraine including childs and destroy their cities. We are uniting against Putin’s invasion and violence, in support of the people in Ukraine. You can help by donating to Ukrainian's army.

First of all let's imagine Bob generates pair of private and public keys:

from ecdsa import SigningKey, VerifyingKey, NIST192p
# NIST192p private key length is 24 bytes
# to see benchmark algorythm use Ctrl+F "NIST192p" in full table here https://pypi.org/project/ecdsa/

sk = SigningKey.generate(curve=NIST192p)
verifying_key_hex_string = sk.verifying_key.to_string().hex()
print('Signing Key (private):', sk.to_string().hex())
print('Verification Key (public):', verifying_key_hex_string)

Example output:

Signing Key (private): 78a4dbe1972dc979b97962d0fcb2f18cfad0b09c69079031
Verification Key (public): 8b0073855c90db6e19f8e3bd135a4a319daedb094475a2968ac9f0efbe8f4fbdc695ef03f74f0d30d822a867abd692f0

As we can see private key is hex string with length 48 chars, so it is 24 Bytes (192 bits). Public key length ios 48 Bytes.

Here is how we can use these keys:

To sign some data Bob can run:

sk = SigningKey.from_string(bytearray.fromhex(signing_key_hex_string))
text = "msg1"
signature = sk.sign(text.encode('utf-8'))
signature_hex_string = signature.hex()
print('Signature:', signature_hex_string)

Example output

Signature: 4c0db71ccfda191999f50049b8c450bd45c9ecd4b643e11c296c6a40a8e051e7e45955611608578ab8f20ad8f94ad748

Lenght is also 48 bytes (same as public key).

So he already shared his verifying_key_hex_string with the world, and now he can share the data itelf "msg1" and signature_hex_string.

Alice or anyone who is sure that public key (verifying_key_hex_string) belongs to Bob can verify that exactly Bob issued the msg1:

verifyingkey = VerifyingKey.from_string(bytearray.fromhex(verifying_key_hex_string))
signature = bytearray.fromhex(signature_hex_string)
print('Verify transmited data', verifyingkey.verify(signature, "msg1".encode('utf-8')))

Example output:

Verify transmited data True

If data will be changed, then this call:

print('Verify modified data', verifyingkey.verify(signature, "msg2".encode('utf-8')))

Will raise an exception:

Traceback (most recent call last):
  File "m.py", line 22, in <module>
    print('Verify modified data', verifyingkey.verify(signature, "msg2".encode('utf-8')))
  File "/home/ivan/.local/share/virtualenvs/tmp-zNBhjCoW/lib/python3.8/site-packages/ecdsa/keys.py", line 682, in verify
    return self.verify_digest(signature, digest, sigdecode, allow_truncate)
  File "/home/ivan/.local/share/virtualenvs/tmp-zNBhjCoW/lib/python3.8/site-packages/ecdsa/keys.py", line 736, in verify_digest
    raise BadSignatureError("Signature verification failed")
ecdsa.keys.BadSignatureError: Signature verification failed

Conclusions

Using hash

If the message is pretty long, then instead of signing data itself, Bob could just sign HASH of data, e.g. sha-256, hash is shorter (fixed 32 bytes for any amount of text) so signing it would be faster, when reliability of the idea is same.

To hash data in python you can use:

sk = SigningKey.from_string(bytearray.fromhex(signing_key_hex_string))
text = "msg1"
hash_bytes = sha256(text.encode('utf-8')).digest()
print('Data hash', hash_bytes.hex())
signature = sk.sign(hash_bytes)
signature_hex_string = signature.hex()
print('Signature:', signature_hex_string)

Then, to verify the data make same hashing:

hash_bytes = sha256(text.encode('utf-8')).digest()
print('Verify transmited data', verifyingkey.verify(signature, hash_bytes))

Trick is that you can't change the data without changing hash, so it gives same guarantee if we would sign original amout of text.

ecdsa python

#ecdsa #hash
0
Ivan Borshchov profile picture
Apr 10, 2022
by Ivan Borshchov
Did it help you?
Yes !
No