Skip to content
ecdsa.com

PEM ⇄ DER · Swift

How to convert a certificate and a private key between PEM and DER in Swift

To convert a certificate between PEM and DER in Swift, use PEMDocument from swift-asn1 (part of swift-certificates): it holds the label and the DER bytes of a block and writes the PEM back. Private keys convert through CryptoKit's key types — pemRepresentation and derRepresentation, both PKCS#8 — via swift-crypto on Linux. This recipe converts both ways and checks the round trip.

Tested with swift-certificates 1.21.0 (Swift 6.4.1 on Ubuntu 24.04, swift-asn1 PEMDocument) and swift-crypto 5.0.3 (P256 key in PEM and DER).

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: "convert",
dependencies: [
.product(name: "X509", package: "swift-certificates"),
.product(name: "Crypto", package: "swift-crypto"),
]
),
]
)
Sources/convert/main.swift — certificate and P-256 key, PEM ⇄ DER
import Crypto
import Foundation
import SwiftASN1
import X509
// Any PEM block: PEMDocument holds the label (discriminator) and the DER bytes
let certPEM = try String(contentsOfFile: "cert.pem", encoding: .utf8)
let document = try PEMDocument(pemString: certPEM)
try Data(document.derBytes).write(to: URL(fileURLWithPath: "cert.der"))
let certAgain = PEMDocument(type: "CERTIFICATE", derBytes: document.derBytes).pemString
// Parsing the DER proves it is a certificate
let certificate = try Certificate(derEncoded: document.derBytes)
// Private key: CryptoKit types read and write PKCS#8 in both encodings
let keyPEM = try String(contentsOfFile: "key.pem", encoding: .utf8)
let key = try P256.Signing.PrivateKey(pemRepresentation: keyPEM)
let keyDER = key.derRepresentation
let keyAgain = try P256.Signing.PrivateKey(derRepresentation: keyDER).pemRepresentation
print("\(document.discriminator) DER: \(document.derBytes.count) bytes, sha256 \(SHA256.hash(data: document.derBytes).map { String(format: "%02x", $0) }.joined())")
print("cert PEM again identical: \(certAgain + "\n" == certPEM), subject \(certificate.subject)")
print("key DER: \(keyDER.count) bytes, sha256 \(SHA256.hash(data: keyDER).map { String(format: "%02x", $0) }.joined())")
print("key PEM again identical: \(keyAgain + "\n" == keyPEM)")

How it works

  1. PEMDocument(pemString:) parses one block; discriminator is the label (CERTIFICATE) and derBytes the DER (563 bytes here).
  2. PEMDocument(type:derBytes:).pemString builds the block again; Certificate(derEncoded:) proves the bytes are a certificate.
  3. P256.Signing.PrivateKey(pemRepresentation:) reads a PKCS#8 PEM key; derRepresentation is its PKCS#8 DER (138 bytes) and PrivateKey(derRepresentation:) reads it back.
  4. The run checks the certificate's DER with OpenSSL (openssl x509 -inform DER).

Gotchas

  • pemString and pemRepresentation end without a trailing newline; the run appends \n to compare with files written by OpenSSL.
  • The CryptoKit key types are per curve (P256, P384, Curve25519 …): you need to know the algorithm before you can read a key's DER, because nothing in the call says it.
  • PEMDocument(pemString:) takes one block; use PEMDocument.parseMultiple(pemString:) for a bundle.

Related recipes