Skip to content
ecdsa.com

Key fingerprint · Rust

How to compute a public key fingerprint in Rust

To compute a public key fingerprint in Rust, run sha2's Sha256::digest over the key's DER-encoded SubjectPublicKeyInfo: parse the PEM with p256, re-encode with to_public_key_der, hash. The value matches OpenSSL's DER pipe and every other recipe on this site, and the certificate variant uses x509-parser to hash the key a certificate carries.

Tested with p256 (RustCrypto) 0.14.0 (Rust 1.99.1, sha2 0.11.1, base64 0.23.1), x509-parser 0.18.1 (certificate variant), and OpenSSL 3.5.7 (openssl dgst cross-check).

Cargo.toml — p256, sha2, base64 and x509-parser
[dependencies]
p256 = { version = "=0.14.0", features = ["pem"] }
sha2 = "=0.11.1"
base64 = "=0.23.1"
x509-parser = "=0.18.1"
src/main.rs — SHA-256 over SPKI DER, as hex and as a base64 pin
use base64::Engine;
use base64::engine::general_purpose::STANDARD;
use p256::PublicKey;
use p256::pkcs8::{DecodePublicKey, EncodePublicKey};
use sha2::{Digest, Sha256};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// parse first: a damaged file fails here instead of yielding a fingerprint of noise
let key = PublicKey::from_public_key_pem(&std::fs::read_to_string("pub.pem")?)?;
let spki = key.to_public_key_der()?; // canonical SPKI DER, 91 bytes for P-256
let digest = Sha256::digest(spki.as_bytes());
let hex: Vec<String> = digest.iter().map(|b| format!("{b:02x}")).collect();
println!("{}", hex.join(":")); // the layout of openssl dgst -c
println!("{}", STANDARD.encode(digest)); // pin-sha256 (RFC 7469)
Ok(())
}
the fingerprint of the key inside a certificate
// The key inside a certificate: hash its SubjectPublicKeyInfo, not the whole certificate
let data = std::fs::read("cert.pem")?;
let (_, pem) = x509_parser::pem::parse_x509_pem(&data)?;
let cert = pem.parse_x509()?;
let key_digest = Sha256::digest(cert.public_key().raw); // raw = the SPKI DER as it appears in the certificate
println!("certificate key: {}", key_digest.iter().map(|b| format!("{b:02x}")).collect::<String>());

How it works

  1. PublicKey::from_public_key_pem validates the key (a point on P-256) before anything is hashed.
  2. to_public_key_der re-encodes the canonical SPKI; hashing those 91 bytes gives the same digest as openssl pkey -pubin -outform DER | openssl dgst -sha256, which the run compares.
  3. The colon-separated hex is presentation only; STANDARD.encode(digest) from the base64 crate gives the pin-sha256 form.
  4. In a certificate, cert.public_key().raw is the SPKI exactly as encoded there, so it can be hashed without re-encoding the key.

Gotchas

  • The certificate fingerprint (SHA-256 of the whole DER certificate, 54:7a:73:… for the example) is not the key fingerprint (3d:bb:28:…). Only the key fingerprint survives renewal with the same key.
  • cert.public_key().raw is the SPKI as the CA encoded it. Certificates that carry explicit curve parameters instead of a named-curve OID hash differently from the same key re-encoded by to_public_key_der; re-encode when comparing keys from different sources.
  • Hashing std::fs::read("pub.pem") hashes the PEM text — line endings included — and matches no other tool.
  • OpenSSH fingerprints (SHA256:… from ssh-keygen -lf) hash the SSH wire encoding of the key and never equal the SPKI fingerprint.

Related recipes