openssl/openssl
Encoders and decoders
Active contributors: Tomas Mraz, Pauli, Richard Levitte, Matt Caswell
Purpose
The OSSL_ENCODER and OSSL_DECODER framework converts cryptographic keys (EVP_PKEY) and key parameters between in-memory form and various encoded forms — DER, PEM, JSON-ish text, native-provider OSSL_PARAM representations, and structured wrappers like PKCS#8 and SubjectPublicKeyInfo. It is the 3.0-era replacement for the per-algorithm i2d_*/d2i_* zoo.
The framework is provider-routed: an encoder or decoder is published by a provider and selected by name + property. This is what makes it possible for the FIPS provider to expose a PEM↔EVP_PKEY decoder without statically linking the file-format code.
Why a new framework
Pre-3.0, every key type had its own family:
i2d_RSAPrivateKey,d2i_RSAPrivateKey(DER / PKCS#1 RSAPrivateKey)i2d_PrivateKey,d2i_PrivateKey(DER / PKCS#8)PEM_write_bio_RSAPrivateKey,PEM_read_bio_RSAPrivateKey,PEM_write_bio_PrivateKey,PEM_read_bio_PrivateKey- algorithm-by-algorithm helpers in
crypto/rsa/,crypto/dsa/,crypto/ec/,crypto/dh/, …
Adding a new public-key algorithm meant reproducing this family for each format. The framework breaks the matrix into:
- Algorithm-aware encoder/decoder implementations in providers, registered by the (input/output structure, type, output type) triple.
- A driver in libcrypto (
crypto/encode_decode/) that chains them together to find a path from caller-input to caller-output.
Directory layout
crypto/encode_decode/
├── encoder_lib.c 28 KB -- the encoder driver (chain construction, output)
├── encoder_meth.c 21 KB -- encoder method registration / fetch
├── encoder_pkey.c 13 KB -- the EVP_PKEY-specific entry points
├── encoder_local.h, encoder_err.c
├── decoder_lib.c 40 KB -- the decoder driver
├── decoder_meth.c 20 KB -- decoder method registration / fetch
├── decoder_pkey.c 31 KB -- the EVP_PKEY-specific entry points
└── decoder_err.c
providers/implementations/encode_decode/
├── encode_key2any.c, encode_key2text.c, encode_key2ms.c, encode_key2blob.c -- algorithm-agnostic encoder front-ends
├── decode_*.c -- per-format decoders (DER, PEM, "MSBLOB", PVK, "epki" PKCS#8)
├── encoder_local.h, decoder_local.h
└── ml_kem_codecs.c, ml_dsa_codecs.c, slh_dsa_codecs.c, lms_codecs.c -- post-quantum algorithm specific bitsConceptual model
A decoder is a function that turns one encoded form into another. The framework chains decoders together to bridge from input to a target type:
graph LR
PEM[PEM bytes] --> D1[decoder<br/>PEM -> DER]
D1 --> DER[DER bytes]
DER --> D2[decoder<br/>DER -> PKCS8 PrivateKeyInfo]
D2 --> P8[PKCS#8]
P8 --> D3[decoder<br/>PKCS8 -> ML-KEM-768 EVP_PKEY]
D3 --> KEY[EVP_PKEY]Each decoder publishes:
- An input structure name (e.g.
PEM,DER,PKCS8,SubjectPublicKeyInfo,RSAPrivateKey). - An input type (data, key, signature, …).
- An output keytype for the final stage (
RSA,EC,Ed25519,ML-KEM-768, …).
Encoders are the mirror image, with output structure / output type / input keytype.
The driver runs a search to find any acyclic chain whose total cost is minimised and whose first stage matches the caller's input. It does this without requiring callers to enumerate the chain.
Caller surface
/* Decode: read a PEM file containing a private key into an EVP_PKEY. */
EVP_PKEY *pkey = NULL;
const char *pwd = "secret";
OSSL_DECODER_CTX *dctx = OSSL_DECODER_CTX_new_for_pkey(
&pkey,
"PEM", /* input structure */
NULL, /* input type, NULL = auto */
"RSA", /* desired keytype, or NULL = any */
EVP_PKEY_PRIVATE_KEY,
libctx,
NULL /* propq */ );
OSSL_DECODER_CTX_set_passphrase(dctx, (unsigned char *)pwd, strlen(pwd));
OSSL_DECODER_from_bio(dctx, bio);
OSSL_DECODER_CTX_free(dctx);/* Encode: write an EVP_PKEY to a BIO as PEM PKCS#8 with a passphrase. */
OSSL_ENCODER_CTX *ectx = OSSL_ENCODER_CTX_new_for_pkey(
pkey,
EVP_PKEY_KEY_PARAMETERS | EVP_PKEY_PRIVATE_KEY,
"PEM",
"PrivateKeyInfo",
NULL /* propq */ );
OSSL_ENCODER_CTX_set_passphrase(ectx, (unsigned char *)pwd, strlen(pwd));
OSSL_ENCODER_to_bio(ectx, bio);
OSSL_ENCODER_CTX_free(ectx);PEM_read_bio_PrivateKey_ex and the surviving i2d_*/d2i_* for keys are now thin wrappers that build an OSSL_ENCODER_CTX / OSSL_DECODER_CTX and run it.
Where the implementations live
All built-in encoders/decoders are under providers/implementations/encode_decode/:
encode_key2any.c— encodes any provider-side keydata into a provider-neutral structure (PKCS#8, SubjectPublicKeyInfo, type-specific PKCS#1, etc.) in DER, then optionally wraps in PEM.encode_key2text.c— produces human-readable text dumps.encode_key2ms.c— Microsoft "MSBLOB" key format.encode_key2blob.c— raw key bytes (e.g. for X25519 raw private keys).decode_pem2der.c— strip the PEM armor.decode_der2*.c— DER → PKCS#8 / DER → SubjectPublicKeyInfo / DER → algorithm-specific structure.decode_msblob2*.c,decode_pvk2*.c,decode_epki2pki.c— special-purpose wrappers.- Per-algorithm decoders for ML-KEM, ML-DSA, SLH-DSA, LMS in
ml_kem_codecs.cetc.
The default and base providers expose all of them; the FIPS provider exposes none (deliberately — FIPS doesn't include PEM/DER plumbing).
Provider authoring
A new encoder is an OSSL_DISPATCH[] exposing:
OSSL_FUNC_ENCODER_NEWCTX,_FREECTX,_GETTABLE_PARAMS,_GET_PARAMS,_SETTABLE_CTX_PARAMS,_SET_CTX_PARAMS,_DOES_SELECTION,_ENCODE,_IMPORT_OBJECT,_FREE_OBJECT.
Plus an OSSL_ALGORITHM row registering the algorithm with its name (e.g. RSA), its property string (output=pem,structure=PrivateKeyInfo), and a description.
Decoders mirror this with OSSL_FUNC_DECODER_* and OSSL_FUNC_DECODER_DECODE.
Property keys
The framework uses these property keys (defined in core_names.h):
output=<name>— for encoders: which output format the encoder produces (e.g.pem,der,text,msblob,pvk,blob).input=<name>— for decoders: which input format the decoder accepts.structure=<name>— the structural wrapper (e.g.PrivateKeyInfofor PKCS#8,SubjectPublicKeyInfofor SPKI,EncryptedPrivateKeyInfofor encrypted PKCS#8,PrivateKeyInfo,pkcs1,RSAPublicKey,OAEP,epki,type-specific).
Combined with provider=… and fips=yes, this lets callers select e.g. "PEM PKCS#8 from the FIPS provider via the base provider's encoder".
EVP_PKEY interaction
When OSSL_DECODER produces a key it always stores it in an EVP_PKEY in the provider representation (i.e. an EVP_KEYMGMT plus a void *keydata). Likewise OSSL_ENCODER reads from the provider representation. Legacy EVP_PKEY payloads (RSA struct etc.) are upgraded automatically before encoding and downgraded after decoding only if the legacy API path is used.
Integration points
PEM_*_PrivateKey,PEM_*_PUBKEY,i2d_PUBKEY,d2i_PrivateKey— all reroute via the encoder/decoder framework.OSSL_STOREuses the decoder framework when reading afile:URI: PEM blocks are detected, fed through decoders, and becomeOSSL_STORE_INFOitems.- TLS (
SSL_use_certificate_file,SSL_use_PrivateKey_file) uses these to load configuration from disk. - CMP / CMS / X.509 consume keys through
EVP_PKEYregardless of how it was decoded.
Entry points for modification
- New algorithm decoder: write a per-keytype decoder under
providers/implementations/encode_decode/(seeml_kem_codecs.cfor a recent example), register in the default and base providers' tables. - New format (e.g. CBOR for keys): write a
decode_<from>2<to>.cand anencode_<from>2<to>.c, register them. The driver finds them automatically. - New structure: extend
core_names.hwith the structure-name macro, register the new encoders/decoders for it.
Documentation
doc/man3/OSSL_DECODER.pod,OSSL_DECODER_from_bio.pod,OSSL_DECODER_CTX_new_for_pkey.pod,OSSL_DECODER_CTX_set_passphrase_cb.pod.doc/man3/OSSL_ENCODER.pod,OSSL_ENCODER_to_bio.pod,OSSL_ENCODER_CTX_new_for_pkey.pod.doc/man7/provider-encoder.pod,provider-decoder.pod— provider-side ABI.doc/designs/passing-algorithmidentifier-parameters.md— design notes.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.