SecKeyVerifySignature failing to verify signature

I am using the ecdsa python library to generate keys and signatures for messages, but I am unable to verify those signatures in Swift.


I'm trying to simply verify a signature of the text "S". I've been able to successfully create a public SecKey, but no matter what I do, I cannot seem to get SecKeyVerifySignature to pass. The payload is hashed using SHA-1, and I've been able to verify the digest in Swift matches the digest in python. The failing code below should run in xCode.


//  ecdsaVerify

import Foundation
import CommonCrypto


extension Data {
    struct HexEncodingOptions: OptionSet {
        let rawValue: Int
        static let upperCase = HexEncodingOptions(rawValue: 1 << 0)
    }
    
    func hexEncodedString(options: HexEncodingOptions = []) -> String {
        let format = options.contains(.upperCase) ? "%02hhX" : "%02hhx"
        return map { String(format: format, $0) }.joined()
    }
}

extension String{
    func sha1() -> String {
        let data = Data(self.utf8)
        var digest = [UInt8](repeating: 0, count:Int(CC_SHA1_DIGEST_LENGTH))
        data.withUnsafeBytes {
            _ = CC_SHA1($0.baseAddress, CC_LONG(data.count), &digest)
        }
        let hexBytes = digest.map { String(format: "%02hhx", $0) }
        return hexBytes.joined()
    }
    
    var hexadecimalStringToData: Data? {
        // Convert a hexstring to data
        var data = Data(capacity: self.count / 2)
        
        let regex = try! NSRegularExpression(pattern: "[0-9a-f]{1,2}", options: .caseInsensitive)
        regex.enumerateMatches(in: self, range: NSRange(startIndex..., in: self)) { match, _, _ in
            let byteString = (self as NSString).substring(with: match!.range)
            let num = UInt8(byteString, radix: 16)!
            data.append(num)
        }
        
        guard data.count > 0 else { return nil }
        
        return data
    }
}



//prime256v1, a.k.a kSecECCurveSecp256r1
// .der with first 26 bits removed
let EC_PUBLIC_KEY = """
-----BEGIN BARE KEY-----
BOptLvuuwPYOaQC6v7hDh36fTqsuwFTCyHVg3uYvfdbPtV5c3urT8HeS/UaGgaO8tMFfpz1sljTnwWTPb8gQcD8=
-----END BARE KEY-----
"""

// Here for example purposes - to help others generate signatures.
let PRIVATE_KEY = """
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEINxdvHX9q9PGrJBWHcmvXGe3poOc0Qc1/C4HnScp6cP7oAoGCCqGSM49
AwEHoUQDQgAE6m0u+67A9g5pALq/uEOHfp9Oqy7AVMLIdWDe5i991s+1Xlze6tPw
d5L9RoaBo7y0wV+nPWyWNOfBZM9vyBBwPw==
-----END EC PRIVATE KEY-----
"""

class ecdsaVerifier{
    
    var publicKeyData:Data
    var publicKey: SecKey?
    
    init(_ pemPublicKey: String){
        
        let pemPublicKeyNoComments = pemPublicKey.replacingOccurrences(
            of: #"(?m)^----.*"#,
            with: "",
            options: .regularExpression
            ).trimmingCharacters(in: .whitespacesAndNewlines).replacingOccurrences(of: "\n", with: "")
        self.publicKeyData = Data(base64Encoded:pemPublicKeyNoComments)!
        var error: Unmanaged<CFError>? = nil
        self.publicKey = SecKeyCreateWithData(self.publicKeyData as NSData, [
            kSecAttrKeyType: kSecAttrKeyTypeECDSA,
            kSecAttrKeyClass: kSecAttrKeyClassPublic,
            kSecAttrKeySizeInBits: 256
            ] as NSDictionary, &error)
        print(self.publicKey)
        
    }
    
    
    func verifyFromHexString(content: String, hexSignature: String) -> Bool{
        let cleanSignature = hexSignature.trimmingCharacters(in: .whitespacesAndNewlines).replacingOccurrences(of: "\n", with: "").replacingOccurrences(of: " ", with: "")
        print("verify from", cleanSignature, content)
        let cleanSignatureData = cleanSignature.hexadecimalStringToData!
        let digest = content.sha1().hexadecimalStringToData!
        //let digest = content.data(using: .utf8)!
        print("Digest", digest.count, digest.hexEncodedString())
        
        return self.verifyFromDigest(digest, signature: cleanSignatureData)
    }
    func verifyFromDigest(_ digest: Data, signature: Data) -> Bool{
        var error: Unmanaged<CFError>? = nil
        
        guard SecKeyVerifySignature(publicKey!, SecKeyAlgorithm.ecdsaSignatureDigestX962SHA1, digest as CFData, signature as CFData, &error) else {
            print("error", error)
            return false
        }
        print("true!")
        return true
    }
    
}

let verifier = ecdsaVerifier(EC_PUBLIC_KEY)

let signature = """
30 44 02 20 14 08 69 ac 3a 39 29 78 c2 fd e1 0a
c2 6d 87 4e 66 57 fd bf 1a 01 b8 4c b2 13 63 fc
f6 e8 0b 64 02 20 42 88 2e a0 c8 4a ca 04 a1 0a
59 be e3 c7 50 d6 53 d4 14 d3 a6 f7 e6 07 0d e2
e5 6b 8f 44 47 52
"""

let content = "S"
verifier.verifyFromHexString(content: content, hexSignature: signature)


Any help would be appriciated.

Replies

Debugging problems like this is a pain, especially with EC signatures [1].

I had a quick look at your stuff and I can’t see anything obviously broken. have you tried creating the signature on the Apple side (using

SecKeyCreateSignature
) and verifying that? That should work, obviously, but if it fails it might be point to a problem with your verify code. You can take then take that signature to the Python side to see if that works.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

[1] In RSA you can use raw mode to see what’s inside the signature.

Problem was on the python side.


`Missed that there was a `sigencode` parameter to ensure the signatures were compatible with openssl.

https://github.com/warner/python-ecdsa#openssl-compatibility


privkey.sign(msg, hashfunc=hashlib.sha1, sigencode=ecdsa.util.sigencode_der)