Skip to main content

arcana/cipher/
ctx.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Cédric Mesnil <cslashm@pm.me>
3
4// Index-based loops in this module deliberately mirror the reference
5// algorithm (parallel-array access `a[i]`/`b[i]`, or a position-indexed
6// packing/reduction state) and keep the constant-time data layout explicit;
7// the `iter().enumerate()` rewrite would obscure it. Documented, single-lint
8// allow per CLAUDE.md §6 (scoped to this module, not a blanket suppression).
9#![allow(clippy::needless_range_loop)]
10
11//! Stateful streaming cipher context (init / update / finalize) with
12//! padding, on top of the existing block-cipher and mode primitives.
13//!
14//! This module provides the [`Cipher`] object that wraps the block
15//! ciphers (AES, DES, 3DES) and the unauthenticated modes (ECB, CBC,
16//! CTR) into a single uniform `init / update / finalize` API. The
17//! design intent is to mirror what OpenSSL's `EVP_CIPHER_CTX` and PSA
18//! Crypto's `psa_cipher_operation_t` offer, while keeping all buffers
19//! caller-provided so the implementation is suitable for embedded
20//! targets without an allocator.
21//!
22//! AEAD modes (GCM, CCM, ChaCha20-Poly1305) are intentionally **not**
23//! routed through this object; they remain function-oriented in
24//! their own modules to avoid the trap of releasing unverified
25//! plaintext during streaming decryption.
26//!
27//! # Cycle of life
28//!
29//! ```text
30//!   new(algo, mode, padding)
31//!         │
32//!         ▼
33//!   init(direction, key, iv) ◄──┐  (may be called again to rekey
34//!         │                     │   or change direction)
35//!         ▼                     │
36//!   update(input, output) ──────┘
37//!         │   (0..N times)
38//!         ▼
39//!   finalize(output)
40//! ```
41//!
42//! # Buffer ownership
43//!
44//! Both `update` and `finalize` write into a caller-provided output
45//! slice and return the number of bytes actually written. Callers can
46//! query a safe upper bound for the output size with
47//! [`Cipher::update_output_size`] / [`Cipher::finalize_output_size`]
48//! before sizing the buffer. Convenience helpers
49//! [`Cipher::update_to_vec`] / [`Cipher::finalize_to_vec`] are
50//! provided for callers that prefer allocation.
51//!
52//! # Example: AES-256-CBC with PKCS#7 padding
53//!
54//! ```rust,ignore
55//! use arcana::cipher::ctx::{Cipher, Algorithm, Mode, Padding, Direction};
56//!
57//! let key = [0u8; 32];
58//! let iv  = [0u8; 16];
59//!
60//! let mut c = Cipher::new(Algorithm::Aes256, Mode::Cbc, Padding::Pkcs7).unwrap();
61//! c.init(Direction::Encrypt, &key, &iv).unwrap();
62//!
63//! let mut out = vec![0u8; 64];
64//! let mut written = 0;
65//! written += c.update(b"hello, ", &mut out[written..]).unwrap();
66//! written += c.update(b"world!",  &mut out[written..]).unwrap();
67//! written += c.finalize(&mut out[written..]).unwrap();
68//! out.truncate(written);
69//! ```
70
71use crate::BlockCipher;
72use crate::cipher::aes::Aes;
73use crate::cipher::des::{Des, TripleDes};
74
75// ============================================================
76// Public enums
77// ============================================================
78
79/// Symmetric block cipher selection for [`Cipher`].
80#[derive(Clone, Copy, Debug, PartialEq, Eq)]
81pub enum Algorithm {
82    /// AES with a 128-bit key (FIPS 197). 16-byte block.
83    Aes128,
84    /// AES with a 192-bit key (FIPS 197). 16-byte block.
85    Aes192,
86    /// AES with a 256-bit key (FIPS 197). 16-byte block.
87    Aes256,
88    /// Single DES (FIPS 46-3). 8-byte block, 8-byte key (56 effective bits).
89    /// **Legacy / cryptographically broken**: shipped for interop only.
90    Des,
91    /// Triple DES (FIPS 46-3, EDE3). 8-byte block, 24-byte key.
92    /// **Legacy / deprecated**: shipped for interop only.
93    TripleDes,
94}
95
96/// Mode of operation selection for [`Cipher`].
97#[derive(Clone, Copy, Debug, PartialEq, Eq)]
98pub enum Mode {
99    /// Electronic Codebook (NIST SP 800-38A §6.1). Insecure for any
100    /// general use; ships for interop / test-vector replay only.
101    Ecb,
102    /// Cipher Block Chaining (NIST SP 800-38A §6.2).
103    Cbc,
104    /// Counter mode (NIST SP 800-38A §6.5). Padding **must** be
105    /// [`Padding::None`]; output length always equals input length.
106    Ctr,
107}
108
109/// Padding scheme used at the end of an [`Cipher`] block-mode stream.
110///
111/// Padding only applies to ECB and CBC. CTR mode requires
112/// [`Padding::None`] and the constructor will return
113/// [`Error::InvalidPaddingForMode`] otherwise.
114#[derive(Clone, Copy, Debug, PartialEq, Eq)]
115pub enum Padding {
116    /// No padding. Caller must feed a multiple of the block size in
117    /// total; otherwise `finalize` returns [`Error::UnpaddedInput`].
118    None,
119    /// PKCS#7 padding (RFC 5652 §6.3). Pads with `n` copies of the
120    /// byte `n`, where `n` is the number of bytes added (1..=block).
121    /// Always adds at least one byte. Equivalent to JCE's
122    /// `PKCS5Padding` for 8-byte blocks.
123    Pkcs7,
124    /// ISO/IEC 9797-1 Padding Method 1: append `0x00` bytes until the
125    /// block is full. Adds **zero** bytes if the input is already
126    /// block-aligned, which makes the operation lossy on decryption
127    /// (trailing zero bytes of the original message are
128    /// indistinguishable from padding). Also known as **zero
129    /// padding**.
130    Iso9797M1,
131    /// ISO/IEC 9797-1 Padding Method 2: append `0x80` followed by as
132    /// many `0x00` bytes as needed to fill the block. Always adds at
133    /// least one byte. Identical to ISO/IEC 7816-4 padding (also
134    /// called "bit padding").
135    Iso9797M2,
136    /// ANSI X9.23 padding: append `0x00` bytes followed by a final
137    /// byte holding the padding length. Always adds at least one
138    /// byte. Legacy; ships for interop with X9.23-encoded data.
139    AnsiX923,
140}
141
142/// Direction selection for [`Cipher::init`].
143#[derive(Clone, Copy, Debug, PartialEq, Eq)]
144pub enum Direction {
145    /// Encrypt plaintext to ciphertext.
146    Encrypt,
147    /// Decrypt ciphertext to plaintext.
148    Decrypt,
149}
150
151/// Errors returned by [`Cipher`].
152#[derive(Clone, Copy, Debug, PartialEq, Eq)]
153pub enum Error {
154    /// The supplied key length does not match the algorithm.
155    InvalidKeyLen,
156    /// The supplied IV length does not match the mode's block size.
157    InvalidIvLen,
158    /// `Padding::None` is required for stream-shaped modes (CTR), and
159    /// padded modes are required for block-shaped modes (ECB, CBC) if
160    /// the caller wants to feed unaligned data.
161    InvalidPaddingForMode,
162    /// Caller-provided output buffer is too small for the bytes that
163    /// the call would have produced. The `needed` field reports the
164    /// total number of bytes the call would have written.
165    OutputBufferTooSmall {
166        /// The total number of output bytes the call would have written.
167        needed: usize,
168    },
169    /// `update` / `finalize` was called before [`Cipher::init`].
170    NotInitialized,
171    /// `update` / `init` was called after [`Cipher::finalize`] without
172    /// re-initializing.
173    AlreadyFinalized,
174    /// Encrypting or decrypting with [`Padding::None`] but the total
175    /// input was not a multiple of the block size.
176    UnpaddedInput,
177    /// Decrypted padding bytes do not match the declared padding
178    /// scheme.
179    BadPadding,
180}
181
182// ============================================================
183// Internal state
184// ============================================================
185
186const MAX_BLOCK: usize = 16;
187
188/// Algorithm-specific key schedule, set up at [`Cipher::init`] time.
189enum Key {
190    None,
191    Aes(Aes),
192    Des(Des),
193    TripleDes(TripleDes),
194}
195
196impl Key {
197    fn encrypt_block(&self, blk: &mut [u8]) {
198        match self {
199            Key::None => unreachable!("encrypt_block on uninitialised Cipher"),
200            Key::Aes(c) => c.encrypt_block(blk),
201            Key::Des(c) => c.encrypt_block(blk),
202            Key::TripleDes(c) => c.encrypt_block(blk),
203        }
204    }
205
206    fn decrypt_block(&self, blk: &mut [u8]) {
207        match self {
208            Key::None => unreachable!("decrypt_block on uninitialised Cipher"),
209            Key::Aes(c) => c.decrypt_block(blk),
210            Key::Des(c) => c.decrypt_block(blk),
211            Key::TripleDes(c) => c.decrypt_block(blk),
212        }
213    }
214}
215
216// ============================================================
217// Cipher object
218// ============================================================
219
220/// Stateful symmetric cipher context.
221///
222/// See the [module-level documentation](self) for the cycle of life
223/// and the buffer ownership model.
224pub struct Cipher {
225    algo: Algorithm,
226    mode: Mode,
227    padding: Padding,
228    block_len: usize,
229
230    key: Key,
231    direction: Option<Direction>,
232
233    /// For CBC: previous ciphertext block (or IV before the first
234    /// block). For CTR: current counter block. Unused for ECB.
235    iv: [u8; MAX_BLOCK],
236
237    /// CTR keystream buffer and read position. `ks_pos == block_len`
238    /// means the next byte requires a fresh keystream block.
239    ctr_ks: [u8; MAX_BLOCK],
240    ctr_ks_pos: usize,
241
242    /// Input reliquat / look-ahead buffer (block-mode only).
243    ///
244    /// At any moment between `update` calls, `buf[..buf_len]` holds
245    /// the most recent input bytes that have not yet been encrypted
246    /// or decrypted. The buffer can be larger than one block on
247    /// padded decryption, where one full ciphertext block is held
248    /// back so that `finalize` can strip the padding.
249    buf: [u8; 2 * MAX_BLOCK],
250    buf_len: usize,
251
252    /// True once `finalize` has run successfully.
253    finalized: bool,
254}
255
256impl Cipher {
257    /// Create a new cipher context with the given algorithm, mode and
258    /// padding scheme. Validates that the combination is supported;
259    /// the actual key and IV are loaded later by [`Self::init`].
260    ///
261    /// # Errors
262    ///
263    /// Returns [`Error::InvalidPaddingForMode`] when the requested
264    /// padding is incompatible with the requested mode (e.g. any
265    /// padding other than `None` with `Mode::Ctr`).
266    pub fn new(algo: Algorithm, mode: Mode, padding: Padding) -> Result<Self, Error> {
267        let block_len = match algo {
268            Algorithm::Aes128 | Algorithm::Aes192 | Algorithm::Aes256 => 16,
269            Algorithm::Des | Algorithm::TripleDes => 8,
270        };
271
272        // CTR is a stream mode: padding makes no sense.
273        if matches!(mode, Mode::Ctr) && !matches!(padding, Padding::None) {
274            return Err(Error::InvalidPaddingForMode);
275        }
276
277        Ok(Self {
278            algo,
279            mode,
280            padding,
281            block_len,
282            key: Key::None,
283            direction: None,
284            iv: [0u8; MAX_BLOCK],
285            ctr_ks: [0u8; MAX_BLOCK],
286            ctr_ks_pos: MAX_BLOCK, // forces refill on first byte
287            buf: [0u8; 2 * MAX_BLOCK],
288            buf_len: 0,
289            finalized: false,
290        })
291    }
292
293    /// Initialise (or re-initialise) the context with a key, IV and
294    /// direction. Loading a new key wipes any pending input from a
295    /// previous run, so the same `Cipher` object can be reused across
296    /// many independent encryptions or decryptions.
297    ///
298    /// `iv` must be `block_len()` bytes long for CBC and CTR; for ECB
299    /// the slice is ignored but a `&[]` is the conventional choice.
300    ///
301    /// # Errors
302    ///
303    /// - [`Error::InvalidKeyLen`] if `key.len()` does not match the
304    ///   algorithm.
305    /// - [`Error::InvalidIvLen`] if `iv.len()` does not match the
306    ///   block size for a mode that requires an IV.
307    pub fn init(&mut self, direction: Direction, key: &[u8], iv: &[u8]) -> Result<(), Error> {
308        // Validate key length up front.
309        let expected_key_len = match self.algo {
310            Algorithm::Aes128 => 16,
311            Algorithm::Aes192 => 24,
312            Algorithm::Aes256 => 32,
313            Algorithm::Des => 8,
314            Algorithm::TripleDes => 24,
315        };
316        if key.len() != expected_key_len {
317            return Err(Error::InvalidKeyLen);
318        }
319
320        // Validate IV length per mode.
321        match self.mode {
322            Mode::Ecb => { /* iv ignored */ }
323            Mode::Cbc | Mode::Ctr => {
324                if iv.len() != self.block_len {
325                    return Err(Error::InvalidIvLen);
326                }
327            }
328        }
329
330        // Build the key schedule.
331        self.key = match self.algo {
332            Algorithm::Aes128 | Algorithm::Aes192 | Algorithm::Aes256 => Key::Aes(Aes::new(key)),
333            Algorithm::Des => Key::Des(Des::new(key)),
334            Algorithm::TripleDes => Key::TripleDes(TripleDes::new(key)),
335        };
336
337        // Reset mode state.
338        self.iv = [0u8; MAX_BLOCK];
339        if matches!(self.mode, Mode::Cbc | Mode::Ctr) {
340            self.iv[..self.block_len].copy_from_slice(iv);
341        }
342        self.ctr_ks = [0u8; MAX_BLOCK];
343        self.ctr_ks_pos = self.block_len; // empty keystream → refill on first byte
344        self.buf = [0u8; 2 * MAX_BLOCK];
345        self.buf_len = 0;
346        self.direction = Some(direction);
347        self.finalized = false;
348
349        Ok(())
350    }
351
352    /// Block size of the underlying cipher in bytes (16 for AES, 8
353    /// for DES / 3DES).
354    pub const fn block_len(&self) -> usize {
355        self.block_len
356    }
357
358    /// IV / nonce length expected by [`Self::init`] for the configured
359    /// mode. Returns 0 for ECB.
360    pub const fn iv_len(&self) -> usize {
361        match self.mode {
362            Mode::Ecb => 0,
363            Mode::Cbc | Mode::Ctr => self.block_len,
364        }
365    }
366
367    /// Safe upper bound on the number of bytes a single
368    /// [`Self::update`] call would write into `output`, given an
369    /// `input_len`-byte input. Use this to size caller-provided
370    /// buffers.
371    pub fn update_output_size(&self, input_len: usize) -> usize {
372        match self.mode {
373            Mode::Ctr => input_len,
374            Mode::Ecb | Mode::Cbc => {
375                // At most everything we have buffered plus the new
376                // input, rounded up to a full block.
377                let total = self.buf_len + input_len;
378                if total < self.block_len {
379                    0
380                } else {
381                    // Worst case: emit floor(total / block_len) blocks.
382                    (total / self.block_len) * self.block_len
383                }
384            }
385        }
386    }
387
388    /// Safe upper bound on the number of bytes [`Self::finalize`]
389    /// would write into `output`. Always at most one block.
390    pub const fn finalize_output_size(&self) -> usize {
391        match self.mode {
392            Mode::Ctr => 0,
393            Mode::Ecb | Mode::Cbc => self.block_len,
394        }
395    }
396
397    /// Feed `input` bytes through the cipher and write any complete
398    /// output bytes into `output`. Returns the number of bytes
399    /// actually written.
400    ///
401    /// Either slice may be empty. Bytes that don't yet form a full
402    /// output block stay buffered inside the `Cipher` until enough
403    /// data arrives or [`Self::finalize`] is called.
404    ///
405    /// # Errors
406    ///
407    /// - [`Error::NotInitialized`] if [`Self::init`] has not been called.
408    /// - [`Error::AlreadyFinalized`] if [`Self::finalize`] has already run.
409    /// - [`Error::OutputBufferTooSmall`] if `output` is shorter than
410    ///   the number of bytes the call would write.
411    pub fn update(&mut self, input: &[u8], output: &mut [u8]) -> Result<usize, Error> {
412        let dir = self.direction.ok_or(Error::NotInitialized)?;
413        if self.finalized {
414            return Err(Error::AlreadyFinalized);
415        }
416
417        match self.mode {
418            Mode::Ctr => self.update_ctr(input, output),
419            Mode::Ecb | Mode::Cbc => self.update_block(dir, input, output),
420        }
421    }
422
423    /// Finish the operation: pad and emit the trailing block (encrypt)
424    /// or strip the padding from the last buffered ciphertext block
425    /// (decrypt). After this call the context is in a finalized state
426    /// and must be re-initialised with [`Self::init`] before further
427    /// use.
428    ///
429    /// # Errors
430    ///
431    /// - [`Error::NotInitialized`] if [`Self::init`] has not been called.
432    /// - [`Error::AlreadyFinalized`] if `finalize` has already run.
433    /// - [`Error::OutputBufferTooSmall`] if `output` is too small.
434    /// - [`Error::UnpaddedInput`] if `Padding::None` was selected and
435    ///   the total input length is not a multiple of the block size.
436    /// - [`Error::BadPadding`] (decryption only) if the trailing
437    ///   plaintext does not match the declared padding scheme.
438    pub fn finalize(&mut self, output: &mut [u8]) -> Result<usize, Error> {
439        let dir = self.direction.ok_or(Error::NotInitialized)?;
440        if self.finalized {
441            return Err(Error::AlreadyFinalized);
442        }
443
444        let written = match self.mode {
445            Mode::Ctr => {
446                // Stream mode: nothing buffered, nothing to flush.
447                0
448            }
449            Mode::Ecb | Mode::Cbc => match dir {
450                Direction::Encrypt => self.finalize_block_encrypt(output)?,
451                Direction::Decrypt => self.finalize_block_decrypt(output)?,
452            },
453        };
454
455        self.finalized = true;
456        Ok(written)
457    }
458
459    // --------------------------------------------------------
460    // CTR mode update path
461    // --------------------------------------------------------
462
463    fn update_ctr(&mut self, input: &[u8], output: &mut [u8]) -> Result<usize, Error> {
464        if output.len() < input.len() {
465            return Err(Error::OutputBufferTooSmall { needed: input.len() });
466        }
467        let bs = self.block_len;
468        for i in 0..input.len() {
469            if self.ctr_ks_pos == bs {
470                // Generate next keystream block from the current counter,
471                // then increment the counter (rightmost 64 bits, big-endian).
472                self.ctr_ks[..bs].copy_from_slice(&self.iv[..bs]);
473                self.key.encrypt_block(&mut self.ctr_ks[..bs]);
474                ctr_increment(&mut self.iv[..bs]);
475                self.ctr_ks_pos = 0;
476            }
477            output[i] = input[i] ^ self.ctr_ks[self.ctr_ks_pos];
478            self.ctr_ks_pos += 1;
479        }
480        Ok(input.len())
481    }
482
483    // --------------------------------------------------------
484    // Block-mode (ECB / CBC) update path
485    // --------------------------------------------------------
486
487    /// Number of bytes we must keep buffered until `finalize` so that
488    /// the padding can be stripped on decryption. Zero in every other
489    /// configuration.
490    fn keep_back(&self, dir: Direction) -> usize {
491        if matches!(dir, Direction::Decrypt) && !matches!(self.padding, Padding::None) {
492            self.block_len
493        } else {
494            0
495        }
496    }
497
498    fn update_block(&mut self, dir: Direction, input: &[u8], output: &mut [u8]) -> Result<usize, Error> {
499        let bs = self.block_len;
500        let kb = self.keep_back(dir);
501        let mut written = 0usize;
502        let mut in_pos = 0usize;
503        let cap = 2 * bs;
504
505        while in_pos < input.len() {
506            // 1. Fill the internal buffer as much as it can hold.
507            let space = cap - self.buf_len;
508            if space > 0 {
509                let take = space.min(input.len() - in_pos);
510                self.buf[self.buf_len..self.buf_len + take].copy_from_slice(&input[in_pos..in_pos + take]);
511                self.buf_len += take;
512                in_pos += take;
513            }
514
515            // 2. Drain as many full blocks as we can while still
516            //    leaving `kb` bytes behind for finalize.
517            while self.buf_len >= bs && self.buf_len - bs >= kb {
518                if output.len() - written < bs {
519                    return Err(Error::OutputBufferTooSmall { needed: written + bs });
520                }
521                let mut blk = [0u8; MAX_BLOCK];
522                blk[..bs].copy_from_slice(&self.buf[..bs]);
523                self.process_one_block(dir, &mut blk[..bs]);
524                output[written..written + bs].copy_from_slice(&blk[..bs]);
525                written += bs;
526                // Shift remaining bytes to the start of buf.
527                self.buf.copy_within(bs..self.buf_len, 0);
528                self.buf_len -= bs;
529            }
530        }
531
532        Ok(written)
533    }
534
535    /// Process exactly one block in-place, applying the mode's
536    /// chaining if any.
537    fn process_one_block(&mut self, dir: Direction, blk: &mut [u8]) {
538        let bs = self.block_len;
539        match (self.mode, dir) {
540            (Mode::Ecb, Direction::Encrypt) => {
541                self.key.encrypt_block(blk);
542            }
543            (Mode::Ecb, Direction::Decrypt) => {
544                self.key.decrypt_block(blk);
545            }
546            (Mode::Cbc, Direction::Encrypt) => {
547                for i in 0..bs {
548                    blk[i] ^= self.iv[i];
549                }
550                self.key.encrypt_block(blk);
551                self.iv[..bs].copy_from_slice(&blk[..bs]);
552            }
553            (Mode::Cbc, Direction::Decrypt) => {
554                let ct_copy = {
555                    let mut tmp = [0u8; MAX_BLOCK];
556                    tmp[..bs].copy_from_slice(&blk[..bs]);
557                    tmp
558                };
559                self.key.decrypt_block(blk);
560                for i in 0..bs {
561                    blk[i] ^= self.iv[i];
562                }
563                self.iv[..bs].copy_from_slice(&ct_copy[..bs]);
564            }
565            (Mode::Ctr, _) => unreachable!("CTR uses update_ctr"),
566        }
567    }
568
569    // --------------------------------------------------------
570    // Finalize: encrypt path
571    // --------------------------------------------------------
572
573    fn finalize_block_encrypt(&mut self, output: &mut [u8]) -> Result<usize, Error> {
574        let bs = self.block_len;
575
576        match self.padding {
577            Padding::None => {
578                if self.buf_len != 0 {
579                    return Err(Error::UnpaddedInput);
580                }
581                Ok(0)
582            }
583            Padding::Pkcs7 | Padding::Iso9797M2 | Padding::Iso9797M1 | Padding::AnsiX923 => {
584                // Iso9797M1 = zero padding: if already aligned, no padding
585                // is added at all.
586                if matches!(self.padding, Padding::Iso9797M1) && self.buf_len == 0 {
587                    return Ok(0);
588                }
589
590                if output.len() < bs {
591                    return Err(Error::OutputBufferTooSmall { needed: bs });
592                }
593                let pad_len = bs - self.buf_len; // 1..=bs for the others
594                apply_padding(self.padding, &mut self.buf[..bs], self.buf_len, pad_len);
595                self.buf_len = bs;
596
597                let mut blk = [0u8; MAX_BLOCK];
598                blk[..bs].copy_from_slice(&self.buf[..bs]);
599                self.process_one_block(Direction::Encrypt, &mut blk[..bs]);
600                output[..bs].copy_from_slice(&blk[..bs]);
601                self.buf_len = 0;
602                Ok(bs)
603            }
604        }
605    }
606
607    // --------------------------------------------------------
608    // Finalize: decrypt path
609    // --------------------------------------------------------
610
611    fn finalize_block_decrypt(&mut self, output: &mut [u8]) -> Result<usize, Error> {
612        let bs = self.block_len;
613
614        match self.padding {
615            Padding::None => {
616                if self.buf_len != 0 {
617                    return Err(Error::UnpaddedInput);
618                }
619                Ok(0)
620            }
621            _ => {
622                // We must have exactly one full block buffered (the
623                // reserved last ciphertext block). If buf_len == 0 the
624                // caller fed nothing at all.
625                if self.buf_len != bs {
626                    return Err(Error::UnpaddedInput);
627                }
628                let mut blk = [0u8; MAX_BLOCK];
629                blk[..bs].copy_from_slice(&self.buf[..bs]);
630                self.process_one_block(Direction::Decrypt, &mut blk[..bs]);
631
632                let unpadded = strip_padding(self.padding, &blk[..bs])?;
633                if output.len() < unpadded {
634                    return Err(Error::OutputBufferTooSmall { needed: unpadded });
635                }
636                output[..unpadded].copy_from_slice(&blk[..unpadded]);
637                self.buf_len = 0;
638                Ok(unpadded)
639            }
640        }
641    }
642
643    // --------------------------------------------------------
644    // Allocating helpers
645    // --------------------------------------------------------
646
647    /// Convenience wrapper around [`Self::update`] that returns the
648    /// freshly produced bytes as a `Vec<u8>`. Allocates.
649    pub fn update_to_vec(&mut self, input: &[u8]) -> Result<Vec<u8>, Error> {
650        let upper = self.update_output_size(input.len());
651        let mut out = vec![0u8; upper];
652        let n = self.update(input, &mut out)?;
653        out.truncate(n);
654        Ok(out)
655    }
656
657    /// Convenience wrapper around [`Self::finalize`] that returns the
658    /// trailing bytes as a `Vec<u8>`. Allocates.
659    pub fn finalize_to_vec(&mut self) -> Result<Vec<u8>, Error> {
660        let upper = self.finalize_output_size();
661        let mut out = vec![0u8; upper];
662        let n = self.finalize(&mut out)?;
663        out.truncate(n);
664        Ok(out)
665    }
666}
667
668// ============================================================
669// Padding helpers
670// ============================================================
671
672/// Append `pad_len` padding bytes starting at offset `data_len` in
673/// `block`. The block must already be sized to one full block; the
674/// caller is responsible for `data_len + pad_len == block.len()`.
675fn apply_padding(padding: Padding, block: &mut [u8], data_len: usize, pad_len: usize) {
676    let bs = block.len();
677    debug_assert_eq!(data_len + pad_len, bs);
678    match padding {
679        Padding::None => {}
680        Padding::Pkcs7 => {
681            for b in &mut block[data_len..bs] {
682                *b = pad_len as u8;
683            }
684        }
685        Padding::Iso9797M1 => {
686            for b in &mut block[data_len..bs] {
687                *b = 0x00;
688            }
689        }
690        Padding::Iso9797M2 => {
691            block[data_len] = 0x80;
692            for b in &mut block[data_len + 1..bs] {
693                *b = 0x00;
694            }
695        }
696        Padding::AnsiX923 => {
697            for b in &mut block[data_len..bs - 1] {
698                *b = 0x00;
699            }
700            block[bs - 1] = pad_len as u8;
701        }
702    }
703}
704
705/// Validate the padding bytes of a freshly decrypted last block and
706/// return the unpadded length.
707fn strip_padding(padding: Padding, block: &[u8]) -> Result<usize, Error> {
708    let bs = block.len();
709    match padding {
710        Padding::None => Ok(bs),
711        Padding::Pkcs7 => {
712            let pad_len = block[bs - 1] as usize;
713            if pad_len == 0 || pad_len > bs {
714                return Err(Error::BadPadding);
715            }
716            for &b in &block[bs - pad_len..bs] {
717                if b as usize != pad_len {
718                    return Err(Error::BadPadding);
719                }
720            }
721            Ok(bs - pad_len)
722        }
723        Padding::Iso9797M1 => {
724            // Zero padding is ambiguous: we cannot tell trailing zero
725            // bytes of the message from padding. We adopt the
726            // convention "strip all trailing zeros", which matches
727            // most ISO 9797-1 M1 deployments.
728            let mut len = bs;
729            while len > 0 && block[len - 1] == 0x00 {
730                len -= 1;
731            }
732            Ok(len)
733        }
734        Padding::Iso9797M2 => {
735            // Find the rightmost 0x80 preceded only by 0x00 bytes.
736            let mut i = bs;
737            while i > 0 {
738                i -= 1;
739                if block[i] == 0x80 {
740                    return Ok(i);
741                }
742                if block[i] != 0x00 {
743                    return Err(Error::BadPadding);
744                }
745            }
746            Err(Error::BadPadding)
747        }
748        Padding::AnsiX923 => {
749            let pad_len = block[bs - 1] as usize;
750            if pad_len == 0 || pad_len > bs {
751                return Err(Error::BadPadding);
752            }
753            for &b in &block[bs - pad_len..bs - 1] {
754                if b != 0x00 {
755                    return Err(Error::BadPadding);
756                }
757            }
758            Ok(bs - pad_len)
759        }
760    }
761}
762
763// ============================================================
764// CTR counter helper
765// ============================================================
766
767/// Increment the rightmost 64 bits of an n-byte counter (big-endian).
768/// Used by CTR mode update path.
769fn ctr_increment(counter: &mut [u8]) {
770    let n = counter.len();
771    let lo = n.saturating_sub(8);
772    for i in (lo..n).rev() {
773        let (v, carry) = counter[i].overflowing_add(1);
774        counter[i] = v;
775        if !carry {
776            return;
777        }
778    }
779}
780
781// ============================================================
782// Tests
783// ============================================================
784
785#[cfg(test)]
786mod tests {
787    use super::*;
788    use crate::BlockCipher;
789    use crate::cipher::aes::Aes128;
790    use crate::cipher::modes::{cbc_decrypt, cbc_encrypt, ctr_encrypt, ecb_encrypt};
791
792    fn hex(s: &str) -> Vec<u8> {
793        (0..s.len())
794            .step_by(2)
795            .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
796            .collect()
797    }
798
799    // ---------- ECB ----------
800
801    #[test]
802    fn ecb_aes128_no_padding_round_trip() {
803        let key = hex("2b7e151628aed2a6abf7158809cf4f3c");
804        let pt = hex("6bc1bee22e409f96e93d7e117393172a"); // 16 bytes
805
806        let mut enc = Cipher::new(Algorithm::Aes128, Mode::Ecb, Padding::None).unwrap();
807        enc.init(Direction::Encrypt, &key, &[]).unwrap();
808        let ct = {
809            let mut out = vec![0u8; 32];
810            let n = enc.update(&pt, &mut out).unwrap();
811            let m = enc.finalize(&mut out[n..]).unwrap();
812            out.truncate(n + m);
813            out
814        };
815
816        let mut dec = Cipher::new(Algorithm::Aes128, Mode::Ecb, Padding::None).unwrap();
817        dec.init(Direction::Decrypt, &key, &[]).unwrap();
818        let mut got = vec![0u8; 32];
819        let n = dec.update(&ct, &mut got).unwrap();
820        let m = dec.finalize(&mut got[n..]).unwrap();
821        got.truncate(n + m);
822        assert_eq!(got, pt);
823    }
824
825    #[test]
826    fn ecb_aes128_pkcs7_unaligned() {
827        let key = [0x42u8; 16];
828        let pt: Vec<u8> = (0u8..30).collect(); // 30 bytes → 2 blocks of CT
829
830        // Reference: pad with PKCS#7 by hand and call ecb_encrypt.
831        let mut padded = pt.clone();
832        let pad = 16 - (padded.len() % 16);
833        padded.extend(std::iter::repeat_n(pad as u8, pad));
834        let cipher_ref = Aes128::new(&key);
835        ecb_encrypt(&cipher_ref, &mut padded);
836        let ct_ref = padded;
837
838        // Round-trip via Cipher.
839        let mut enc = Cipher::new(Algorithm::Aes128, Mode::Ecb, Padding::Pkcs7).unwrap();
840        enc.init(Direction::Encrypt, &key, &[]).unwrap();
841        let ct = {
842            let mut out = vec![0u8; 64];
843            let n = enc.update(&pt, &mut out).unwrap();
844            let m = enc.finalize(&mut out[n..]).unwrap();
845            out.truncate(n + m);
846            out
847        };
848        assert_eq!(ct, ct_ref, "Cipher PKCS#7 ECB encrypt mismatch");
849
850        let mut dec = Cipher::new(Algorithm::Aes128, Mode::Ecb, Padding::Pkcs7).unwrap();
851        dec.init(Direction::Decrypt, &key, &[]).unwrap();
852        let pt_back = {
853            let mut out = vec![0u8; 64];
854            let n = dec.update(&ct, &mut out).unwrap();
855            let m = dec.finalize(&mut out[n..]).unwrap();
856            out.truncate(n + m);
857            out
858        };
859        assert_eq!(pt_back, pt);
860    }
861
862    // ---------- CBC ----------
863
864    #[test]
865    fn cbc_aes128_pkcs7_matches_modes_helper() {
866        let key = hex("2b7e151628aed2a6abf7158809cf4f3c");
867        let iv = hex("000102030405060708090a0b0c0d0e0f");
868        let pt: Vec<u8> = (0u8..37).collect(); // unaligned
869
870        // Reference path through cipher::modes::cbc_encrypt.
871        let mut padded = pt.clone();
872        let pad = 16 - (padded.len() % 16);
873        padded.extend(std::iter::repeat_n(pad as u8, pad));
874        let cipher_ref = Aes128::new(&key);
875        cbc_encrypt(&cipher_ref, &iv, &mut padded);
876        let ct_ref = padded;
877
878        let mut enc = Cipher::new(Algorithm::Aes128, Mode::Cbc, Padding::Pkcs7).unwrap();
879        enc.init(Direction::Encrypt, &key, &iv).unwrap();
880        let mut ct = vec![0u8; 64];
881        // Feed in two pieces to exercise the streaming path.
882        let mut w = enc.update(&pt[..10], &mut ct).unwrap();
883        w += enc.update(&pt[10..], &mut ct[w..]).unwrap();
884        w += enc.finalize(&mut ct[w..]).unwrap();
885        ct.truncate(w);
886        assert_eq!(ct, ct_ref, "Cipher CBC PKCS#7 encrypt mismatch");
887
888        // Decrypt via cbc_decrypt and via Cipher.
889        let mut ref_pt = ct.clone();
890        cbc_decrypt(&cipher_ref, &iv, &mut ref_pt);
891        // Strip PKCS#7 from the reference.
892        let pad = *ref_pt.last().unwrap() as usize;
893        ref_pt.truncate(ref_pt.len() - pad);
894        assert_eq!(ref_pt, pt);
895
896        let mut dec = Cipher::new(Algorithm::Aes128, Mode::Cbc, Padding::Pkcs7).unwrap();
897        dec.init(Direction::Decrypt, &key, &iv).unwrap();
898        let mut got = vec![0u8; 64];
899        let mut n = dec.update(&ct[..7], &mut got).unwrap();
900        n += dec.update(&ct[7..], &mut got[n..]).unwrap();
901        n += dec.finalize(&mut got[n..]).unwrap();
902        got.truncate(n);
903        assert_eq!(got, pt);
904    }
905
906    #[test]
907    fn cbc_aes128_none_padding_aligned() {
908        let key = hex("2b7e151628aed2a6abf7158809cf4f3c");
909        let iv = hex("000102030405060708090a0b0c0d0e0f");
910        let pt = hex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51");
911
912        let mut enc = Cipher::new(Algorithm::Aes128, Mode::Cbc, Padding::None).unwrap();
913        enc.init(Direction::Encrypt, &key, &iv).unwrap();
914        let mut ct = vec![0u8; 64];
915        let mut n = enc.update(&pt, &mut ct).unwrap();
916        n += enc.finalize(&mut ct[n..]).unwrap();
917        ct.truncate(n);
918
919        // Reference.
920        let cipher_ref = Aes128::new(&key);
921        let mut ct_ref = pt.clone();
922        cbc_encrypt(&cipher_ref, &iv, &mut ct_ref);
923        assert_eq!(ct, ct_ref);
924    }
925
926    #[test]
927    fn cbc_aes128_none_padding_unaligned_errors() {
928        let key = [0u8; 16];
929        let iv = [0u8; 16];
930        let mut enc = Cipher::new(Algorithm::Aes128, Mode::Cbc, Padding::None).unwrap();
931        enc.init(Direction::Encrypt, &key, &iv).unwrap();
932        let mut out = vec![0u8; 64];
933        enc.update(&[0u8; 13], &mut out).unwrap();
934        assert_eq!(enc.finalize(&mut out), Err(Error::UnpaddedInput));
935    }
936
937    // ---------- CTR ----------
938
939    #[test]
940    fn ctr_aes128_streaming_matches_one_shot() {
941        let key = hex("2b7e151628aed2a6abf7158809cf4f3c");
942        let nonce = hex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"); // 16-byte counter block
943        let pt = hex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51\
944             30c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710");
945
946        // Reference: cipher::modes::ctr_encrypt with shorter nonce; we
947        // need to mimic the CTR layout. For interoperability we use
948        // the full 16-byte counter block path: build the cipher_ref
949        // and step the counter manually.
950        let cipher_ref = Aes128::new(&key);
951        let mut ct_ref = pt.clone();
952        // Use ctr_encrypt with nonce of length < block to match its API:
953        // here we use nonce of 8 bytes, counter starts at 1 in the rest.
954        // For Cipher we will use the same equivalent counter block:
955        //   nonce[0..8] || 0x0000000000000001
956        let nonce8 = &nonce[..8];
957        ctr_encrypt(&cipher_ref, nonce8, &mut ct_ref);
958
959        // Build the matching 16-byte initial counter block for Cipher.
960        let mut iv = [0u8; 16];
961        iv[..8].copy_from_slice(nonce8);
962        iv[15] = 1;
963
964        let mut enc = Cipher::new(Algorithm::Aes128, Mode::Ctr, Padding::None).unwrap();
965        enc.init(Direction::Encrypt, &key, &iv).unwrap();
966        let mut ct = vec![0u8; pt.len()];
967        // Feed in awkward chunks.
968        let mut w = 0;
969        for chunk in pt.chunks(7) {
970            w += enc.update(chunk, &mut ct[w..]).unwrap();
971        }
972        w += enc.finalize(&mut ct[w..]).unwrap();
973        assert_eq!(w, pt.len());
974        assert_eq!(ct, ct_ref, "Cipher CTR streaming mismatch");
975
976        // Decrypt path (CTR is symmetric).
977        let mut dec = Cipher::new(Algorithm::Aes128, Mode::Ctr, Padding::None).unwrap();
978        dec.init(Direction::Decrypt, &key, &iv).unwrap();
979        let mut pt_back = vec![0u8; ct.len()];
980        let mut w = 0;
981        for chunk in ct.chunks(11) {
982            w += dec.update(chunk, &mut pt_back[w..]).unwrap();
983        }
984        w += dec.finalize(&mut pt_back[w..]).unwrap();
985        assert_eq!(w, ct.len());
986        assert_eq!(pt_back, pt);
987    }
988
989    // ---------- Padding variants ----------
990
991    fn pad_round_trip(padding: Padding, msg_len: usize) {
992        let key = [0x33u8; 16];
993        let iv = [0x77u8; 16];
994        let pt: Vec<u8> = (0..msg_len).map(|i| (i as u8).wrapping_mul(7)).collect();
995
996        let mut enc = Cipher::new(Algorithm::Aes128, Mode::Cbc, padding).unwrap();
997        enc.init(Direction::Encrypt, &key, &iv).unwrap();
998        let mut ct = vec![0u8; pt.len() + 16];
999        let mut n = enc.update(&pt, &mut ct).unwrap();
1000        n += enc.finalize(&mut ct[n..]).unwrap();
1001        ct.truncate(n);
1002
1003        let mut dec = Cipher::new(Algorithm::Aes128, Mode::Cbc, padding).unwrap();
1004        dec.init(Direction::Decrypt, &key, &iv).unwrap();
1005        let mut got = vec![0u8; ct.len() + 16];
1006        let mut n = dec.update(&ct, &mut got).unwrap();
1007        n += dec.finalize(&mut got[n..]).unwrap();
1008        got.truncate(n);
1009        assert_eq!(got, pt, "round-trip {:?} len={}", padding, msg_len);
1010    }
1011
1012    #[test]
1013    fn pkcs7_round_trips() {
1014        for len in &[0, 1, 15, 16, 17, 31, 32, 33, 100] {
1015            pad_round_trip(Padding::Pkcs7, *len);
1016        }
1017    }
1018
1019    #[test]
1020    fn iso9797_m2_round_trips() {
1021        for len in &[0, 1, 15, 16, 17, 31, 32, 33, 100] {
1022            pad_round_trip(Padding::Iso9797M2, *len);
1023        }
1024    }
1025
1026    #[test]
1027    fn ansix923_round_trips() {
1028        for len in &[1, 15, 16, 17, 31, 32, 33, 100] {
1029            pad_round_trip(Padding::AnsiX923, *len);
1030        }
1031    }
1032
1033    #[test]
1034    fn iso9797_m1_round_trips_when_no_trailing_zero() {
1035        // Zero padding can't survive trailing zero bytes; exercise it
1036        // only with messages that don't end in 0x00.
1037        let padding = Padding::Iso9797M1;
1038        let key = [0u8; 16];
1039        let iv = [0u8; 16];
1040
1041        for len in &[1usize, 5, 15, 17, 30] {
1042            let pt: Vec<u8> = (0..*len).map(|i| (i as u8) | 1).collect();
1043            let mut enc = Cipher::new(Algorithm::Aes128, Mode::Cbc, padding).unwrap();
1044            enc.init(Direction::Encrypt, &key, &iv).unwrap();
1045            let mut ct = vec![0u8; pt.len() + 16];
1046            let mut n = enc.update(&pt, &mut ct).unwrap();
1047            n += enc.finalize(&mut ct[n..]).unwrap();
1048            ct.truncate(n);
1049
1050            let mut dec = Cipher::new(Algorithm::Aes128, Mode::Cbc, padding).unwrap();
1051            dec.init(Direction::Decrypt, &key, &iv).unwrap();
1052            let mut got = vec![0u8; ct.len() + 16];
1053            let mut n = dec.update(&ct, &mut got).unwrap();
1054            n += dec.finalize(&mut got[n..]).unwrap();
1055            got.truncate(n);
1056            assert_eq!(got, pt);
1057        }
1058    }
1059
1060    // ---------- Error paths ----------
1061
1062    #[test]
1063    fn invalid_key_len_rejected() {
1064        let mut c = Cipher::new(Algorithm::Aes128, Mode::Cbc, Padding::Pkcs7).unwrap();
1065        assert_eq!(
1066            c.init(Direction::Encrypt, &[0u8; 17], &[0u8; 16]),
1067            Err(Error::InvalidKeyLen)
1068        );
1069    }
1070
1071    #[test]
1072    fn invalid_iv_len_rejected() {
1073        let mut c = Cipher::new(Algorithm::Aes128, Mode::Cbc, Padding::Pkcs7).unwrap();
1074        assert_eq!(
1075            c.init(Direction::Encrypt, &[0u8; 16], &[0u8; 8]),
1076            Err(Error::InvalidIvLen)
1077        );
1078    }
1079
1080    #[test]
1081    fn ctr_with_padding_rejected() {
1082        assert_eq!(
1083            Cipher::new(Algorithm::Aes128, Mode::Ctr, Padding::Pkcs7).err(),
1084            Some(Error::InvalidPaddingForMode)
1085        );
1086    }
1087
1088    #[test]
1089    fn output_too_small_reported() {
1090        let key = [0u8; 16];
1091        let iv = [0u8; 16];
1092        let mut enc = Cipher::new(Algorithm::Aes128, Mode::Cbc, Padding::Pkcs7).unwrap();
1093        enc.init(Direction::Encrypt, &key, &iv).unwrap();
1094        let mut tiny = [0u8; 4];
1095        let err = enc.update(&[0u8; 32], &mut tiny);
1096        assert!(matches!(err, Err(Error::OutputBufferTooSmall { .. })));
1097    }
1098
1099    #[test]
1100    fn bad_padding_detected() {
1101        // Encrypt some data, flip the last ciphertext byte, expect BadPadding.
1102        let key = [1u8; 16];
1103        let iv = [2u8; 16];
1104        let pt = b"hello, world! Some content here.";
1105
1106        let mut enc = Cipher::new(Algorithm::Aes128, Mode::Cbc, Padding::Pkcs7).unwrap();
1107        enc.init(Direction::Encrypt, &key, &iv).unwrap();
1108        let mut ct = vec![0u8; 64];
1109        let mut n = enc.update(pt, &mut ct).unwrap();
1110        n += enc.finalize(&mut ct[n..]).unwrap();
1111        ct.truncate(n);
1112        let last = ct.len() - 1;
1113        ct[last] ^= 0xFF;
1114
1115        let mut dec = Cipher::new(Algorithm::Aes128, Mode::Cbc, Padding::Pkcs7).unwrap();
1116        dec.init(Direction::Decrypt, &key, &iv).unwrap();
1117        let mut out = vec![0u8; 64];
1118        let n = dec.update(&ct, &mut out).unwrap();
1119        let r = dec.finalize(&mut out[n..]);
1120        assert_eq!(r, Err(Error::BadPadding));
1121    }
1122
1123    // ---------- DES / 3DES ----------
1124
1125    #[test]
1126    fn tripledes_cbc_pkcs7_round_trip() {
1127        let key = [0x11u8; 24];
1128        let iv = [0x22u8; 8];
1129        let pt: Vec<u8> = (0u8..23).collect();
1130
1131        let mut enc = Cipher::new(Algorithm::TripleDes, Mode::Cbc, Padding::Pkcs7).unwrap();
1132        enc.init(Direction::Encrypt, &key, &iv).unwrap();
1133        let mut ct = vec![0u8; 64];
1134        let mut n = enc.update(&pt, &mut ct).unwrap();
1135        n += enc.finalize(&mut ct[n..]).unwrap();
1136        ct.truncate(n);
1137        assert_eq!(ct.len() % 8, 0);
1138
1139        let mut dec = Cipher::new(Algorithm::TripleDes, Mode::Cbc, Padding::Pkcs7).unwrap();
1140        dec.init(Direction::Decrypt, &key, &iv).unwrap();
1141        let mut got = vec![0u8; 64];
1142        let mut n = dec.update(&ct, &mut got).unwrap();
1143        n += dec.finalize(&mut got[n..]).unwrap();
1144        got.truncate(n);
1145        assert_eq!(got, pt);
1146    }
1147
1148    #[test]
1149    fn reuse_after_init() {
1150        let key = [9u8; 16];
1151        let iv = [8u8; 16];
1152        let mut c = Cipher::new(Algorithm::Aes128, Mode::Cbc, Padding::Pkcs7).unwrap();
1153
1154        for &msg in &[b"first message".as_slice(), b"second", b"a third one!"] {
1155            c.init(Direction::Encrypt, &key, &iv).unwrap();
1156            let ct = {
1157                let mut out = vec![0u8; 64];
1158                let mut n = c.update(msg, &mut out).unwrap();
1159                n += c.finalize(&mut out[n..]).unwrap();
1160                out.truncate(n);
1161                out
1162            };
1163            c.init(Direction::Decrypt, &key, &iv).unwrap();
1164            let pt = {
1165                let mut out = vec![0u8; 64];
1166                let mut n = c.update(&ct, &mut out).unwrap();
1167                n += c.finalize(&mut out[n..]).unwrap();
1168                out.truncate(n);
1169                out
1170            };
1171            assert_eq!(pt, msg);
1172        }
1173    }
1174}