Symmetric Encryption
Two-Object Model
Symmetric encryption uses the same fetch-then-use pattern as digests:
CipherAlg— algorithm descriptor. Fetch once, reuse.Send + Sync.CipherCtx<Dir>— stateful context. EitherCipherCtx<Encrypt>orCipherCtx<Decrypt>.
For AEAD modes (GCM, CCM, ChaCha20-Poly1305), use the dedicated wrappers
AeadEncryptCtx and AeadDecryptCtx instead of CipherCtx<Dir> directly.
CipherAlg — Algorithm Descriptor
#![allow(unused)]
fn main() {
pub struct CipherAlg { /* EVP_CIPHER* */ }
impl CipherAlg {
/// Fetch from the global default library context.
pub fn fetch(name: &CStr, props: Option<&CStr>) -> Result<Self, ErrorStack>;
/// Fetch from an explicit library context (for FIPS isolation).
pub fn fetch_in(ctx: &Arc<LibCtx>, name: &CStr, props: Option<&CStr>)
-> Result<Self, ErrorStack>;
/// Expected key length in bytes (e.g. 32 for AES-256-*).
pub fn key_len(&self) -> usize;
/// Expected IV/nonce length in bytes (0 for ECB mode).
pub fn iv_len(&self) -> usize;
/// Block size in bytes (1 for stream ciphers; 16 for AES).
pub fn block_size(&self) -> usize;
/// Raw OpenSSL cipher flags.
pub fn flags(&self) -> u64;
/// `true` if this is an AEAD cipher (GCM, CCM, ChaCha20-Poly1305).
pub fn is_aead(&self) -> bool;
/// Create an encryption context.
/// Key and IV are always copied into OpenSSL's internal context.
pub fn encrypt(&self, key: &[u8], iv: &[u8], params: Option<&Params<'_>>)
-> Result<CipherCtx<Encrypt>, ErrorStack>;
/// Create a decryption context.
pub fn decrypt(&self, key: &[u8], iv: &[u8], params: Option<&Params<'_>>)
-> Result<CipherCtx<Decrypt>, ErrorStack>;
}
}
CipherCtx<Dir> — Non-AEAD Context
#![allow(unused)]
fn main() {
pub struct CipherCtx<Dir> { /* EVP_CIPHER_CTX* */ }
impl<Dir: Direction> CipherCtx<Dir> {
/// Feed `input` through the cipher; write output into `output`.
/// `output` must be at least `input.len() + block_size - 1` bytes.
/// Returns the number of bytes written.
pub fn update(&mut self, input: &[u8], output: &mut [u8]) -> Result<usize, ErrorStack>;
/// Like `update` but allocates the output buffer.
pub fn update_to_vec(&mut self, input: &[u8]) -> Result<Vec<u8>, ErrorStack>;
/// Finalise (flush padding / verify). Returns bytes written.
pub fn finalize(&mut self, output: &mut [u8]) -> Result<usize, ErrorStack>;
/// Set dynamic parameters after construction.
pub fn set_params(&mut self, params: &Params<'_>) -> Result<(), ErrorStack>;
/// Query arbitrary parameters from the context via a pre-built getter array.
/// Build a `Params` array with placeholder values, call this, then read results.
pub fn get_params(&self, params: &mut Params<'_>) -> Result<(), ErrorStack>;
/// Return the AEAD authentication tag length (e.g. 16 for AES-GCM).
pub fn aead_tag_len(&self) -> Result<usize, ErrorStack>;
/// Return the key length in bytes for this cipher context.
pub fn key_len(&self) -> Result<usize, ErrorStack>;
/// Return the IV length in bytes for this cipher context.
pub fn iv_len(&self) -> Result<usize, ErrorStack>;
}
}
The marker types Encrypt and Decrypt prevent calling encrypt functions on a decrypt
context and vice versa.
AEAD Contexts
AEAD modes add authentication. Use these wrappers rather than CipherCtx<Dir>:
#![allow(unused)]
fn main() {
pub struct AeadEncryptCtx(CipherCtx<Encrypt>);
pub struct AeadDecryptCtx(CipherCtx<Decrypt>);
}
AeadEncryptCtx
#![allow(unused)]
fn main() {
impl AeadEncryptCtx {
/// Create an AEAD encryption context.
///
/// # Panics
///
/// Panics if `alg.is_aead()` is false. Check with `alg.is_aead()` if unsure.
pub fn new(alg: &CipherAlg, key: &[u8], iv: &[u8], params: Option<&Params<'_>>)
-> Result<Self, ErrorStack>;
/// Set additional authenticated data (AAD). Call before first `update`.
pub fn set_aad(&mut self, aad: &[u8]) -> Result<(), ErrorStack>;
pub fn update(&mut self, input: &[u8], output: &mut [u8]) -> Result<usize, ErrorStack>;
pub fn finalize(&mut self, output: &mut [u8]) -> Result<usize, ErrorStack>;
/// Retrieve the authentication tag after `finalize`. Typically 16 bytes for GCM.
pub fn tag(&self, tag: &mut [u8]) -> Result<(), ErrorStack>;
}
}
AeadDecryptCtx
#![allow(unused)]
fn main() {
impl AeadDecryptCtx {
pub fn new(alg: &CipherAlg, key: &[u8], iv: &[u8], params: Option<&Params<'_>>)
-> Result<Self, ErrorStack>;
pub fn set_aad(&mut self, aad: &[u8]) -> Result<(), ErrorStack>;
pub fn update(&mut self, input: &[u8], output: &mut [u8]) -> Result<usize, ErrorStack>;
/// Set the expected authentication tag before calling `finalize`.
pub fn set_tag(&mut self, tag: &[u8]) -> Result<(), ErrorStack>;
/// Finalise: returns `Err` if authentication fails.
pub fn finalize(&mut self, output: &mut [u8]) -> Result<usize, ErrorStack>;
}
}
Algorithm Names
| OpenSSL name string | Key (bytes) | IV (bytes) | Notes |
|---|---|---|---|
c"AES-128-CBC" | 16 | 16 | PKCS#7 padding |
c"AES-256-CBC" | 32 | 16 | PKCS#7 padding |
c"AES-128-GCM" | 16 | 12 | AEAD |
c"AES-256-GCM" | 32 | 12 | AEAD |
c"AES-256-CTR" | 32 | 16 | Stream mode |
c"ChaCha20-Poly1305" | 32 | 12 | AEAD |
c"AES-128-CCM" | 16 | 7–13 | AEAD; tag length required |
Examples
AES-256-GCM (Encrypt)
#![allow(unused)]
fn main() {
use native_ossl::cipher::{AeadEncryptCtx, AeadDecryptCtx, CipherAlg};
use native_ossl::rand::Rand;
let alg = CipherAlg::fetch(c"AES-256-GCM", None)?;
assert_eq!(alg.key_len(), 32);
assert_eq!(alg.iv_len(), 12);
let mut key = [0u8; 32];
let mut nonce = [0u8; 12];
Rand::fill(&mut key)?;
Rand::fill(&mut nonce)?;
let plaintext = b"secret message";
// Encrypt
let mut enc = AeadEncryptCtx::new(&alg, &key, &nonce, None)?;
enc.set_aad(b"additional data")?;
let mut ciphertext = vec![0u8; plaintext.len()];
let n = enc.update(plaintext, &mut ciphertext)?;
enc.finalize(&mut ciphertext[n..])?;
let mut tag = [0u8; 16];
enc.tag(&mut tag)?;
// Decrypt
let mut dec = AeadDecryptCtx::new(&alg, &key, &nonce, None)?;
dec.set_aad(b"additional data")?;
dec.set_tag(&tag)?;
let mut recovered = vec![0u8; ciphertext.len()];
let n = dec.update(&ciphertext, &mut recovered)?;
dec.finalize(&mut recovered[n..])?;
assert_eq!(&recovered, plaintext);
}
AES-256-CBC (Non-AEAD)
#![allow(unused)]
fn main() {
let alg = CipherAlg::fetch(c"AES-256-CBC", None)?;
assert!(!alg.is_aead());
let key = [0u8; 32];
let iv = [0u8; 16];
let mut enc = alg.encrypt(&key, &iv, None)?;
let plaintext = b"sixteen bytes!!!";
let mut ciphertext = vec![0u8; plaintext.len() + alg.block_size()];
let n = enc.update(plaintext, &mut ciphertext)?;
let m = enc.finalize(&mut ciphertext[n..])?;
ciphertext.truncate(n + m);
}
Design Notes
- Key and IV are always copied —
EVP_EncryptInit_ex2copies key material into the C context for key scheduling and eventual zeroization. The slices may be dropped immediately after construction. - Output buffer sizing — for
update, the caller must provide at leastinput.len() + block_size - 1bytes. When the exact output size is unknown, useupdate_to_vec. finalizemust be called — even for stream ciphers and GCM (which write 0 bytes),finalizeadvances the context into its final state and is required before callingtag()on the encrypt side.- Tag order — for
AeadDecryptCtx, callset_tagbeforefinalize, not after.finalizeperforms the authentication check. push_size(key, 0)getter pattern —EVP_CIPHER_CTX_get_paramsfills a caller-providedOSSL_PARAMarray rather than returning a value directly. To use it, build aParamsarray with a placeholder value (0) for each key you want to query (push_size(c"taglen", 0)), callget_params(or a typed helper such asaead_tag_len), then read the result withget_size_t. OpenSSL overwrites the placeholder in-place. This is the standardOSSL_PARAMgetter protocol for all context-levelget_paramscalls in OpenSSL 3.x.