ECDSA on python explained: Elliptic Curve Digital Signature Algorithm
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:
- Bob will use his private key to sign some data. He will never share this private key with anyone. He will just store it on his device. He can use it once to sign one portion of data, or use it as many times as he needs to sign new and new portions of data.
- Bob will share his public key with a whole world and say "Hey, look, it is my key, anyone can use it to verify that some message is created by me".
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
- When Bob issues some message, he provides a signature with this messages, and suggests anyone to verify that the data were created exactly by him, not by someone else.
- Someone else e.g. Lora who has access to Bob's public key (because it public) can say "it is my public key", so when Bob will issue a message and sign it, Lora can say "it was me, because it is my key". But if there is no conflicting interests, Lora will have no sense to abuse it.
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.