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?

I have encountered a similar issue before. Hope my post can give some help here.

Source code: Blaming Forumate/Shared/ViewModel/APIKeyManager.swift at 943676a4751fc4e5d64990cbb224c56393cd9275 · Kyle-Ye/Forumate · GitHub

Thanks but unfortunately, changing the header didn't work. I sent eskimo1 an email, fingers crossed maybe he can help with a definitive answer.

1 Like

Good day! I think that the problem is in the different key formats. First key is X.509 SubjectPublicKeyInfo formatted key. It means that this key contains OBJECT IDENTIFIER and BIT STRING in its structure. You can use dumpasn1 util to see it.

SEQUENCE {
  SEQUENCE {
    OBJECT IDENTIFIER rsaEncryption (1 2 840 113549 1 1 1)
    NULL
    }
  BIT STRING, encapsulates {
    SEQUENCE {
      INTEGER
        00 AD E8 24 73 F4 14 37 F3 9B 9E 2B 57 28 1C 87
        BE DC B7 DF 38 90 8C 6E 3C E6 57 A0 78 F7 75 C2
        A2 FE F5 6A 6E F6 00 4F 28 DB DE 68 86 6C 44 93
        B6 B1 63 FD 14 12 6B BF 1F D2 EA 31 9B 21 7E D1
        33 3C BA 48 F5 DD 79 DF B3 B8 FF 12 F1 21 9A 4B
        C1 8A 86 71 69 4A 66 66 6C 8F 7E 3C 70 BF AD 29
        22 06 F3 E4 C0 E6 80 AE E2 4B 8F B7 99 7E 94 03
        9F D3 47 97 7C 99 48 23 53 E8 38 AE 4F 0A 6F 83
        2E D1 49 57 8C 80 74 B6 DA 2F D0 38 8D 7B 03 70
        21 1B 75 F2 30 3C FA 8F AE DD DA 63 AB EB 16 4F
        C2 8E 11 4B 7E CF 0B E8 FF B5 77 2E F4 B2 7B 4A
        E0 4C 12 25 0C 70 8D 03 29 A0 E1 53 24 EC 13 D9
        EE 19 BF 10 B3 4A 8C 3F 89 A3 61 51 DE AC 87 07
        94 F4 63 71 EC 2E E2 6F 5B 98 81 E1 89 5C 34 79
        6C 76 EF 3B 90 62 79 E6 DB A4 9A 2F 26 C5 D0 10
        E1 0E DE D9 10 8E 16 FB B7 F7 A8 F7 C7 E5 02 07
        98 8F 36 08 95 E7 E2 37 96 0D 36 75 9E FB 0E 72
        B1 1D 9B BC 03 F9 49 05 D8 81 DD 05 B4 2A D6 41
        E9 AC 01 76 95 0A 0F D8 DF D5 BD 12 1F 35 2F 28
        17 6C D2 98 C1 A8 09 64 77 6E 47 37 BA CE AC 59
        5E 68 9D 7F 72 D6 89 C5 06 41 29 3E 59 3E DD 26
        F5 24 C9 11 A7 5A A3 4C 40 1F 46 A1 99 B5 A7 3A
        51 6E 86 3B 9E 7D 72 A7 12 05 78 59 ED 3E 51 78
        15 0B 03 8F 8D D0 2F 05 B2 3E 7B 4A 1C 4B 73 05
        12 FC C6 EA E0 50 13 7C 43 93 74 B3 CA 74 E7 8E
        1F 01 08 D0 30 D4 5B 71 36 B4 07 BA C1 30 30 5C
        48 B7 82 3B 98 A6 7D 60 8A A2 A3 29 82 CC BA BD
        83 04 1B A2 83 03 41 A1 D6 05 F1 1B C2 B6 F0 A8
        7C 86 3B 46 A8 48 2A 88 DC 76 9A 76 BF 1F 6A A5
        3D 19 8F EB 38 F3 64 DE C8 2B 0D 0A 28 FF F7 DB
        E2 15 42 D4 22 D0 27 5D E1 79 FE 18 E7 70 88 AD
        4E E6 D9 8B 3A C6 DD 27 51 6E FF BC 64 F5 33 43
        4F
      INTEGER 65537
      }
    }
  }

