Python Cryptography: Sign and Verify Digital Signatures
python cryptography digital signatures sign and verify: Learn how to generate RSA and ECDSA keys, sign messages, and verify signatures in Python using the cryptography...
When you need to prove that a message came from a specific sender and was not altered in transit, digital signatures are the standard mechanism. In Python, the cryptography library provides a straightforward API for signing and verifying data. This article covers how to generate keys, sign messages, verify signatures, and avoid common pitfalls when using python cryptography digital signatures sign and verify.
Why Digital Signatures Matter in Python Applications
Digital signatures solve two problems at once: authenticity and integrity. A signature created with a private key can be verified by anyone holding the corresponding public key, confirming that the message was signed by the key owner and that the content has not been modified. This is essential for scenarios like distributing software updates, authenticating API requests, or signing documents in a workflow.
The cryptography library is the de facto standard for cryptographic operations in Python. It exposes low-level primitives with a clean, high-level API, and it is actively maintained. The library supports both RSA and ECDSA signature algorithms, each with its own tradeoffs.
Generating a Key Pair
Before signing, you need a key pair. The cryptography library generates RSA and ECDSA keys with a few lines of code. The private key is used for signing, and the public key is distributed for verification.
from cryptography.hazmat.primitives.asymmetric import rsa, ec from cryptography.hazmat.primitives import serialization # RSA key pair (2048-bit is a common minimum) rsa_private_key = rsa.generate_private_key( public_exponent=65537, key_size=2048, ) # ECDSA key pair (using the P-256 curve) ecdsa_private_key = ec.generate_private_key(ec.SECP256R1())
The RSA key generation uses a public exponent of 65537, which is a widely accepted value for security and performance. The key size of 2048 bits is considered secure for most applications. ECDSA keys are smaller and faster to generate; the P-256 curve is a good default.
You can serialize these keys to disk for later use, but for this article we focus on the in-memory operations.
Signing a Message
Signing a message involves hashing it and then applying the private key to the hash. The cryptography library handles the hashing internally when you pass a hash algorithm to the signing method.
For RSA, you must choose a padding scheme. PKCS1v15 is simpler and widely compatible, while PSS is more modern and provides better security properties. The following example signs a byte string with an RSA private key using PSS padding:
from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import padding message = b"Important message to sign" signature = rsa_private_key.sign( message, padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH ), hashes.SHA256() )
The MGF1 mask generation function is required for PSS, and the salt length can be set to MAX_LENGTH to use the maximum salt, which is the most secure option. The hash algorithm is specified separately; SHA-256 is a good default.
For ECDSA, the signing process is simpler because there is no padding parameter. You just pass the hash algorithm:
signature = ecdsa_private_key.sign( message, ec.ECDSA(hashes.SHA256()) )
The resulting signature is a byte string that can be transmitted along with the message. It is not a fixed size for RSA (it depends on the key size), but for ECDSA it is typically 64 bytes for P-256.
Verifying a Signature
The verification process uses the public key and the same padding and hash parameters that were used for signing. If the signature does not match, the library raises an InvalidSignature exception.
from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives.asymmetric import ec, padding public_key = rsa_private_key.public_key() try: public_key.verify( signature, message, padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH ), hashes.SHA256() ) print("Signature is valid.") except InvalidSignature: print("Signature is invalid.")
For ECDSA, the verification call is analogous:
public_key = ecdsa_private_key.public_key() try: public_key.verify( signature, message, ec.ECDSA(hashes.SHA256()) ) print("Signature is valid.") except InvalidSignature: print("Signature is invalid.")
It is critical to use the exact same padding and hash parameters for verification as were used for signing. A mismatch will cause verification to fail even if the key is correct.
Choosing Between RSA and ECDSA
The choice between RSA and ECDSA depends on your application's constraints. Both are widely supported, but they differ in key size, performance, and compatibility.
| Property | RSA | ECDSA |
|---|---|---|
| Key size | 2048–4096 bits | 256–521 bits (curve) |
| Signature size | Same as key size | ~2× curve size (64 bytes for P-256) |
| Signing speed | Slower | Faster |
| Verification | Faster | Slower |
| Compatibility | Universal | Common in modern systems |
| Use case | Legacy systems, broad support | Resource-constrained devices, high-throughput signing |
RSA is more widely supported in older protocols and libraries. ECDSA offers smaller keys and signatures, which can reduce storage and bandwidth. If you are building a new system and have control over both ends, ECDSA with P-256 is a solid choice. If you need to interoperate with existing infrastructure that expects RSA, stick with RSA.
Common Mistakes When Signing and Verifying
Several pitfalls can lead to signatures that fail or are insecure. The most frequent mistake is using different padding or hash parameters during signing and verification. Always centralize these parameters in a shared configuration or helper function.
Another common issue is encoding. Signatures and messages are byte strings. If you are signing text, ensure you encode it consistently (e.g., UTF-8) before signing and before verification. A mismatch in encoding will produce a different byte sequence and cause verification to fail.
Using the wrong key is another problem. Verification must use the public key that corresponds to the private key used for signing. If you accidentally use a different key, the signature will not verify. This can happen when keys are rotated or loaded from different sources.
Finally, do not reuse a key pair for both signing and encryption. RSA keys can be used for both, but doing so increases the attack surface. Generate separate key pairs for each purpose.
Performance and Security Considerations
Signing and verification are computationally expensive operations. The cost depends on the algorithm and key size. RSA signing is slower than ECDSA signing, but RSA verification is faster. If your application signs many messages, ECDSA may be more efficient. If you verify many signatures, RSA might be preferable.
Key size directly affects security. RSA 2048-bit is considered secure today, but some organizations require 3072 or 4096 bits for long-term protection. ECDSA with P-256 offers equivalent security to RSA 3072. Larger keys increase CPU usage and signature size, so choose the smallest key that meets your security requirements.
Hash selection matters as well. SHA-256 is a good default. Avoid SHA-1 for any new system, as it is considered broken for collision resistance. The cryptography library will raise an error if you attempt to use an insecure hash for signing.
From a security perspective, the private key must be protected. Never hardcode keys in source code. Use environment variables, secret managers, or hardware security modules (HSMs) in production. Also, ensure that the public key you use for verification is authentic. An attacker who can replace a public key can forge signatures.
Finally, be aware that signature verification does not protect against replay attacks. If an attacker captures a valid signed message, they can resend it later. If replay is a concern, include a timestamp or a nonce in the signed payload.