// 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"), .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"), ] ), ]) import Cryptoimport 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 -cprint(Data(digest).base64EncodedString()) // pin-sha256 (RFC 7469) import X509 // The key inside a certificate: hash its SPKI, not the certificatelet 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
P256.Signing.PublicKey(pemRepresentation:)validates the key first, so a damaged file throws instead of yielding a fingerprint of noise.derRepresentationis the canonical SPKI DER, whatever form the key came from;SHA256.hash(data:)returns aSHA256Digest, which is a byte sequence.- Mapping the bytes to
%02xand joining with:gives theopenssl dgst -clayout;Data(digest).base64EncodedString()gives thepin-sha256form. - For a certificate,
P256.Signing.PublicKey(certificate.publicKey)converts the swift-certificates key to CryptoKit, and the same two lines hash its SPKI.
Gotchas
x963RepresentationandrawRepresentationare point encodings, not SPKI — hashing them gives a value no other tool reproduces. OnlyderRepresentationis 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.descriptionprintsSHA256 digest: …with a prefix; format the bytes yourself, as above, rather than relying ondescription.- OpenSSH fingerprints (
SHA256:…fromssh-keygen -lf) hash the SSH encoding of the key and never equal the SPKI value.