-
Notifications
You must be signed in to change notification settings - Fork 315
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
any support for ASN.1 encoding signature? #55
Comments
Currently I write some customized wrapper by myself to solve this issue, but not sure @warner has a plan already. |
This works well for me. Is there any reason why this should not work? How about unsigned integers? from ecdsa import SigningKey, VerifyingKey, NIST256p
import hashlib
import logging
logging.basicConfig(level=logging.DEBUG)
# Generate public & private keys
private_key = SigningKey.generate(curve=NIST256p)
public_key = private_key.get_verifying_key()
# Sign the "ABC" message, hashed with SHA-256
digest = "ABC".encode('utf-8')
signature = private_key.sign(digest, hashfunc=hashlib.sha256)
# Verify the "ABC" message, using the
public_key.verify(signature, digest, hashfunc=hashlib.sha256)
# Encode the signature in ASN.1 format
raw_signature_hex = ''.join(x.encode('hex') for x in signature)
asn1_signature = "3046" + "022100" + raw_signature_hex[:64] + "022100" + raw_signature_hex[64:]
# Write keys to disk
open("private_key.pem","wb").write(private_key.to_pem())
open("public_key.pem","wb").write(public_key.to_pem())
open("data.bin","wb").write(digest)
open("data.sig","wb").write(asn1_signature)
logging.info('Digest : ' + ''.join(x.encode('hex') for x in digest))
logging.info('Signature : ' + asn1_signature)
logging.info('Public key : \n' + public_key.to_pem()) |
@hfossli this is P-256 specific, the ASN.1 encoding will fail with any curve that produces different length output |
Yep, thanks for pointing that out. 👍 |
So, it seems my code has several flaws. I decided to code the asn1 encoder in a language I was more familiar with. The asn1 encoding can look like this. Tested with 10 000 signatures. // https://crypto.stackexchange.com/a/1797
public struct EcdsaAsn1Signature {
static func smallestBigEndian(_ bytes: Data) -> Data {
var smallest = bytes
while smallest.first == 0x00 {
smallest.removeFirst()
}
if let firstByte = smallest.first, firstByte > 0x7f {
return Data(bytes: [0x00]) + smallest
} else {
return smallest
}
}
static func isProperlyAsn1Encoded(_ bytes: Data) -> Bool {
var parser = bytes
guard bytes.count > 2 else { return false }
guard parser.popFirst() == 0x30 else { return false }
guard let sequenceLength = parser.popFirst() else { return false }
guard parser.count == sequenceLength else { return false }
guard parser.popFirst() == 0x02 else { return false }
guard let rLength = parser.popFirst() else { return false }
guard rLength < parser.count else { return false }
parser.removeFirst(Int(rLength))
guard parser.count > 2 else { return false }
guard parser.popFirst() == 0x02 else { return false }
guard let sLength = parser.popFirst() else { return false }
guard sLength == parser.count else { return false }
return true
}
static func encode(_ bytes: Data) -> Data {
guard !isProperlyAsn1Encoded(bytes) else {
return bytes
}
let r = smallestBigEndian(bytes.prefix(bytes.count / 2))
let s = smallestBigEndian(bytes.suffix(bytes.count / 2))
var asn1 = Data()
asn1.append(0x30)
asn1.append(UInt8(2 + r.count + 2 + s.count))
asn1.append(0x02)
asn1.append(UInt8(r.count))
asn1.append(r)
asn1.append(0x02)
asn1.append(UInt8(s.count))
asn1.append(s)
return asn1
}
} It should be fairly easy to port this to python. I don't think this code has a problem with being P-256 specific as it makes no assumptions about length. |
I'm not so sure if it will work for P-521 - for it the the combined length will be 136, which is longer than 127, and if I recall my ASN.1 correctly, that requires different way to encode length... |
Thanks for letting me know. Good point! |
The |
As @wiml pointed out, this feature is already implemented, the example code from #55 (comment) from ecdsa import SigningKey, VerifyingKey, NIST256p
from ecdsa.util import sigencode_der, sigdecode_der
import hashlib
import logging
logging.basicConfig(level=logging.DEBUG)
# Generate public & private keys
private_key = SigningKey.generate(curve=NIST256p)
public_key = private_key.get_verifying_key()
# Sign the "ABC" message, hashed with SHA-256
digest = "ABC".encode('utf-8')
signature = private_key.sign(digest, hashfunc=hashlib.sha256, sigencode=sigencode_der)
# Verify the "ABC" message, using the
public_key.verify(signature, digest, hashfunc=hashlib.sha256, sigdecode=sigdecode_der)
# Write keys to disk
open("private_key.pem","wb").write(private_key.to_pem())
open("public_key.pem","wb").write(public_key.to_pem())
open("data.bin","wb").write(digest)
open("data.sig","wb").write(signature)
logging.info('Digest : ' + digest.hex())
logging.info('Signature : ' + signature.hex())
logging.info('Public key : \n' + str(public_key.to_pem(), 'ascii')) |
And please note that, in general, the signatures should be DER encoded, that means that 0 byte padding needs to be applied only if the first byte of encoded integer has value greater than 0x7f. In other words, the code from #55 (comment) will create malformed signatures (it does create valid BER encoded signatures, so implementations that expect BER will be able to verify them). That being said, |
I wonder to know if the library supports ASN.1 format output of signature?
The text was updated successfully, but these errors were encountered: