CryptoKit odd signature format

Hi,


I'm playing around with the new CryptoKit framework on iOS. I'm trying to generate eliptic curve keys, sign some data with them, export the signature and the public key and verify the signature externaly (in this case with openssl).


I'm not getting this to work with the format that ECDSASignature.derRepresentation provides. However, I can make it work when using SecKeyRawSign from the old Security framework.


Here is the code for both singings using the same key. It creates a shell script that demonstrate the signature verification with on baord openssl.


import Foundation
import CryptoKit


struct SignDemo {
    
    private let secp384r1header: [UInt8] = [0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x22, 0x03, 0x62, 0x00]
    
    func run() {
        var shell: [String] = []
        shell.append("\n\n#! /bin/sh")
        
        // The data to sign
        let dataToSign = "The data to sign!".data(using: .utf8)!
        shell.append("echo \(dataToSign.map { String(format: "%02hhx", $0) }.joined()) | xxd -r -p > dataToSign.dat")
        
        // Keys
        let privKey = P384.Signing.PrivateKey()
        let pubKey = privKey.publicKey
        var pubKeyDER = Data(secp384r1header)
        pubKeyDER.append(pubKey.x963Representation)
        shell.append("echo \(pubKeyDER.map { String(format: "%02hhx", $0) }.joined()) | xxd -r -p > key.der")
        shell.append("cat > key.pem <<eof\n-----begin public="" key-----\n\(pubkeyder.base64encodedstring().separate())\n-----end="" key-----\neof")<br="">        
        // Sign with Sec
        let secSignature = try! secSing(digest: dataToSign, privKeyData: privKey.x963Representation)
        shell.append("echo \(secSignature.map { String(format: "%02hhx", $0) }.joined()) | xxd -r -p > sig-sec.dat")
        shell.append("/usr/local/opt/openssl/bin/openssl dgst -SHA256 -verify key.pem -signature sig-sec.dat dataToSign.dat")
        
        // Sign with CryptoKit
        let ckSignature = try! cryptoKitSing(digest: dataToSign, privKey: privKey)
        shell.append("echo \(ckSignature.map { String(format: "%02hhx", $0) }.joined()) | xxd -r -p > sig-ck.dat")
        shell.append("/usr/local/opt/openssl/bin/openssl dgst -SHA256 -verify key.pem -signature sig-ck.dat dataToSign.dat")
        
        print(shell.joined(separator: "\n"))
    }
    
    private func cryptoKitSing(digest: Data, privKey: P384.Signing.PrivateKey) throws -> Data {
        return try privKey.signature(for: digest).derRepresentation
    }
    
    private func secSing(digest: Data, privKeyData: Data) throws -> Data {
        let attributes: [String:Any] =
            [
                kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
                kSecAttrKeyType as String: kSecAttrKeyTypeEC,
                kSecAttrKeySizeInBits as String: 384,
        ]
        
        guard let secKey = SecKeyCreateWithData(privKeyData as CFData, attributes as CFDictionary, nil) else {
            throw MyError(msg: "Error: Problem in SecKeyCreateWithData()")
        }
        
        // TODO: I guess that can be done easier
        let digestToSign = SHA256.hash(data: digest).makeIterator()
        var digestToSignBytes = [UInt8]()
        digestToSign.forEach { byte in
            digestToSignBytes.append(byte)
        }
        
        var signatureLength = 103
        var signatureBytes = [UInt8](repeating: 0, count: signatureLength)
        let signErr = SecKeyRawSign(secKey, .PKCS1SHA256, &digestToSignBytes, digestToSignBytes.count, &signatureBytes, &signatureLength)
        guard signErr == errSecSuccess else {
            throw MyError(msg: "Could not create signature. OSStatus: \(signErr)")
        }
        
        return Data(signatureBytes)
    }
}

struct MyError: Error {
    let msg: String
}


