Skip to main content

arcana/cipher/
ctx.rs

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