The second one is old-fashioned PKCS#1 key. It means that it contains only modulus (first INTEGER) and publicExponent (second INTEGER).

SEQUENCE {
  INTEGER
    00 AD E8 24 73 F4 14 37 F3 9B 9E 2B 57 28 1C 87
    BE DC B7 DF 38 90 8C 6E 3C E6 57 A0 78 F7 75 C2
    A2 FE F5 6A 6E F6 00 4F 28 DB DE 68 86 6C 44 93
    B6 B1 63 FD 14 12 6B BF 1F D2 EA 31 9B 21 7E D1
    33 3C BA 48 F5 DD 79 DF B3 B8 FF 12 F1 21 9A 4B
    C1 8A 86 71 69 4A 66 66 6C 8F 7E 3C 70 BF AD 29
    22 06 F3 E4 C0 E6 80 AE E2 4B 8F B7 99 7E 94 03
    9F D3 47 97 7C 99 48 23 53 E8 38 AE 4F 0A 6F 83
    2E D1 49 57 8C 80 74 B6 DA 2F D0 38 8D 7B 03 70
    21 1B 75 F2 30 3C FA 8F AE DD DA 63 AB EB 16 4F
    C2 8E 11 4B 7E CF 0B E8 FF B5 77 2E F4 B2 7B 4A
    E0 4C 12 25 0C 70 8D 03 29 A0 E1 53 24 EC 13 D9
    EE 19 BF 10 B3 4A 8C 3F 89 A3 61 51 DE AC 87 07
    94 F4 63 71 EC 2E E2 6F 5B 98 81 E1 89 5C 34 79
    6C 76 EF 3B 90 62 79 E6 DB A4 9A 2F 26 C5 D0 10
    E1 0E DE D9 10 8E 16 FB B7 F7 A8 F7 C7 E5 02 07
    98 8F 36 08 95 E7 E2 37 96 0D 36 75 9E FB 0E 72
    B1 1D 9B BC 03 F9 49 05 D8 81 DD 05 B4 2A D6 41
    E9 AC 01 76 95 0A 0F D8 DF D5 BD 12 1F 35 2F 28
    17 6C D2 98 C1 A8 09 64 77 6E 47 37 BA CE AC 59
    5E 68 9D 7F 72 D6 89 C5 06 41 29 3E 59 3E DD 26
    F5 24 C9 11 A7 5A A3 4C 40 1F 46 A1 99 B5 A7 3A
    51 6E 86 3B 9E 7D 72 A7 12 05 78 59 ED 3E 51 78
    15 0B 03 8F 8D D0 2F 05 B2 3E 7B 4A 1C 4B 73 05
    12 FC C6 EA E0 50 13 7C 43 93 74 B3 CA 74 E7 8E
    1F 01 08 D0 30 D4 5B 71 36 B4 07 BA C1 30 30 5C
    48 B7 82 3B 98 A6 7D 60 8A A2 A3 29 82 CC BA BD
    83 04 1B A2 83 03 41 A1 D6 05 F1 1B C2 B6 F0 A8
    7C 86 3B 46 A8 48 2A 88 DC 76 9A 76 BF 1F 6A A5
    3D 19 8F EB 38 F3 64 DE C8 2B 0D 0A 28 FF F7 DB
    E2 15 42 D4 22 D0 27 5D E1 79 FE 18 E7 70 88 AD
    4E E6 D9 8B 3A C6 DD 27 51 6E FF BC 64 F5 33 43
    4F
  INTEGER 65537
  }

So, both keys contain the same payload, but first one contains additional metadata.

If we talk about PEM file, you should use -----BEGIN/END PUBLIC KEY----- prefix/suffix with first one and -----BEGIN/END RSA PUBLIC KEY----- with second one because first one already contains algorithm identifier as you can see.

Answer to your question -> You should manually add the required data to the key. But I think this is not about the Swift language forum. This issue has already been discussed on the Apple Developers forum.

1 Like

You are right, that's why I also asked the question on Apple developer forums: How can I get the public key from … | Apple Developer Forums