extension String {
    func separate(every stride: Int = 64, with separator: Character = "\n") -> String {
        return String(enumerated().map { $0 > 0 && $0 % stride == 0 ? [separator, $1] : [$1]}.joined())
}

Wenn running that code in an iOS App and then use the script to verify, the following happens:


bash-3.2$
bash-3.2$ echo 546865206461746120746f207369676e21 | xxd -r -p > dataToSign.dat
bash-3.2$ echo 3076301006072a8648ce3d020106052b8104002203620004d2d3225b5c46dea5e39a0592e20ae25b8ba823374a110fde409ad6c71bcc334d7b0b5916d7fb0256b7c5716f0a9f339b35c0b96813101e24b8d5849bde61d710b62b71fc3e0e7e59dbdc6ed77820aecd5857ae1545c0d7eaefb43d02641e9b4a | xxd -r -p > key.der
cat > key.pem <<eof
-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE0tMiW1xG3qXjmgWS4griW4uoIzdKEQ/e
QJrWxxvMM017C1kW1/sCVrfFcW8KnzObNcC5aBMQHiS41YSb3mHXELYrcfw+Dn5Z
29xu13ggrs1YV64VRcDX6u+0PQJkHptK
-----END PUBLIC KEY-----
EOF
echo 3065023100e4ad6c0d5fbd68fe0ee6754e4d8651af07e49d72edcd1dffdf2fc3c79d7690ce91196ffe6d5aa67b85560c1283a0db3502307c8769b38f75e7f90ef64432c2746021bf3198507edbc95eefbf43dde7b83d95efd2e06549efc73742439d0aab057a86 | xxd -r -p > sig-sec.dat
/usr/local/opt/openssl/bin/openssl dgst -SHA256 -verify key.pem -signature sig-sec.dat dataToSign.dat
echo 30640230702fd094ca99e0736fa0f7cd005a9d68105d2c3be7de4ecf3e8f47531b6692629449e13abc8162f4bdcf0e6a340f59de023001d45750caf603615eed2404b05007ef44dcc10460a66bc346e62299808bd430a93e96b70e68c73bcd6b320dd026e9c9 | xxd -r -p > sig-ck.dat
/usr/local/opt/openssl/bin/openssl dgst -SHA256 -verify key.pem -signature sig-ck.dat dataToSign.datbash-3.2$ cat > key.pem <<eof
> -----BEGIN PUBLIC KEY-----
> MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE0tMiW1xG3qXjmgWS4griW4uoIzdKEQ/e
> QJrWxxvMM017C1kW1/sCVrfFcW8KnzObNcC5aBMQHiS41YSb3mHXELYrcfw+Dn5Z
> 29xu13ggrs1YV64VRcDX6u+0PQJkHptK
> -----END PUBLIC KEY-----
> EOF
bash-3.2$ echo 3065023100e4ad6c0d5fbd68fe0ee6754e4d8651af07e49d72edcd1dffdf2fc3c79d7690ce91196ffe6d5aa67b85560c1283a0db3502307c8769b38f75e7f90ef64432c2746021bf3198507edbc95eefbf43dde7b83d95efd2e06549efc73742439d0aab057a86 | xxd -r -p > sig-sec.dat
bash-3.2$ /usr/local/opt/openssl/bin/openssl dgst -SHA256 -verify key.pem -signature sig-sec.dat dataToSign.dat
Verified OK
bash-3.2$ echo 30640230702fd094ca99e0736fa0f7cd005a9d68105d2c3be7de4ecf3e8f47531b6692629449e13abc8162f4bdcf0e6a340f59de023001d45750caf603615eed2404b05007ef44dcc10460a66bc346e62299808bd430a93e96b70e68c73bcd6b320dd026e9c9 | xxd -r -p > sig-ck.dat
bash-3.2$ /usr/local/opt/openssl/bin/openssl dgst -SHA256 -verify key.pem -signature sig-ck.dat dataToSign.dat
Verification Failure

You can see in line 22 and 23 that verification works with the signature of the Secutity framework and this also shows that the exported key works. At line 25 and 26 you can see the verification of the CryptoKit signature, which failes.


Did anyone got verification of a crypto kit signature to work with external tools (like openssl)?


Thanks!


Best

Alex

Hi sofacoder, Were you able to figure this out or did you go another route?

The following code worked for me. I ran the code in a playground. The code prints out the public key in pem format (text file friendly). I cut and pasted pem form of the public key into a file named public.key.pem using the terminal and vi. I don't think the use of vi is important here as it's just text. The name isn't important as long as it matches the filename in the openssl command below.

Then you cut / paste / execute the two printed echo commands into a terminal. Then you run the following openssl command:

openssl dgst -SHA256 -verify public.key.pem -signature sig-ck.dat dataToSign.dat

The results from openssl for me are 'Verified OK'

I'm using openssl version OpenSSL 1.1.1k 25 Mar 2021

import CryptoKit

let privateKey = P256.Signing.PrivateKey()
let publicKey = privateKey.publicKey
print(publicKey.pemRepresentation)
let dataToSign = "squeamish ossifrage".data(using: .utf8)!
print("echo \(dataToSign.map { String(format: "%02hhx", $0) }.joined()) | xxd -r -p > dataToSign.dat")
let signed = try! privateKey.signature(for: dataToSign)
print("echo \(signed.derRepresentation.map { String(format: "%02hhx", $0) }.joined()) | xxd -r -p > sig-ck.dat")
CryptoKit odd signature format
 
 
Q