Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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. Either CipherCtx<Encrypt> or CipherCtx<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 stringKey (bytes)IV (bytes)Notes
c"AES-128-CBC"1616PKCS#7 padding
c"AES-256-CBC"3216PKCS#7 padding
c"AES-128-GCM"1612AEAD
c"AES-256-GCM"3212AEAD
c"AES-256-CTR"3216Stream mode
c"ChaCha20-Poly1305"3212AEAD
c"AES-128-CCM"167–13AEAD; 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 copiedEVP_EncryptInit_ex2 copies 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 least input.len() + block_size - 1 bytes. When the exact output size is unknown, use update_to_vec.
  • finalize must be called — even for stream ciphers and GCM (which write 0 bytes), finalize advances the context into its final state and is required before calling tag() on the encrypt side.
  • Tag order — for AeadDecryptCtx, call set_tag before finalize, not after. finalize performs the authentication check.
  • push_size(key, 0) getter patternEVP_CIPHER_CTX_get_params fills a caller-provided OSSL_PARAM array rather than returning a value directly. To use it, build a Params array with a placeholder value (0) for each key you want to query (push_size(c"taglen", 0)), call get_params (or a typed helper such as aead_tag_len), then read the result with get_size_t. OpenSSL overwrites the placeholder in-place. This is the standard OSSL_PARAM getter protocol for all context-level get_params calls in OpenSSL 3.x.