Skip to content
ecdsa.com

Export public key PEM · Swift

How to export a public key as PEM in Swift

To export a public key as PEM in Swift, one property is enough: privateKey.publicKey.pemRepresentation returns the SPKI BEGIN PUBLIC KEY block, byte-identical to openssl pkey -pubout. This works with CryptoKit on Apple platforms and swift-crypto on Linux, and the same key object gives the coordinates for a JWK.

Tested with swift-crypto 5.0.3 (Swift 6.4.1 on Ubuntu 24.04 (BoringSSL backend)) and OpenSSL 3.0.13 (openssl pkey cross-check).

Package.swift — swift-crypto for Linux (Apple platforms ship CryptoKit)
// swift-tools-version:6.2
import PackageDescription
let package = Package(
name: "howto",
platforms: [.macOS(.v15)],
dependencies: [
.package(url: "https://github.com/apple/swift-crypto.git", exact: "5.0.3"),
],
targets: [
.executableTarget(name: "export", dependencies: [.product(name: "Crypto", package: "swift-crypto")]),
]
)
Sources/export/main.swift — private key PEM in, SPKI PEM and JWK out
import Crypto
import Foundation
let input = CommandLine.arguments.count > 1 ? CommandLine.arguments[1] : "key.pem"
// PKCS#8 ("BEGIN PRIVATE KEY") or SEC 1 ("BEGIN EC PRIVATE KEY")
let privateKey = try P256.Signing.PrivateKey(pemRepresentation: String(contentsOfFile: input, encoding: .utf8))
let publicPEM = privateKey.publicKey.pemRepresentation + "\n" // SPKI, "BEGIN PUBLIC KEY"
try Data(publicPEM.utf8).write(to: URL(fileURLWithPath: "pub.pem"))
print(publicPEM, terminator: "")
// The same key as a JWK: x963Representation is 0x04 ‖ x ‖ y with fixed 32-byte coordinates
let point = privateKey.publicKey.x963Representation
func base64URL(_ data: Data) -> String {
data.base64EncodedString().replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_").replacingOccurrences(of: "=", with: "")
}
print(#"{"kty":"EC","crv":"P-256","x":"\#(base64URL(point[1..<33]))","y":"\#(base64URL(point[33..<65]))"}"#)

How it works

  1. P256.Signing.PrivateKey(pemRepresentation:) reads both BEGIN PRIVATE KEY (PKCS#8) and BEGIN EC PRIVATE KEY (SEC 1); the run checks that both give the same output.
  2. publicKey.pemRepresentation writes SPKI with the curve OID; publicKey.derRepresentation is the same structure as bytes, for fingerprints and binary APIs.
  3. x963Representation is the uncompressed point 0x04 ‖ x ‖ y, always 65 bytes for P-256, so slicing bytes 1–32 and 33–64 gives JWK coordinates with their leading zeros intact.
  4. rawRepresentation (64 bytes, x ‖ y) and compressedRepresentation (33 bytes) cover the other point encodings protocols ask for.

Gotchas

  • pemRepresentation has no trailing newline; add one before writing the file, or byte comparisons with other tools fail.
  • Data slices keep the parent's indices: point[1..<33] is valid, but point[1..<33][0] is not the first byte of the slice. Pass slices straight to APIs or wrap them in Data(...) before indexing.
  • SecureEnclave.P256.Signing.PrivateKey exposes publicKey like a regular key, so its public PEM exports fine even though the private key never leaves the device.
  • A JWK crv must match the key type: write P-384 or P-521 and slice 48 or 66-byte coordinates when the key is not P-256.

Related recipes