Skip to content
ecdsa.com

PEM ⇄ JWK · Rust

How to convert a PEM public key to a JWK and back in Rust

In Rust, the p256 crate converts a P-256 public key between PEM and JWK in four calls: from_public_key_pem and to_jwk_string for one direction, from_jwk_str and to_public_key_pem for the other. The jwk feature exists in p256 0.13, which this recipe pins; the run checks the RFC 7515 round trip byte for byte.

Tested with p256 0.13.2 (Rust 1.99.1, features jwk and pem).

Cargo.toml — p256 0.13 with the jwk and pem features
[dependencies]
# "jwk" adds the JWK conversions, "pem" the SPKI PEM ones (p256 0.13; 0.14 dropped "jwk")
p256 = { version = "=0.13.2", features = ["jwk", "pem"] }
src/main.rs — PEM to JWK and JWK to PEM
use p256::pkcs8::{DecodePublicKey, EncodePublicKey, LineEnding};
use p256::PublicKey;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// PEM -> JWK
let key = PublicKey::from_public_key_pem(&std::fs::read_to_string("pub.pem")?)?;
let jwk = key.to_jwk_string(); // {"kty":"EC","crv":"P-256","x":"…","y":"…"}
std::fs::write("out.jwk.json", &jwk)?;
println!("{jwk}");
// JWK -> PEM: from_jwk_str also checks that the point is on the curve
let key = PublicKey::from_jwk_str(&std::fs::read_to_string("pub.jwk.json")?)?;
let pem = key.to_public_key_pem(LineEnding::LF)?;
std::fs::write("out.pem", &pem)?;
print!("{pem}");
Ok(())
}

How it works

  1. PublicKey::from_public_key_pem (trait DecodePublicKey) reads BEGIN PUBLIC KEY.
  2. to_jwk_string() returns {"kty":"EC","crv":"P-256","x":…,"y":…}.
  3. PublicKey::from_jwk_str parses the JWK, checks the curve name and rejects points that are not on the curve.
  4. to_public_key_pem(LineEnding::LF) (trait EncodePublicKey) writes the SPKI PEM, identical to the original.

Gotchas

  • p256 0.14 has no jwk feature: with =0.14.0, Cargo stops with package depends on p256 with feature jwk but p256 does not have that feature. JWK support in RustCrypto stayed with the 0.13 line (elliptic-curve 0.13), which is also what jsonwebtoken 11 uses.
  • SecretKey::to_jwk_string() includes d. Publish secret_key.public_key().to_jwk_string() instead.
  • The crate is curve-specific: P-384 keys need the p384 crate, RSA keys the rsa crate. For mixed key sets, jsonwebtoken's Jwk or a JOSE crate covers all types.
  • LineEnding::LF matters for byte-exact comparisons: CRLF writes the same key with Windows line endings.

Related recipes