Skip to content
ecdsa.com

Export public key PEM · Rust

How to export a public key as PEM in Rust

To export a public key as PEM in Rust from an ECDSA private key, load it as a p256::SecretKey, call public_key() to compute the point and to_public_key_pem to write SPKI. The pure-Rust RustCrypto crates read both private key flavours — PKCS#8 and the legacy SEC 1 that OpenSSL's ecparam still writes — and produce output byte-identical to openssl pkey -pubout.

Tested with p256 (RustCrypto) 0.14.0 (Rust 1.99.1, pkcs8 0.11.0, sec1 0.8.1) and OpenSSL 3.5.7 (openssl pkey cross-check).

Cargo.toml — p256 with PEM support
[dependencies]
p256 = { version = "=0.14.0", features = ["pem"] }
src/main.rs — private key PEM (PKCS#8 or SEC 1) in, SPKI PEM out
use p256::SecretKey;
use p256::pkcs8::{DecodePrivateKey, EncodePublicKey, LineEnding};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let pem = std::fs::read_to_string(std::env::args().nth(1).unwrap_or("key.pem".into()))?;
// "BEGIN PRIVATE KEY" is PKCS#8; legacy "BEGIN EC PRIVATE KEY" is SEC 1
let secret = if pem.contains("BEGIN EC PRIVATE KEY") {
SecretKey::from_sec1_pem(&pem)?
} else {
SecretKey::from_pkcs8_pem(&pem)?
};
let public_pem = secret.public_key().to_public_key_pem(LineEnding::LF)?; // SPKI
std::fs::write("pub.pem", &public_pem)?;
print!("{public_pem}");
Ok(())
}

How it works

  1. SecretKey::from_pkcs8_pem reads BEGIN PRIVATE KEY and SecretKey::from_sec1_pem reads BEGIN EC PRIVATE KEY; the PEM label decides which one applies.
  2. public_key() computes Q = d·G on P-256 — EC public keys are always derivable from the private scalar, whether or not the file stored the point.
  3. to_public_key_pem (trait EncodePublicKey) writes the SPKI with the curve OID, at 64 columns with the line ending you choose; to_public_key_der gives the same structure as bytes.
  4. The run compares the output byte for byte with openssl pkey -pubout and checks that the SEC 1 file yields the identical PEM.

Gotchas

  • The key type is per curve: p256::SecretKey rejects a P-384 key with a decoding error. For keys of unknown curve, inspect the algorithm OID first (for example with the pkcs8 crate's PrivateKeyInfo) and dispatch to p384, p521 or k256.
  • from_pkcs8_pem on a SEC 1 file (and vice versa) fails with a PEM label error rather than guessing — check the label, as above, instead of trying both blindly.
  • Encrypted keys (BEGIN ENCRYPTED PRIVATE KEY) need the encryption feature of pkcs8 and from_pkcs8_encrypted_pem; the plain loaders refuse them.
  • ring cannot export a public key as SPKI: public_key().as_ref() is the bare 65-byte point. Wrap it in SPKI with the spki crate or use RustCrypto types for anything that must be read by other tools.

Related recipes