如何获取ECDSA签名的固定长度字节表示(Python+cryptography.io)
Great question! The variable length of DER-encoded ECDSA signatures happens because ASN.1 DER omits leading zeros from the integer values r and s—plus, if an integer's highest bit is set, DER adds an extra zero byte to avoid interpreting it as a negative number. This leads to signatures that can be 70, 71, or 72 bytes for 256-bit curves.
To get a fixed-length representation, the most common approach is to convert the DER signature into a concatenation of the r and s values, each padded to exactly 32 bytes (since 256 bits = 32 bytes). This gives you a consistent 64-byte signature. Here's how to do it with Python and the cryptography ecosystem:
Step 1: Install dependencies (if needed)
First, make sure you have asn1crypto installed (it's a reliable library for parsing ASN.1/DER data):
pip install asn1crypto cryptography
Step 2: Convert DER signature to fixed-length bytes
This function takes your DER-encoded signature and returns a 64-byte fixed-length version:
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey from cryptography.hazmat.primitives import hashes import asn1crypto.core def der_to_fixed_length(der_signature: bytes, curve_bit_length: int = 256) -> bytes: # Parse the DER signature into r and s integers signature_seq = asn1crypto.core.Sequence.load(der_signature) r = signature_seq[0].native s = signature_seq[1].native # Calculate the required byte length per value (256 bits = 32 bytes) per_value_byte_len = curve_bit_length // 8 # Convert r and s to big-endian bytes, padding with leading zeros to reach fixed length r_bytes = r.to_bytes(per_value_byte_len, byteorder="big", signed=False) s_bytes = s.to_bytes(per_value_byte_len, byteorder="big", signed=False) # Concatenate to get the fixed-length signature return r_bytes + s_bytes
Step 3: Convert fixed-length back to DER for verification
If you need to verify the fixed-length signature later, you'll need to convert it back to DER format first. Here's how:
def fixed_length_to_der(fixed_signature: bytes, curve_bit_length: int = 256) -> bytes: per_value_byte_len = curve_bit_length // 8 if len(fixed_signature) != per_value_byte_len * 2: raise ValueError(f"Fixed signature must be {per_value_byte_len * 2} bytes long") # Extract r and s from the fixed-length bytes r = int.from_bytes(fixed_signature[:per_value_byte_len], byteorder="big", signed=False) s = int.from_bytes(fixed_signature[per_value_byte_len:], byteorder="big", signed=False) # Build the ASN.1 sequence and dump it to DER signature_seq = asn1crypto.core.Sequence() signature_seq.append(asn1crypto.core.Integer(r)) signature_seq.append(asn1crypto.core.Integer(s)) return signature_seq.dump()
Example Usage
Here's how to tie it all together with your existing signing code:
# Assume you have your private key loaded as an EllipticCurvePrivateKey instance private_key: EllipticCurvePrivateKey = ... message = b"Your message to sign" # Generate DER signature der_sig = private_key.sign(message, ECDSA(hashes.SHA256())) # Convert to fixed-length (64 bytes) fixed_sig = der_to_fixed_length(der_sig) print(f"Fixed signature length: {len(fixed_sig)} bytes") # Output: 64 # Convert back to DER for verification restored_der_sig = fixed_length_to_der(fixed_sig) # Verify the restored signature (using your public key) public_key = private_key.public_key() public_key.verify(restored_der_sig, message, ECDSA(hashes.SHA256()))
This approach is widely used in systems like Bitcoin and other blockchain protocols, so it's a well-tested way to get consistent signature lengths.
内容的提问来源于stack exchange,提问作者jMyles

