Skip to content
ecdsa.com

Key fingerprint · Swift

How to compute a public key fingerprint in Swift

To compute a public key fingerprint in Swift, take SHA256.hash over the key's derRepresentation — the DER-encoded SubjectPublicKeyInfo that CryptoKit (swift-crypto on Linux) produces for every public key. The digest matches OpenSSL's DER pipe and every other recipe on this site, and swift-certificates supplies the key when it sits inside a certificate.

Tested with swift-crypto 5.0.3 (Swift 6.4.1 on Ubuntu 24.04 (BoringSSL backend)), swift-certificates 1.21.0 (certificate variant), and OpenSSL 3.0.13 (openssl dgst cross-check).

Package.swift — swift-crypto and swift-certificates
// 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"),
.package(url: "https://github.com/apple/swift-certificates.git", exact: "1.21.0"),
],
targets: [
.executableTarget(
name: "fingerprint",
dependencies: [
.product(name: "Crypto", package: "swift-crypto"),
.product(name: "X509", package: "swift-certificates"),
]
),
]
)
SHA-256 over SPKI DER, as hex and as a base64 pin
import Crypto
import Foundation
let publicKey = try P256.Signing.PublicKey(pemRepresentation: String(contentsOfFile: "pub.pem", encoding: .utf8))
let digest = SHA256.hash(data: publicKey.derRepresentation) // SPKI DER, 91 bytes for P-256
print(digest.map { String(format: "%02x", $0) }.joined(separator: ":")) // the layout of openssl dgst -c
print(Data(digest).base64EncodedString()) // pin-sha256 (RFC 7469)
the fingerprint of the key inside a certificate
import X509
// The key inside a certificate: hash its SPKI, not the certificate
let certificate = try Certificate(pemEncoded: String(contentsOfFile: "cert.pem", encoding: .utf8))
if let certificateKey = P256.Signing.PublicKey(certificate.publicKey) {
let keyDigest = SHA256.hash(data: certificateKey.derRepresentation)
print("certificate key: \(keyDigest.map { String(format: "%02x", $0) }.joined())")
}

How it works

  1. P256.Signing.PublicKey(pemRepresentation:) validates the key first, so a damaged file throws instead of yielding a fingerprint of noise.
  2. derRepresentation is the canonical SPKI DER, whatever form the key came from; SHA256.hash(data:) returns a SHA256Digest, which is a byte sequence.
  3. Mapping the bytes to %02x and joining with : gives the openssl dgst -c layout; Data(digest).base64EncodedString() gives the pin-sha256 form.
  4. For a certificate, P256.Signing.PublicKey(certificate.publicKey) converts the swift-certificates key to CryptoKit, and the same two lines hash its SPKI.

Gotchas

  • x963Representation and rawRepresentation are point encodings, not SPKI — hashing them gives a value no other tool reproduces. Only derRepresentation is the pinning input.
  • The certificate fingerprint (SHA-256 over the whole certificate, 54:7A:73:… for the example) differs from its key fingerprint (3d:bb:28:…) and changes on renewal; pin the key.
  • SHA256Digest.description prints SHA256 digest: … with a prefix; format the bytes yourself, as above, rather than relying on description.
  • OpenSSH fingerprints (SHA256:… from ssh-keygen -lf) hash the SSH encoding of the key and never equal the SPKI value.

Related recipes