Asymmetric Keys
Key Visibility Typestate
All asymmetric key material is held in Pkey<T>, where T is a zero-sized marker:
Private → HasPrivate (also HasPublic + HasParams)
Public → HasPublic (also HasParams)
Params → HasParams (DH/DSA parameters only; no key material)
A Pkey<Private> can be used anywhere a Pkey<Public> is accepted via the trait
hierarchy — no explicit conversion is needed for operations that only require the
public component. However, you cannot extract a Pkey<Private> from a Pkey<Public>.
Pkey<T> — Key Container
#![allow(unused)]
fn main() {
pub struct Pkey<T> { /* EVP_PKEY* */ }
impl<T: HasParams> Pkey<T> {
/// Key size in bits (e.g. 2048 for RSA-2048, 256 for P-256).
pub fn bits(&self) -> u32;
/// Security level in bits.
pub fn security_bits(&self) -> u32;
/// Maximum output size in bytes (signature length for signing keys, ciphertext for encryption).
/// Use this to size output buffers before signing. Returns 0 if not applicable.
pub fn max_output_size(&self) -> usize;
/// Check algorithm type by name (e.g. c"RSA", c"EC", c"ED25519").
pub fn is_a(&self, name: &CStr) -> bool;
/// Compare public components of two keys for equality.
pub fn public_eq<U: HasPublic>(&self, other: &Pkey<U>) -> bool
where T: HasPublic;
/// Export public key as DER (SubjectPublicKeyInfo).
pub fn public_key_to_der(&self) -> Result<Vec<u8>, ErrorStack>
where T: HasPublic;
/// Fill values into a pre-prepared mutable query array.
/// Wraps EVP_PKEY_get_params. Works for all key roles.
pub fn get_params(&self, params: &mut Params<'_>) -> Result<(), ErrorStack>;
/// Returns the default digest algorithm name for this key, or None if the
/// key type mandates no external digest (e.g. Ed25519, ML-DSA, SLH-DSA).
///
/// Wraps EVP_PKEY_get_default_digest_name. Both return-value 1 (optional digest)
/// and 2 (mandatory digest) are treated as success. "none" or empty string
/// maps to None.
///
/// Requires T: HasPublic (i.e. Pkey<Public> or Pkey<Private>).
pub fn default_digest_name(&self) -> Result<Option<String>, ErrorStack>
where T: HasPublic;
}
}
Pkey<T> is Clone (via EVP_PKEY_up_ref) and Send + Sync.
Key Loading
Private Key
#![allow(unused)]
fn main() {
impl Pkey<Private> {
/// Load from PEM (PKCS#8 or traditional format).
pub fn from_pem(pem: &[u8]) -> Result<Self, ErrorStack>;
/// Load from PEM using an explicit library context (FIPS isolation).
pub fn from_pem_in(ctx: &Arc<LibCtx>, pem: &[u8]) -> Result<Self, ErrorStack>;
/// Load from passphrase-encrypted PEM.
/// Passes `passphrase` to OpenSSL via a `pem_password_cb`.
/// Use `from_pem` for unencrypted PEM.
pub fn from_pem_passphrase(pem: &[u8], passphrase: &[u8]) -> Result<Self, ErrorStack>;
/// Load from DER (PKCS#8 or traditional, auto-detected).
/// Uses MemBioBuf — reads directly from caller's slice without copying.
pub fn from_der(der: &[u8]) -> Result<Self, ErrorStack>;
/// Load from DER bytes using an explicit library context.
/// Format is auto-detected (PKCS#8, traditional, etc.) via d2i_AutoPrivateKey_ex.
/// Use when the key will be used with EVP operations in an isolated (e.g. FIPS) context.
pub fn from_der_in(ctx: &Arc<LibCtx>, der: &[u8]) -> Result<Self, ErrorStack>;
/// Encode this private key to legacy raw-key DER (not PKCS#8).
/// Wraps i2d_PrivateKey. Output is the algorithm-native structure
/// (e.g. RSAPrivateKey for RSA, ECPrivateKey for EC).
/// For new code prefer to_pkcs8_der() which is algorithm-agnostic.
pub fn to_der_legacy(&self) -> Result<Vec<u8>, ErrorStack>;
/// Serialize to PEM (PKCS#8 `BEGIN PRIVATE KEY` format).
pub fn to_pem(&self) -> Result<Vec<u8>, ErrorStack>;
/// Serialize to passphrase-encrypted PKCS#8 PEM (`BEGIN ENCRYPTED PRIVATE KEY`).
/// `cipher` controls the wrapping algorithm (e.g. `CipherAlg::fetch(c"AES-256-CBC", None)`).
pub fn to_pem_encrypted(&self, cipher: &CipherAlg, passphrase: &[u8]) -> Result<Vec<u8>, ErrorStack>;
/// Serialize to unencrypted PKCS#8 DER (`PrivateKeyInfo` / RFC 5958).
/// Avoids the base64 round-trip of `to_pem`. Use `to_pem_encrypted` for an encrypted form.
pub fn to_pkcs8_der(&self) -> Result<Vec<u8>, ErrorStack>;
}
}
Public Key
#![allow(unused)]
fn main() {
impl Pkey<Public> {
/// Load from PEM (SubjectPublicKeyInfo or RSA public key format).
pub fn from_pem(pem: &[u8]) -> Result<Self, ErrorStack>;
/// Load from PEM using an explicit library context (FIPS isolation).
///
/// Uses PEM_read_bio_PUBKEY_ex so the key's internal algorithm fetch
/// uses ctx's provider set. Necessary when the public key is later used
/// for EVP operations inside an isolated (e.g. FIPS) context.
pub fn from_pem_in(ctx: &Arc<LibCtx>, pem: &[u8]) -> Result<Self, ErrorStack>;
/// Load from DER (SubjectPublicKeyInfo).
pub fn from_der(der: &[u8]) -> Result<Self, ErrorStack>;
/// Load SubjectPublicKeyInfo DER using an explicit library context.
/// Wraps d2i_PUBKEY_ex so the key's algorithm fetch is bound to ctx's
/// provider set. Use when the key will be used with EVP operations in
/// an isolated (e.g. FIPS) context.
pub fn from_der_in(ctx: &Arc<LibCtx>, der: &[u8]) -> Result<Self, ErrorStack>;
/// Serialize to PEM.
pub fn to_pem(&self) -> Result<Vec<u8>, ErrorStack>;
}
}
Both from_der variants take &[u8] (not a cursor). The full slice is consumed.
Key Import and Export via OSSL_PARAM
These methods load or dump raw key material through OSSL_PARAM arrays. They
are the basis for interoperability with external key stores, HSMs, and protocol
implementations that work with raw key components (e.g. RSA modulus/exponent,
EC coordinates, raw Ed25519 seed bytes).
Import
#![allow(unused)]
fn main() {
impl Pkey<Private> {
/// Import a private key pair from an OSSL_PARAM array.
///
/// Wraps EVP_PKEY_fromdata with KEYPAIR selection.
/// Pass ctx = None to use the global default library context.
pub fn from_params(
ctx: Option<&Arc<LibCtx>>,
pkey_type: &CStr,
params: &Params<'_>,
) -> Result<Self, ErrorStack>;
}
impl Pkey<Public> {
/// Import a public key from an OSSL_PARAM array.
///
/// Wraps EVP_PKEY_fromdata with PUBLIC_KEY selection.
pub fn from_params(
ctx: Option<&Arc<LibCtx>>,
pkey_type: &CStr,
params: &Params<'_>,
) -> Result<Self, ErrorStack>;
}
}
Export
#![allow(unused)]
fn main() {
impl Pkey<Private> {
/// Export all key parameters (private + public) as an owned OSSL_PARAM array.
///
/// Wraps EVP_PKEY_todata with KEYPAIR selection.
/// The returned Params<'static> is owned by the caller.
pub fn export(&self) -> Result<Params<'static>, ErrorStack>;
}
impl Pkey<Public> {
/// Export the public key parameters as an owned OSSL_PARAM array.
///
/// Wraps EVP_PKEY_todata with PUBLIC_KEY selection.
pub fn export(&self) -> Result<Params<'static>, ErrorStack>;
}
}
Targeted Parameter Query
#![allow(unused)]
fn main() {
impl<T: HasParams> Pkey<T> {
/// Fill values into a pre-prepared mutable query array.
///
/// Wraps EVP_PKEY_get_params. The array must already contain the parameter
/// names of interest (built via ParamBuilder); OpenSSL writes the values
/// in place.
pub fn get_params(&self, params: &mut Params<'_>) -> Result<(), ErrorStack>;
}
}
get_params is more efficient than export when only a subset of parameters
is needed, because OpenSSL only copies the requested fields.
Key Generation
#![allow(unused)]
fn main() {
pub struct KeygenCtx { /* EVP_PKEY_CTX in keygen mode */ }
impl KeygenCtx {
/// Create a keygen context for the named algorithm.
/// Calls EVP_PKEY_CTX_new_from_name + EVP_PKEY_keygen_init.
///
/// Common names: c"RSA", c"EC", c"ED25519", c"X25519",
/// c"ML-KEM-768", c"ML-DSA-65".
pub fn new(name: &CStr) -> Result<Self, ErrorStack>;
/// Set keygen parameters (e.g. RSA bit length, EC curve name).
/// Wraps EVP_PKEY_CTX_set_params.
pub fn set_params(&mut self, params: &Params<'_>) -> Result<(), ErrorStack>;
/// Retrieve parameter values from this keygen context.
///
/// Build a Params with placeholder values for the keys you want,
/// call this method, then read back with params.get_*.
///
/// Useful for reading algorithm-negotiated parameters after keygen
/// initialisation (e.g. security strength, EC group name, RSA modulus size).
/// Wraps EVP_PKEY_CTX_get_params.
pub fn get_params(&self, params: &mut Params<'_>) -> Result<(), ErrorStack>;
/// Return the security strength of the key operation in bits.
///
/// Available after keygen initialisation; reads OSSL_PKEY_PARAM_SECURITY_BITS
/// ("security-bits"). Convenience wrapper around get_params.
pub fn security_bits(&self) -> Result<u32, ErrorStack>;
/// Generate the key pair.
pub fn generate(&mut self) -> Result<Pkey<Private>, ErrorStack>;
}
}
There are no convenience methods like generate_rsa(). Use KeygenCtx::new directly.
Signing
#![allow(unused)]
fn main() {
/// Parameters for creating a Signer or Verifier.
#[derive(Default)]
pub struct SignInit<'a> {
/// Digest algorithm, or None for EdDSA (Ed25519, Ed448).
pub digest: Option<&'a DigestAlg>,
/// Optional parameters (e.g. RSA PSS salt length).
pub params: Option<&'a Params<'a>>,
}
}
Signer
#![allow(unused)]
fn main() {
pub struct Signer { ctx: DigestCtx, _key: Pkey<Private> }
impl Signer {
/// Create a signing context. The key is cloned internally (EVP_PKEY_up_ref).
pub fn new(key: &Pkey<Private>, init: &SignInit<'_>) -> Result<Self, ErrorStack>;
/// Feed data. May be called multiple times.
pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack>;
/// Finalise: returns the signature as Vec<u8>.
/// Queries size then writes — always allocates.
pub fn finish(&mut self) -> Result<Vec<u8>, ErrorStack>;
/// One-shot sign over `data`. Required for Ed25519 / Ed448.
pub fn sign_oneshot(&mut self, data: &[u8]) -> Result<Vec<u8>, ErrorStack>;
}
}
Verifier
#![allow(unused)]
fn main() {
pub struct Verifier { ctx: DigestCtx, _key: Pkey<Public> }
impl Verifier {
pub fn new(key: &Pkey<Public>, init: &SignInit<'_>) -> Result<Self, ErrorStack>;
pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack>;
/// Returns Ok(true) if valid, Ok(false) if signature is wrong,
/// Err on a fatal OpenSSL error.
pub fn verify(&mut self, signature: &[u8]) -> Result<bool, ErrorStack>;
/// One-shot verify. Required for Ed25519 / Ed448.
pub fn verify_oneshot(&mut self, data: &[u8], sig: &[u8]) -> Result<bool, ErrorStack>;
}
}
Raw (No-Digest) Signing — RawSigner / RawVerifier
RawSigner and RawVerifier wrap EVP_PKEY_CTX after EVP_PKEY_sign_init /
EVP_PKEY_verify_init. They operate directly on pre-hashed data — the caller
is responsible for hashing the message before calling sign or verify.
Use these types for:
- ECDSA over a pre-computed hash (e.g. SHA-256 digest passed as the TBS input)
- Raw RSA with explicit padding configuration
For algorithms that hash internally (Ed25519, RSA-PSS with automatic hashing),
use Signer / Verifier or MessageSigner / MessageVerifier instead.
RawSigner
#![allow(unused)]
fn main() {
pub struct RawSigner { /* EVP_PKEY_CTX after sign_init */ }
impl RawSigner {
/// Create and initialise a sign context.
///
/// Pass libctx = Some(ctx) to restrict provider lookup to that library
/// context; None uses the key's own library context.
pub fn new(
key: &Pkey<Private>,
libctx: Option<&Arc<LibCtx>>,
) -> Result<Self, ErrorStack>;
/// Apply parameters after init (e.g. RSA padding mode, PSS salt length).
pub fn set_params(&mut self, params: &Params<'_>) -> Result<(), ErrorStack>;
/// Query the signature output size for the given input length.
/// Null-probe via EVP_PKEY_sign — does not consume the signing state.
pub fn sign_len(&mut self, tbs_len: usize) -> Result<usize, ErrorStack>;
/// Sign pre-hashed data into sig. Returns the number of bytes written.
/// sig.len() must be >= sign_len(tbs.len()).
pub fn sign(&mut self, tbs: &[u8], sig: &mut [u8]) -> Result<usize, ErrorStack>;
/// Convenience: sign_len + sign with an allocated output Vec.
pub fn sign_alloc(&mut self, tbs: &[u8]) -> Result<Vec<u8>, ErrorStack>;
}
}
The context is reusable across multiple sign calls — the padding parameters
set via set_params remain in effect until the RawSigner is dropped.
RawVerifier
#![allow(unused)]
fn main() {
pub struct RawVerifier { /* EVP_PKEY_CTX after verify_init */ }
impl RawVerifier {
/// Create and initialise a verify context.
///
/// Accepts any key with public material (Pkey<Public> or Pkey<Private>).
pub fn new<T: HasPublic>(
key: &Pkey<T>,
libctx: Option<&Arc<LibCtx>>,
) -> Result<Self, ErrorStack>;
/// Apply parameters after init (e.g. RSA padding mode).
pub fn set_params(&mut self, params: &Params<'_>) -> Result<(), ErrorStack>;
/// Verify sig against pre-hashed tbs. Returns Ok(()) if valid.
/// Returns Err if the signature is invalid or on any OpenSSL error.
pub fn verify(&mut self, tbs: &[u8], sig: &[u8]) -> Result<(), ErrorStack>;
}
}
Note that RawVerifier::verify returns Result<()> — unlike Verifier::verify
which returns Result<bool>, an invalid raw signature is an error rather than
Ok(false). This reflects the EVP_PKEY_verify API convention.
SigAlg — Algorithm Descriptor
SigAlg is an EVP_SIGNATURE algorithm descriptor. It mirrors DigestAlg,
CipherAlg, and MacAlg in naming and lifecycle. SigAlg is needed only when
constructing MessageSigner or MessageVerifier; the older Signer/Verifier
types use DigestAlg instead.
#![allow(unused)]
fn main() {
pub struct SigAlg { /* EVP_SIGNATURE* */ }
impl SigAlg {
/// Fetch from the default library context.
///
/// Example names: c"ML-DSA-44", c"ED25519", c"SLH-DSA-SHA2-128s".
pub fn fetch(name: &CStr, props: Option<&CStr>) -> Result<Self, ErrorStack>;
/// Fetch within a specific library context.
pub fn fetch_in(ctx: &Arc<LibCtx>, name: &CStr, props: Option<&CStr>) -> Result<Self, ErrorStack>;
}
}
SigAlg is Clone (via EVP_SIGNATURE_up_ref) and Send + Sync.
Drop calls EVP_SIGNATURE_free. Cloning is cheap — it increments a reference
count inside OpenSSL.
Message Signing — MessageSigner / MessageVerifier
MessageSigner and MessageVerifier wrap EVP_PKEY_sign_message_* /
EVP_PKEY_verify_message_* (OpenSSL 3.2+, present at the 3.5 minimum floor).
These types differ from Signer/Verifier in two ways:
- The algorithm is specified as a
SigAlgrather than aDigestAlg. This enables post-quantum and other algorithms that do not separate hashing from signing at the API level. finishandsign_oneshotare consuming (selfby value) — the context cannot be reused after finalisation.
MessageSigner
#![allow(unused)]
fn main() {
pub struct MessageSigner { /* EVP_PKEY_CTX after sign_message_init */ }
impl MessageSigner {
/// Create and initialise a message-sign context.
///
/// alg is consumed by EVP_PKEY_sign_message_init; pass alg.clone() if
/// you need to keep the SigAlg alive.
/// params sets algorithm-specific options (e.g. context string for Ed25519).
pub fn new(
key: &Pkey<Private>,
alg: &mut SigAlg,
params: Option<&Params<'_>>,
) -> Result<Self, ErrorStack>;
/// Probe whether this algorithm backend supports incremental update calls.
///
/// Uses ERR_set_mark / ERR_pop_to_mark so the error queue is clean
/// regardless of the outcome.
pub fn supports_streaming(&mut self) -> bool;
/// Feed data into the signing operation.
pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack>;
/// Query the signature output length (null-probe — does not finalise).
pub fn sig_len(&mut self) -> Result<usize, ErrorStack>;
/// Finalise and produce the signature into sig. Consuming.
/// Returns the number of bytes written.
pub fn finish(self, sig: &mut [u8]) -> Result<usize, ErrorStack>;
/// One-shot: update + finish in a single call. Consuming.
pub fn sign_oneshot(self, data: &[u8], sig: &mut [u8]) -> Result<usize, ErrorStack>;
}
}
MessageVerifier
#![allow(unused)]
fn main() {
pub struct MessageVerifier { /* EVP_PKEY_CTX after verify_message_init */ }
impl MessageVerifier {
/// Create and initialise a message-verify context.
///
/// Accepts any key with public material.
pub fn new<T: HasPublic>(
key: &Pkey<T>,
alg: &mut SigAlg,
params: Option<&Params<'_>>,
) -> Result<Self, ErrorStack>;
/// Apply parameters after init.
pub fn set_params(&mut self, params: &Params<'_>) -> Result<(), ErrorStack>;
/// Supply the signature to verify against. Required before streaming finish.
/// Not needed for verify_oneshot.
pub fn set_signature(&mut self, sig: &[u8]) -> Result<(), ErrorStack>;
/// Probe whether this algorithm backend supports incremental update calls.
pub fn supports_streaming(&mut self) -> bool;
/// Feed data into the verification operation.
pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack>;
/// Finalise and verify. Signature must be set via set_signature. Consuming.
/// Returns Ok(()) if the signature is valid.
pub fn finish(self) -> Result<(), ErrorStack>;
/// One-shot: set signature, feed data, finalise. Consuming.
pub fn verify_oneshot(self, data: &[u8], sig: &[u8]) -> Result<(), ErrorStack>;
}
}
ML-DSA Provider Caveat
OpenSSL 3.5’s built-in ML-DSA provider implements
OSSL_FUNC_SIGNATURE_SIGN_MESSAGE (the one-shot EVP_DigestSign path) but not
OSSL_FUNC_SIGNATURE_SIGN_MESSAGE_FINAL. As a result:
MessageSigner::supports_streaming()returnsfalsefor ML-DSA with the default provider.MessageSigner::finish()will fail for ML-DSA with the default provider.- ML-DSA signing with the default provider must use
SignerwithSignInit { digest: None, .. }(theEVP_DigestSignone-shot path).
MessageSigner / MessageVerifier work correctly for algorithms whose provider
implements the full streaming path (e.g. Ed25519 with context strings in a
provider that supports it).
Key Agreement (ECDH, X25519)
#![allow(unused)]
fn main() {
pub struct DeriveCtx { /* EVP_PKEY_CTX in derive mode */ }
impl DeriveCtx {
/// Create from a private key. Calls EVP_PKEY_derive_init.
pub fn new(key: &Pkey<Private>) -> Result<Self, ErrorStack>;
/// Set the peer's public key.
pub fn set_peer(&mut self, peer: &Pkey<Public>) -> Result<(), ErrorStack>;
/// Query the shared secret length.
pub fn derive_len(&mut self) -> Result<usize, ErrorStack>;
/// Derive into caller-provided buffer.
pub fn derive(&mut self, out: &mut [u8]) -> Result<usize, ErrorStack>;
}
}
RSA Encryption
#![allow(unused)]
fn main() {
pub struct PkeyEncryptCtx { /* EVP_PKEY_CTX in encrypt mode */ }
impl PkeyEncryptCtx {
/// Create from a public key. params sets padding mode (e.g. OAEP).
pub fn new(key: &Pkey<Public>, params: Option<&Params<'_>>) -> Result<Self, ErrorStack>;
/// Query ciphertext length for a given plaintext length.
pub fn encrypt_len(&mut self, plaintext_len: usize) -> Result<usize, ErrorStack>;
/// Encrypt plaintext into ciphertext. Returns bytes written.
pub fn encrypt(&mut self, plaintext: &[u8], ciphertext: &mut [u8])
-> Result<usize, ErrorStack>;
}
pub struct PkeyDecryptCtx { /* EVP_PKEY_CTX in decrypt mode */ }
impl PkeyDecryptCtx {
pub fn new(key: &Pkey<Private>, params: Option<&Params<'_>>) -> Result<Self, ErrorStack>;
pub fn decrypt(&mut self, ciphertext: &[u8], plaintext: &mut [u8])
-> Result<usize, ErrorStack>;
}
}
ML-KEM Key Encapsulation
#![allow(unused)]
fn main() {
pub struct EncapResult {
pub wrapped_key: Vec<u8>, // the encapsulated key (ciphertext)
pub shared_secret: Vec<u8>, // the shared secret
}
pub struct EncapCtx { /* EVP_PKEY_CTX in encapsulate mode */ }
impl EncapCtx {
pub fn new(key: &Pkey<Public>) -> Result<Self, ErrorStack>;
/// Encapsulate: generates a fresh shared secret and its encapsulation.
/// Both outputs are returned as owned Vecs.
pub fn encapsulate(&mut self) -> Result<EncapResult, ErrorStack>;
}
pub struct DecapCtx { /* EVP_PKEY_CTX in decapsulate mode */ }
impl DecapCtx {
pub fn new(key: &Pkey<Private>) -> Result<Self, ErrorStack>;
/// Recover the shared secret from a wrapped key.
pub fn decapsulate(&mut self, wrapped_key: &[u8]) -> Result<Vec<u8>, ErrorStack>;
}
}
Examples
Generate RSA-4096 Key Pair
#![allow(unused)]
fn main() {
use native_ossl::pkey::{KeygenCtx, Pkey, Private};
use native_ossl::params::ParamBuilder;
let mut ctx = KeygenCtx::new(c"RSA")?;
let params = ParamBuilder::new()?
.push_uint(c"bits", 4096)?
.build()?;
ctx.set_params(¶ms)?;
let key: Pkey<Private> = ctx.generate()?;
assert!(key.is_a(c"RSA"));
assert_eq!(key.bits(), 4096);
}
Generate Ed25519 Key and Sign
#![allow(unused)]
fn main() {
use native_ossl::pkey::{KeygenCtx, Signer, Verifier, SignInit};
let key = KeygenCtx::new(c"ED25519")?.generate()?;
let pub_key = Pkey::<native_ossl::pkey::Public>::from_pem(&key.to_pem()?)?;
let init = SignInit::default(); // digest = None for Ed25519
let mut signer = Signer::new(&key, &init)?;
let sig = signer.sign_oneshot(b"message to sign")?;
let mut verifier = Verifier::new(&pub_key, &init)?;
assert!(verifier.verify_oneshot(b"message to sign", &sig)?);
}
ECDSA Sign with SHA-256
#![allow(unused)]
fn main() {
use native_ossl::pkey::{KeygenCtx, Signer, Verifier, SignInit};
use native_ossl::digest::DigestAlg;
use native_ossl::params::ParamBuilder;
// Generate P-256 key
let mut ctx = KeygenCtx::new(c"EC")?;
let curve_params = ParamBuilder::new()?
.push_utf8_string(c"group", c"P-256")?
.build()?;
ctx.set_params(&curve_params)?;
let key = ctx.generate()?;
let sha256 = DigestAlg::fetch(c"SHA2-256", None)?;
let init = SignInit { digest: Some(&sha256), params: None };
let mut signer = Signer::new(&key, &init)?;
signer.update(b"hello ")?;
signer.update(b"world")?;
let sig = signer.finish()?;
}
Pre-Hashed ECDSA with RawSigner
When the message digest is computed separately (e.g. already SHA-256 hashed),
use RawSigner to sign the hash directly without re-hashing:
#![allow(unused)]
fn main() {
use native_ossl::pkey::{KeygenCtx, RawSigner, RawVerifier};
use native_ossl::params::ParamBuilder;
// Generate P-256 key
let mut ctx = KeygenCtx::new(c"EC")?;
let curve_params = ParamBuilder::new()?
.push_utf8_string(c"group", c"P-256")?
.build()?;
ctx.set_params(&curve_params)?;
let priv_key = ctx.generate()?;
let pub_key = native_ossl::pkey::Pkey::<native_ossl::pkey::Public>::from(priv_key.clone());
// The caller provides the pre-computed SHA-256 hash (32 bytes).
let tbs: [u8; 32] = compute_sha256(b"my message");
// Sign the hash. None = use the key's own library context.
let mut signer = RawSigner::new(&priv_key, None)?;
let sig = signer.sign_alloc(&tbs)?;
// Verify the signature against the same hash.
let mut verifier = RawVerifier::new(&pub_key, None)?;
verifier.verify(&tbs, &sig)?; // Ok(()) means valid
}
To configure RSA PSS padding after construction, use set_params:
#![allow(unused)]
fn main() {
use native_ossl::pkey::{KeygenCtx, RawSigner};
use native_ossl::params::ParamBuilder;
let key = KeygenCtx::new(c"RSA")?.generate()?;
let mut signer = RawSigner::new(&key, None)?;
let pss_params = ParamBuilder::new()?
.push_utf8_ptr(c"pad-mode", c"pss")?
.push_int(c"saltlen", 32)?
.build()?;
signer.set_params(&pss_params)?;
let mut sig_buf = vec![0u8; signer.sign_len(32)?];
let n = signer.sign(&my_hash, &mut sig_buf)?;
sig_buf.truncate(n);
}
MessageSigner / MessageVerifier with Streaming Probe
#![allow(unused)]
fn main() {
use native_ossl::pkey::{KeygenCtx, SigAlg, MessageSigner, MessageVerifier};
let key = KeygenCtx::new(c"ED25519")?.generate()?;
let pub_key = native_ossl::pkey::Pkey::<native_ossl::pkey::Public>::from(key.clone());
let mut alg = SigAlg::fetch(c"ED25519", None)?;
// Construction takes alg by &mut — pass alg.clone() if you need to reuse it.
let mut signer = MessageSigner::new(&key, &mut alg, None)?;
if signer.supports_streaming() {
// Algorithm supports incremental update calls.
signer.update(b"chunk one")?;
signer.update(b"chunk two")?;
let siglen = signer.sig_len()?; // query before allocating
let mut sig = vec![0u8; siglen];
let n = signer.finish(sig.as_mut_slice())?; // consuming
sig.truncate(n);
// Verify using the streaming path.
let mut alg2 = SigAlg::fetch(c"ED25519", None)?;
let mut verifier = MessageVerifier::new(&pub_key, &mut alg2, None)?;
verifier.set_signature(&sig)?;
verifier.update(b"chunk one")?;
verifier.update(b"chunk two")?;
verifier.finish()?; // Ok(()) means valid; consuming
} else {
// Algorithm only supports one-shot operation (e.g. ML-DSA with default provider).
let siglen = signer.sig_len()?;
let mut sig = vec![0u8; siglen];
let data = b"full message";
let n = signer.sign_oneshot(data, &mut sig)?; // consuming
sig.truncate(n);
let mut alg2 = SigAlg::fetch(c"ED25519", None)?;
let verifier = MessageVerifier::new(&pub_key, &mut alg2, None)?;
verifier.verify_oneshot(data, &sig)?; // consuming
}
}
RSA-OAEP Encrypt / Decrypt
#![allow(unused)]
fn main() {
use native_ossl::pkey::{KeygenCtx, PkeyEncryptCtx, PkeyDecryptCtx};
use native_ossl::params::ParamBuilder;
let key = KeygenCtx::new(c"RSA")?.generate()?;
let oaep = ParamBuilder::new()?
.push_utf8_ptr(c"pad-mode", c"oaep")?
.push_utf8_ptr(c"oaep-digest", c"SHA-256")?
.build()?;
let pub_pem = key.to_pem()?; // in practice load from file
let pub_key = native_ossl::pkey::Pkey::<native_ossl::pkey::Public>::from_pem(&pub_pem)?;
let mut enc = PkeyEncryptCtx::new(&pub_key, Some(&oaep))?;
let ct_len = enc.encrypt_len(b"secret".len())?;
let mut ciphertext = vec![0u8; ct_len];
let n = enc.encrypt(b"secret", &mut ciphertext)?;
ciphertext.truncate(n);
let mut dec = PkeyDecryptCtx::new(&key, Some(&oaep))?;
let mut plaintext = vec![0u8; ciphertext.len()];
let m = dec.decrypt(&ciphertext, &mut plaintext)?;
plaintext.truncate(m);
assert_eq!(&plaintext, b"secret");
}
OSSL_PARAM Round-Trip: Generate → Export → Import
Export a generated EC P-256 key pair to raw parameters and re-import it as a
fresh Pkey<Private>. The public components of both keys must match.
use native_ossl::pkey::{KeygenCtx, Pkey, Private, Public};
use native_ossl::params::ParamBuilder;
// 1. Generate a P-256 key pair.
let mut ctx = KeygenCtx::new(c"EC")?;
let curve = ParamBuilder::new()?
.push_utf8_string(c"group", c"P-256")?
.build()?;
ctx.set_params(&curve)?;
let original: Pkey<Private> = ctx.generate()?;
// 2. Export all key material (private + public) to a Params array.
let exported = original.export()?;
// 3. Inspect individual parameters.
// get_bn allocates a Vec<u8> of big-endian bytes.
if exported.has_param(c"priv") {
let priv_bytes = exported.get_bn(c"priv")?;
println!("private scalar: {} bytes", priv_bytes.len());
}
// 4. Re-import as a new private key.
let recovered = Pkey::<Private>::from_params(None, c"EC", &exported)?;
assert!(recovered.is_a(c"EC"));
// 5. Verify that the public components match.
let pub_original = Pkey::<Public>::from(original.clone());
let pub_recovered = Pkey::<Public>::from(recovered);
assert!(pub_original.public_eq(&pub_recovered));
Targeted Parameter Query with get_params
Fetch only the RSA public exponent without exporting the full key:
use native_ossl::params::ParamBuilder;
// Build a query array with a placeholder for the "e" parameter.
// The zero-length octet slice signals to OpenSSL that this is a query slot.
let mut query = ParamBuilder::new()?
.push_octet_slice(c"e", &[])?
.build()?;
key.get_params(&mut query)?;
// get_bn allocates a Vec<u8> of the BIGNUM value.
let e_bytes = query.get_bn(c"e")?;
println!("public exponent: {:?}", e_bytes);
Load a Public Key in a FIPS Library Context
use native_ossl::lib_ctx::LibCtx;
use native_ossl::pkey::{Pkey, Public};
use std::sync::Arc;
// Load the FIPS provider into an isolated context.
let fips_ctx = Arc::new(LibCtx::new()?);
// fips_ctx.load_provider(...)?; — load your FIPS provider here
let pub_pem = std::fs::read("peer_pubkey.pem")?;
let pub_key = Pkey::<Public>::from_pem_in(&fips_ctx, &pub_pem)?;
// All subsequent operations on pub_key use the FIPS provider.
ML-KEM-768 (Post-Quantum KEM)
#![allow(unused)]
fn main() {
use native_ossl::pkey::{KeygenCtx, EncapCtx, DecapCtx};
// Generate ML-KEM-768 key pair (recipient)
let key = KeygenCtx::new(c"ML-KEM-768")?.generate()?;
let pub_pem = key.to_pem()?;
let pub_key = native_ossl::pkey::Pkey::<native_ossl::pkey::Public>::from_pem(&pub_pem)?;
// Sender encapsulates
let mut enc = EncapCtx::new(&pub_key)?;
let result = enc.encapsulate()?;
// Recipient decapsulates
let mut dec = DecapCtx::new(&key)?;
let shared = dec.decapsulate(&result.wrapped_key)?;
assert_eq!(shared, result.shared_secret);
}
Design Notes
max_output_size()returnsusize, notResult<usize>— it mirrors thebits()/security_bits()pattern. It returns0rather than an error when the key does not support a size query (e.g., for key-agreement-only keys). For RSA the value equalsbits() / 8; for Ed25519 it is always 64.- No convenience constructors — there are no
generate_rsa(),generate_ed25519(), etc. shorthand methods. UseKeygenCtx::new(c"ED25519")?.generate()directly. Signer/Verifierhold an owned clone of the key via_key: Pkey<T>. There is no lifetime parameter — the key is cloned atnew()viaEVP_PKEY_up_ref.Verifier::verifyreturnsResult<bool>—Ok(false)means the signature was well-formed but incorrect;Errmeans a fatal operational error.RawVerifier::verifyreturnsResult<()>—Errcovers both invalid signatures and operational errors, matching theEVP_PKEY_verifyAPI convention.from_derusesMemBioBuf— reads directly from the caller’s slice without copying. The whole slice is consumed; there is no cursor-advancement API.from_der_inuses a raw pointer pair, not a BIO —d2i_AutoPrivateKey_ex/d2i_PUBKEY_extake(const unsigned char**, long)directly. Passing the slice pointer lets OpenSSL bind the key’s internal algorithm fetch to the suppliedLibCtx, which is necessary for FIPS-isolated operations. The format forPkey<Private>::from_der_inis auto-detected (PKCS#8 or traditional); use it when you have DER bytes of unknown provenance and need FIPS isolation. Use the non-_invariants when the global provider context is sufficient.to_der_legacyvs.to_pkcs8_der—to_pkcs8_der(wrapsi2d_PKCS8PrivateKeyInfo) produces a PKCS#8PrivateKeyInfo/ RFC 5958 structure that is algorithm-agnostic and the recommended format for all new code.to_der_legacy(wrapsi2d_PrivateKey) produces the algorithm-specific raw structure (e.g.RSAPrivateKeyper RFC 3447,ECPrivateKeyper RFC 5915). Useto_der_legacyonly for interoperability with legacy software that does not support PKCS#8; some algorithms (e.g. post-quantum) may not support the legacy format at all and will return an error.Pkey<Public>::from_pem_inusesPEM_read_bio_PUBKEY_ex— unlikeX509::from_pem_in(which ignores itsctxargument because no libctx-aware variant exists in OpenSSL 3.5),Pkey<Public>::from_pem_ingenuinely binds the key’s algorithm fetch to the suppliedLibCtx. Use it when the public key will be used for operations in an isolated provider context.from_paramsselection —Pkey<Private>::from_paramsusesKEYPAIRselection (both private and public material required);Pkey<Public>::from_paramsusesPUBLIC_KEYselection. Passing private-key parameters toPkey<Public>::from_paramsis safe — OpenSSL ignores the private fields.exportreturnsParams<'static>— the array is allocated by OpenSSL and owned entirely by the returnedParamsvalue. There are no lifetime constraints from the source key; thePkeymay be dropped before the exportedParams.get_paramsvs.export— useget_paramswhen you need only a subset of fields (more efficient); useexportwhen you need a complete dump or plan to pass theParamsarray tofrom_params.RawSigneris reusable — padding parameters configured viaset_paramspersist across multiplesigncalls on the same instance.MessageSigner/MessageVerifierare consuming —finishandsign_oneshottakeselfby value; the context is freed at the end of the call.SigAlgtaken by&mut—EVP_PKEY_sign_message_initmodifies the algorithm object internally. Passalg.clone()if you need theSigAlgto remain usable after constructing aMessageSignerorMessageVerifier.- ML-DSA streaming caveat — OpenSSL 3.5’s built-in ML-DSA provider does not
implement
OSSL_FUNC_SIGNATURE_SIGN_MESSAGE_FINAL. UseSignerfor ML-DSA signing with the default provider. KeygenCtx::set_paramsvs constructor params —set_paramsmay be called any number of times beforegenerateto layer parameter changes. Each call applies incrementally; earlier settings are not erased unless the same key is overwritten.KeygenCtx::get_paramsgetter pattern — build aParamsarray with placeholder values (e.g.push_uint(c"security-bits", 0)), callget_params, then read back the filled values withparams.get_uint/params.get_utf8_stringetc. The placeholder value is overwritten by OpenSSL; only the key name matters for the query. For string parameters, the placeholder must be long enough to receive the returned value.KeygenCtx::get_paramsvsRawSigner::get_params— keygen contexts expose few gettable parameters (algorithm-specific; most algorithms expose none). Operation contexts (sign/verify) expose more: RSA exposespad-mode,digest,mgf1-digest,saltlen, andalgorithm-id. UseRawSigner::get_paramsto inspect or verify per-operation settings afterEVP_PKEY_sign_init.KeygenCtx::get_paramsvsPkey::get_params—KeygenCtx::get_paramsreads from the context (available immediately afterkeygen_init, before a key exists);Pkey::get_paramsreads from the generated key object (available only aftergenerate). Use the key-object variant forsecurity-bitsand other key properties.default_digest_name()returnsNonefor pre-hash algorithms — For Ed25519, ML-DSA, and SLH-DSA the key type performs all hashing internally and there is no separate digest step.default_digest_name()returnsNonein that case, and callers should passdigest: NoneinSignInit(or omit the digest argument entirely). For RSA and ECDSA keys it returns aSome(name)(typically"SHA256") which can be fetched viaDigestAlg::fetchand passed toSigner::neworDigestSignInit_ex. Check this value before deciding whether to supply a digest toDigestSign— supplying a digest to Ed25519 is an error.