How can I get the public key from SecCertificate? And a few other things

Hi, I'm trying to achieve the following OpenSSL workflow in Swift.

I have this intermediate certificate from Let's encrypt and I want to extract the public key from it and then hash it with SHA-256 and finally encide it in base64.

The OpenSSL commands that achieve this look like this:

 openssl x509 -in isrgrootx1.pem -pubkey -noout > publickey.pem
 openssl rsa -pubin -in publickey.pem -outform der | openssl dgst -sha256 -binary | openssl enc -base64

I've tried Security, CommonCrypto, CryptoKit frameworks with no success. I was able to get the public key out of the certificate but its PEM representation seems to slightly differ from what I get with OpenSSL. At the beginning of the public jet, the OpenSSL version has a string that is not present on what I get with Swift but the rest is the same.

This is the Swift code to use:

import Foundation
import Security
import CommonCrypto

// Step 1: Extract public key from the certificate
func extractPublicKey(from certificate: SecCertificate) -> SecKey? {
    // Extract public key from the certificate
    var publicKey: SecKey?
    if let publicKeyRef = SecCertificateCopyKey(certificate) {
        publicKey = publicKeyRef
    }
    
    return publicKey
}

// Step 2: Calculate SHA-256 hash of the public key
func calculateSHA256(of data: Data) -> Data {
    var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
    data.withUnsafeBytes {
        _ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash)
    }
    return Data(hash)
}

// Step 3: Encode data as base64
func base64EncodedString(from data: Data) -> String {
    return data.base64EncodedString()
}

// Step 4: Main function to perform all steps
func processCertificate(certificate: SecCertificate) {
    // Step 1: Extract public key
    guard let publicKey = extractPublicKey(from: certificate) else {
        return
    }
    
    // Step 2: Export public key as data
    guard let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, nil) as Data? else {
        print("Failed to export public key data")
        return
    }
    
    // Step 3: Calculate SHA-256 hash of the public key
    let sha256Hash = calculateSHA256(of: publicKeyData)
    
    // Step 4: Encode SHA-256 hash as base64
    let base64EncodedHash = base64EncodedString(from: sha256Hash)
    
    print("SHA-256 hash of public key (base64 encoded): \(base64EncodedHash)")
}

This is the Public Key I get with OpenSSL:

-----BEGIN PUBLIC KEY-----

MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAregkc/QUN/ObnitXKByHvty33ziQjG485legePd1wqL+9Wpu9gBPKNveaIZsRJO2sWP9FBJrvx/S6jGbIX7RMzy6SPXded+zuP8S8SGaS8GKhnFpSmZmbI9+PHC/rSkiBvPkwOaAruJLj7eZfpQDn9NHl3yZSCNT6DiuTwpvgy7RSVeMgHS22i/QOI17A3AhG3XyMDz6j67d2mOr6xZPwo4RS37PC+j/tXcu9LJ7SuBMEiUMcI0DKaDhUyTsE9nuGb8Qs0qMP4mjYVHerIcHlPRjcewu4m9bmIHhiVw0eWx27zuQYnnm26SaLybF0BDhDt7ZEI4W+7f3qPfH5QIHmI82CJXn4jeWDTZ1nvsOcrEdm7wD+UkF2IHdBbQq1kHprAF2lQoP2N/VvRIfNS8oF2zSmMGoCWR3bkc3us6sWV5onX9y1onFBkEpPlk+3Sb1JMkRp1qjTEAfRqGZtac6UW6GO559cqcSBXhZ7T5ReBULA4+N0C8Fsj57ShxLcwUS/Mbq4FATfEOTdLPKdOeOHwEI0DDUW3E2tAe6wTAwXEi3gjuYpn1giqKjKYLMur2DBBuigwNBodYF8RvCtvCofIY7RqhIKojcdpp2vx9qpT0Zj+s482TeyCsNCij/99viFULUItAnXeF5/hjncIitTubZizrG3SdRbv+8ZPUzQ08CAwEAAQ==

-----END PUBLIC KEY-----

and this is what I get with Swift:

-----BEGIN PUBLIC KEY-----

MIICCgKCAgEAregkc/QUN/ObnitXKByHvty33ziQjG485legePd1wqL+9Wpu9gBPKNveaIZsRJO2sWP9FBJrvx/S6jGbIX7RMzy6SPXded+zuP8S8SGaS8GKhnFpSmZmbI9+PHC/rSkiBvPkwOaAruJLj7eZfpQDn9NHl3yZSCNT6DiuTwpvgy7RSVeMgHS22i/QOI17A3AhG3XyMDz6j67d2mOr6xZPwo4RS37PC+j/tXcu9LJ7SuBMEiUMcI0DKaDhUyTsE9nuGb8Qs0qMP4mjYVHerIcHlPRjcewu4m9bmIHhiVw0eWx27zuQYnnm26SaLybF0BDhDt7ZEI4W+7f3qPfH5QIHmI82CJXn4jeWDTZ1nvsOcrEdm7wD+UkF2IHdBbQq1kHprAF2lQoP2N/VvRIfNS8oF2zSmMGoCWR3bkc3us6sWV5onX9y1onFBkEpPlk+3Sb1JMkRp1qjTEAfRqGZtac6UW6GO559cqcSBXhZ7T5ReBULA4+N0C8Fsj57ShxLcwUS/Mbq4FATfEOTdLPKdOeOHwEI0DDUW3E2tAe6wTAwXEi3gjuYpn1giqKjKYLMur2DBBuigwNBodYF8RvCtvCofIY7RqhIKojcdpp2vx9qpT0Zj+s482TeyCsNCij/99viFULUItAnXeF5/hjncIitTubZizrG3SdRbv+8ZPUzQ08CAwEAAQ==

-----END PUBLIC KEY-----

Interestingly, if I use the Swift version of the Public Key I get and then run the second command I still get the correct final result. Unfortunately in Swift I don't get the correct final result.

I suspect it must be something about headers since I was able to get the correct output on OpenSSL with the public key I got using the Swift.

Any ideas?

Replies

The difference here is whether your RSAPublicKey structure is wrapped in a SubjectPublicKeyInfo or not. I explain this in gory detail in On Cryptographic Key Formats.

If you were using EC keys, you could use Apple CryptoKit to convert between the various formats. For example, for a P-256 you have all these options.

IMPORTANT If you have any control over this protocol, I recommend that you switch from RSA to EC. Search the ’net for many explanations as to why that’s a good idea.

If you were only exporting a PEM then you could change the PEM labels from PUBLIC KEY to RSA PUBLIC KEY, and OpenSSL-based code is likely to do the right thing. However, in your case you need a hash of the key.

That means that you have to add the SubjectPublicKeyInfo wrapper. This is a pain because RSAPublicKey structures can be of different lengths, even if you fix the key size, and thus can’t just add a fixed prefix. You need to understand a bit about ASN.1 DER encoding.

iOS has no ASN.1 API [1], so you might want to grab a third-party library for this. I usually reach for SwiftASN1. Its parent library, Swift Certificates, probably even has code for this.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] macOS has SecAsn1Coder but, frankly, that’s more trouble than its worth. Oh, and it’s been deprecated for a while now.

Thanks for the reply, lots of to digest. Once I figure out the code solution, I will post it here so others can benefit too.

Appreciate it very much!