Skip to content
ecdsa.com

Public key from a certificate · Swift

How to extract the public key from an X.509 certificate in Swift

To extract the public key from an X.509 certificate in Swift, parse it with swift-certificates and serialize certificate.publicKey: serializeAsPEM() gives the SubjectPublicKeyInfo as a "BEGIN PUBLIC KEY" block, and DER.Serializer the DER. This recipe also computes the key's SHA-256 fingerprint and base64 pin, and checks a private key against the certificate.

Tested with swift-certificates 1.21.0 (Swift 6.4.1 on Ubuntu 24.04, swift-asn1 1.7.3) and swift-crypto 5.0.3 (SHA-256 and the P-256 private key).

Package.swift — swift-certificates and swift-crypto
// swift-tools-version:6.2
import PackageDescription
let package = Package(
name: "howto",
platforms: [.macOS(.v15)],
dependencies: [
.package(url: "https://github.com/apple/swift-certificates.git", exact: "1.21.0"),
.package(url: "https://github.com/apple/swift-crypto.git", exact: "5.0.3"),
],
targets: [
.executableTarget(
name: "extract",
dependencies: [
.product(name: "X509", package: "swift-certificates"),
.product(name: "Crypto", package: "swift-crypto"),
]
),
]
)
Sources/extract/main.swift — SPKI PEM, fingerprint, pin and the key-pair check
import Crypto
import Foundation
import SwiftASN1
import X509
let certificate = try Certificate(pemEncoded: String(contentsOfFile: "cert.pem", encoding: .utf8))
let publicKey = certificate.publicKey // Certificate.PublicKey: P-256/384/521, RSA or Ed25519
// The SPKI: Certificate.PublicKey serializes as SubjectPublicKeyInfo ("PUBLIC KEY")
let spkiPEM = try publicKey.serializeAsPEM().pemString
print(spkiPEM)
var serializer = DER.Serializer()
try serializer.serialize(publicKey)
let spkiDER = serializer.serializedBytes
let digest = SHA256.hash(data: spkiDER)
print("SPKI SHA-256: \(digest.map { String(format: "%02x", $0) }.joined(separator: ":"))")
print("SPKI pin (base64): \(Data(digest).base64EncodedString())")
// Not the SPKI, despite the name: the BIT STRING inside it — here the bare 65-byte EC point
print("subjectPublicKeyInfoBytes: \(publicKey.subjectPublicKeyInfoBytes.count) bytes")
// Does key.pem belong to this certificate?
let privateKey = try P256.Signing.PrivateKey(pemRepresentation: String(contentsOfFile: "key.pem", encoding: .utf8))
print("key.pem matches: \(Certificate.PublicKey(privateKey.publicKey) == publicKey)")

How it works

  1. certificate.publicKey is a Certificate.PublicKey; serializeAsPEM() writes it as a SubjectPublicKeyInfo PEM.
  2. DER.Serializer produces the SPKI DER; SHA-256 over it is the key fingerprint (45:ca:a7:e3:…:7b:5f), and its base64 (Rcqn43J0…e18=) the SPKI pin.
  3. Certificate.PublicKey(privateKey.publicKey) == certificate.publicKey compares keys by value: true means key.pem belongs to the certificate.
  4. P256.Signing.PublicKey(certificate.publicKey) converts the key to a CryptoKit type for signature verification (and returns nil for other algorithms).

Gotchas

  • subjectPublicKeyInfoBytes is not the SubjectPublicKeyInfo despite its name: it returns the BIT STRING inside it, the bare 65-byte EC point. Serialize the key itself for fingerprints and pins.
  • pemString ends without a trailing newline; add one when the file must match OpenSSL's output byte for byte.
  • The certificate's own hash (over the whole DER) changes at every renewal; the SPKI hash stays the same while the key does.

Related recipes