// swift-tools-version:6.2import 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")]), ]) import Cryptoimport 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 coordinateslet point = privateKey.publicKey.x963Representationfunc 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
P256.Signing.PrivateKey(pemRepresentation:)reads bothBEGIN PRIVATE KEY(PKCS#8) andBEGIN EC PRIVATE KEY(SEC 1); the run checks that both give the same output.publicKey.pemRepresentationwrites SPKI with the curve OID;publicKey.derRepresentationis the same structure as bytes, for fingerprints and binary APIs.x963Representationis the uncompressed point0x04 ‖ x ‖ y, always 65 bytes for P-256, so slicing bytes 1–32 and 33–64 gives JWK coordinates with their leading zeros intact.rawRepresentation(64 bytes,x ‖ y) andcompressedRepresentation(33 bytes) cover the other point encodings protocols ask for.
Gotchas
pemRepresentationhas no trailing newline; add one before writing the file, or byte comparisons with other tools fail.Dataslices keep the parent's indices:point[1..<33]is valid, butpoint[1..<33][0]is not the first byte of the slice. Pass slices straight to APIs or wrap them inData(...)before indexing.SecureEnclave.P256.Signing.PrivateKeyexposespublicKeylike a regular key, so its public PEM exports fine even though the private key never leaves the device.- A JWK
crvmust match the key type: writeP-384orP-521and slice 48 or 66-byte coordinates when the key is not P-256.