crypt_io/stream/mod.rs
1//! Streaming / file encryption.
2//!
3//! Chunked AEAD with a [STREAM-construction] frame format. Lets you
4//! encrypt data that doesn't fit in memory, transport it in pieces,
5//! and decrypt back to the original with the same authentication
6//! guarantees as the single-shot [`crate::Crypt`] surface — plus
7//! detection of chunk truncation, reordering, and duplication.
8//!
9//! [STREAM-construction]: https://eprint.iacr.org/2015/189.pdf
10//!
11//! # Quick API tour
12//!
13//! - [`StreamEncryptor`] — buffer plaintext, emit chunks of `chunk_size`
14//! ciphertext + 16 bytes of authentication tag.
15//! - [`StreamDecryptor`] — feed encrypted bytes, get plaintext as
16//! complete chunks decrypt.
17//! - [`encrypt_file`] / [`decrypt_file`] *(requires `std`)* — the
18//! common "encrypt this file into that file" workflow.
19//!
20//! # Wire format
21//!
22//! See [`frame`] for the on-the-wire layout: 24-byte header, then
23//! N-1 non-final chunks of `chunk_size + 16` bytes each, then 1
24//! final chunk of strictly less than `chunk_size + 16` bytes. The
25//! final chunk is always emitted (even if it carries zero plaintext)
26//! so the decoder can detect end-of-stream unambiguously.
27//!
28//! # Security properties
29//!
30//! - **Tampering** in any chunk → `Error::AuthenticationFailed` on
31//! that chunk's decrypt.
32//! - **Truncation** (cutting bytes off the end of the stream) →
33//! `Error::AuthenticationFailed` when the buffered "almost-final"
34//! chunk fails to verify under the `last_flag = 1` nonce.
35//! - **Reordering or duplicating chunks** → each chunk's nonce
36//! includes a 32-bit counter; swapping or repeating produces a
37//! counter mismatch and an authentication failure.
38//! - **Header tampering** (flipping the algorithm byte, the chunk
39//! size, or the nonce prefix) → the header bytes are bound into
40//! every chunk's AAD; tampering shows up as authentication failure
41//! on the first chunk.
42//! - **Wrong key** → authentication failure on the first chunk.
43//!
44//! # Example
45//!
46//! ```
47//! # #[cfg(all(feature = "stream", feature = "aead-chacha20"))] {
48//! use crypt_io::Algorithm;
49//! use crypt_io::stream::{StreamEncryptor, StreamDecryptor};
50//!
51//! let key = [0u8; 32];
52//! let plaintext = b"the quick brown fox jumps over the lazy dog".repeat(1000);
53//!
54//! // Encrypt
55//! let (mut enc, header) = StreamEncryptor::new(&key, Algorithm::ChaCha20Poly1305)?;
56//! let mut wire = header.to_vec();
57//! wire.extend(enc.update(&plaintext)?);
58//! wire.extend(enc.finalize()?);
59//!
60//! // Decrypt
61//! let mut dec = StreamDecryptor::new(&key, &wire[..24])?;
62//! let mut recovered = dec.update(&wire[24..])?;
63//! recovered.extend(dec.finalize()?);
64//!
65//! assert_eq!(recovered, plaintext);
66//! # }
67//! # Ok::<(), crypt_io::Error>(())
68//! ```
69
70// Crypto-style: pass fixed-size keys/nonces/prefixes by reference for
71// caller clarity, even when clippy thinks small arrays should be
72// pass-by-value. Matches the convention of every RustCrypto crate.
73#![allow(clippy::trivially_copy_pass_by_ref)]
74
75mod aead;
76mod decryptor;
77mod encryptor;
78pub mod frame;
79
80#[cfg(feature = "std")]
81mod file;
82
83pub use self::decryptor::StreamDecryptor;
84pub use self::encryptor::StreamEncryptor;
85
86#[cfg(feature = "std")]
87pub use self::file::{decrypt_file, encrypt_file};
88
89// Re-export the bits of the frame format that callers may want to
90// reason about. Keep the rest of `frame` crate-private — it's
91// implementation detail of the wire format.
92pub use self::frame::{
93 DEFAULT_CHUNK_SIZE_LOG2, HEADER_LEN, MAX_CHUNK_SIZE_LOG2, MIN_CHUNK_SIZE_LOG2, TAG_LEN,
94};