(_Every part_ of this crate was _written by a human being._)
Philbin is a Rust library implementing the [AEGIS algorithm][aegis][^1] for
[symmetric-key encryption][sym] which was one of the winners of the [CAESAR
Competition][ceasar]. AEGIS is [_significantly_ faster](#benchmarks) and
more secure than other popular [AEAD] algorithms ([AES]-[GCM] and
[ChaCha20-Poly1305][chacha]) on [CPUs with AES hardware
acceleration][easy::is_hw_accelerated].
Philbin uses _runtime_ CPU detection to dispatch to the best [SIMD]
implementation that the CPU supports. There is _no need_ to use
`RUSTFLAGS="-C target-cpu=native"` or similar. CPU feature detection is
automatically performed _only once_ on the first call to Philbin and then
cached in memory.
A **quick API overview** is in the [Get Started with the `easy`
Module](#get-started-with-the-easy-module) section.
# API Organization
All Philbin APIs are in one of two modules:
- [`easy`] module with _simple, high-level_ APIs,
- [`careful`] module with _low-level_ APIs that are _harder to use_.
**We _strongly_ recommend using only [`easy`] APIs** as they are designed to
be maximally misuse-resistant. Using [`easy`] APIs means never having to
think about:
- Which AEGIS algorithm is "the best" for your use-case? Just use
[`easy::encrypt`] and [`easy::decrypt`]. They're at the "sweet spot" of
security and performance for almost every use-case and _never_ a bad
choice.
- Correct _[nonce]_ handling? [`easy`] APIs handle nonces internally. (Using
a nonce in a subtly wrong way is common and _catastrophic_ to security.)
- Secure random number generation? [`easy`] APIs internally use
[_cryptographically secure_ random number generators][csrng] provided by
your operating system and safe even if your app calls [`fork()`][fork] or
similar.
- Which _authentication tag_ size? [`easy`] APIs use 256 bits; maximally
secure at effectively no performance cost due to the design of AEGIS.
- Sensible payload framing for the nonce, ciphertext and auth tag?
[`easy::encrypt`] and [`easy::decrypt`] already handle it. You get a
[`Vec<u8>`] you can serialize anywhere.
- Etc.
# Get Started with the [`easy`] Module
use philbin::easy::*;
let plaintext = b"foo bar zoo";
let associated_data = b"moo goo";
let key = Key256::generate()?; // Or: Key256::new(), Key256::from_bytes()
let encrypted_payload = encrypt(
Plaintext::new(plaintext),
AssociatedData::new(associated_data), // Or: AssociatedData::EMPTY
&key)?;
let decrypted_plaintext = decrypt(
Ciphertext::new(&encrypted_payload),
AssociatedData::new(associated_data),
&key)?;
assert_eq!(plaintext.as_slice(), decrypted_plaintext);
# Ok::<(), Error>(()) // Makes the doctest compile
Notice the usage of [`Plaintext`][easy::Plaintext],
[`Ciphertext`][easy::Ciphertext] and
[`AssociatedData`][easy::AssociatedData] types. These are wrappers around
[`&[u8]`][slice] and [`&mut [u8]`][slice] that help prevent accidental
parameter swaps (e.g. like swapping _associated data_ and _plaintext_).
[`Key256`][easy::Key256] is merely an alias for [`Key<32>`][easy::Key] which
[`encrypt`][easy::encrypt] and [`decrypt`][easy::decrypt] use.
- [`Key::generate()`][easy::Key::generate] will securely create a brand new
[`Key`][easy::Key].
- [`Key::new()`][easy::Key::new] and
[`Key::from_bytes()`][easy::Key::from_bytes] can create one from an existing
byte array and slice, respectively.
- [`Key::expose_secret()`][easy::Key::expose_secret] provides access to the
key bytes.
**You now know everything you need** to use Philbin effectively. _You can
stop reading._
# The [`careful`] Module
The [`careful`] module provides direct, low-level access to each AEGIS
algorithm and should be used _with great care_.
## AEGIS Variants
[RFC 10032][aegis] defines six AEGIS encryption algorithm _variants_ which are
categorized in the following table:
| **1x rate** | AEGIS-128L | AEGIS-256 |
| **2x rate** | AEGIS-128X2 | AEGIS-256X2 |
| **4x rate** | AEGIS-128X4 | AEGIS-256X4 |
The AEGIS-128 family uses 128 bit keys and nonces while the AEGIS-256 family
uses 256 bit keys and nonces. The "rate" parameter defines the level of
_data parallelism_; for instance, AEGIS-128X2 processes _two_ AES blocks at a
time while AEGIS-128X4 processes _four_.
This is designed to make use of [SIMD] CPU instructions (which Philbin uses).
Note that **no additional OS threads are used** for this type of parallelism.
In general, the higher rate variants are _significantly_ faster than the lower
rate variants, but it does depend on the CPU. They can also be slower.
**Each AEGIS algorithm variant is independent** and _incompatible_ with the
other variants. You cannot encrypt data with e.g. AEGIS-256 and then decrypt it
with AEGIS-256X2; each variant produces a _different ciphertext_.
Each algorithm is exposed in a separate module: [`careful::aegis128l`],
[`careful::aegis128x2`] etc.
## Module Functions
Each AEGIS variant module contains the same set of functions. The functions
provide different ways to pass or receive data. The encryption functions are:
- `encrypt` returns the _ciphertext_ and _authentication tag_ concatenated in a
[`Vec<u8>`].
- `encrypt_detached` returns the _ciphertext_ in a [`Vec<u8>`] and the
_authentication tag_ as a separate ("detached") byte array.
- `encrypt_to_slice_detached` is similar to `encrypt_detached`, but writes the
_ciphertext_ to a user-provided byte slice. It does not allocate heap memory.
- `encrypt_in_place_detached` is similar to `encrypt_to_slice_detached`, but it
overwrites the byte slice storing the _plaintext_ with the _ciphertext_. It
does not allocate heap memory.
Each encryption function has a corresponding decryption function. Thus there's a
`decrypt`, `decrypt_detached`, `decrypt_to_slice_detached` etc.
The desired size of the _authentication tag_ needs to be provided to the
function as a generic parameter. For example:
use philbin::{easy::*, careful::*};
let mut plaintext: Vec<u8> = b"foo bar zoo".into();
let key = Key256::generate()?;
// AuthTag specified with "turbofish" syntax
let ciphertext = aegis256x4::encrypt::<AuthTag256>(
Plaintext::new(&plaintext),
AssociatedData::EMPTY,
&key,
Nonce::generate()?)?;
// AuthTag specified with explicit type declaration
let auth_tag: AuthTag256 = aegis256x4::encrypt_in_place_detached(
PlaintextMut::new(&mut plaintext),
AssociatedData::EMPTY,
&key,
Nonce::generate()?)?;
# Ok::<(), Error>(()) // Makes the doctest compile
# Correctness and Security
Philbin is built with a heavy emphasis on correctness and security. Philbin:
- Was _entirely written by a human_ and _reviewed_ for security issues by
fancy AIs (which found nothing of substance).
- Rejects all-zero keys and nonces since they are almost certainly
_catastrophic_ usage errors that _will_ lead to exploitable
vulnerabilities.
- Has full test coverage, including:
- All [RFC 10032][aegis] test vectors.
- [Rooterberg] test vectors.[^3] (Rooterberg is a continuation of
[Wycheproof].)
- Custom tests for interesting boundary conditions.
- Is thoroughly [fuzzed][fuzzing] under [`AddressSanitizer`][asan].
- Is [_differentially fuzzed_][diff-fuzz] against [`libaegis`][libaegis] to
prove identical output.
- Contains [_two lines of code_ in `unsafe` blocks][unsafe] which are guarded by
both compile-time and runtime checks.
- Contains a custom _Test Vector Leakage Assessment_ (TVLA) harness that and
validates the implementation is constant-time regardless of secret data.
- Exposes _strongly-typed APIs_ designed to prevent common mistakes that lead to
exploitable vulnerabilities.
- Contains rigorous error handling:
- _Does not panic_ and is fuzzed to validate this. [`easy::Error`] is returned
instead.
- Painstakingly written to avoid all integer overflow and underflow.
- Prevents inadvertent secret leakage through logs by:
- Never including _any_ dynamic content in [`easy::Error`] messages.
- Redacting [`Key`][easy::Key] content in [`Debug`] output.
- [`easy::Error`] variants are carefully designed to avoid attacks similar to
[Padding Oracle][oracle].
- Zeroizes [`Key`][easy::Key]s on [`Drop`] and this behavior cannot be disabled.
Philbin has _not_ received a professional, third-party security review.
# Benchmarks
![Benchmarks showing Philbin's performance. AEGIS-128X4 is roughly nine times
faster than ChaCha20-Poly1305 and two times faster than AES-128-GCM; similar for
AEGIS-256X4. Philbin is 1% faster overall than the raw C `aegis` crate. Data was
collected across multiple CPU models, vendors and
architectures.][bench-charts]
## Benchmarking details
- The CPUs that benchmarks were run on: `AMD Ryzen 5600`, `Apple M2`,
`Intel Core Ultra 7 265K`, `Intel N100`, `Intel Xeon Platinum 8581C`.
- The benchmarks used `encrypt_in_place_detached` and equivalent APIs in other
libraries to avoid all heap memory allocation during execution.
- Benchmark execution time was normalized to the performance of either
`ChaCha20-Poly1305` or the equivalent AEGIS variant in the `aegis` crate
(depending on the chart) _on the same CPU_. A _geometric mean_ then produces a
cross-CPU speedup from many per-CPU speedups.
- Benchmarks use random 16 KiB messages; shorter messages don't accurately
portray performance because they are affected by static setup overhead. Longer
messages exhibit similar performance to 16 KiB messages.
- [Raw benchmark data is available][bench-data], as is the [Julia Pluto
notebook][notebook] used to analyze the data and produce the charts.
# Comparison with [`aegis`][aegis-crate] crate
- **Pure Rust**. Philbin is 100% Rust. The `aegis` crate is just a thin
wrapper around [`libaegis`][libaegis], a raw C library.
- **Safer implementation**:
- `libaegis` is 100% `unsafe` (in Rust terms) while Philbin has [_two lines of
code_ in `unsafe` blocks][unsafe] which are guarded by both compile-time and
runtime checks.
- Philbin is [fully fuzzed](#correctness-and-security) while
`aegis`/`libaegis` contain no fuzzing infrastructure and make no
fuzzing claims.
- Philbin [includes a TVLA harness](#correctness-and-security) that checks
for constant-time behavior in CI. `aegis`/`libaegis` include no
constant-time tests.
- Philbin [_does not panic_](#correctness-and-security).
- Philbin checks and returns errors for all-zero keys and nonces
(since they are almost certainly _catastrophic_ usage errors).
- Philbin is painstakingly written to avoid all integer overflow and
underflow.
- **Safer API**. Philbin's API is designed to be hard to misuse and to
prevent catastrophic (and yet very common) mistakes.
[`Nonce::generate()`][careful::Nonce::generate] creates nonces from
cryptographically safe RNGs; nonces are completely encapsulated in [`easy`]
APIs; strong types make it _impossible_ to accidentally swap
nonce/key/plaintext/associated data parameters; all-zero keys and nonces are
detected and rejected, etc. `aegis` has none of this _and_ makes it easy to
re-use a nonce across messages.
- **Higher-level API**. Philbin includes an [`easy`] module with high-level APIs
that make _many_ mistakes impossible by construction. The [`careful`] module
exposes low-level access while still providing a significantly less
error-prone API than `aegis`.
- **Simpler API**. Philbin's [`easy`] module provides [`encrypt`][easy::encrypt]
and [`decrypt`][easy::decrypt] plain functions. Even [`careful`] APIs don't
expose any algorithm traits or structs; every algorithm is just a function
call. Philbin's API "vocabulary" is tiny and obvious.
- **Stable API**. Philbin is `>= 1.0.0`, `aegis` is `< 1.0.0`.
- **Slightly faster**. See the [Benchmarks](#benchmarks) section.
Some cons compared to `aegis`/`libaegis`:
- Rust's `std` is required. Supporting `no_std` is explicitly out-of-scope for
Philbin. If you need `no_std`, use `aegis`.
- Software-only path (no hardware acceleration) for the `powerpc` architecture,
unlike `aegis`.
- Philbin does not (yet) provide _separate_ implementations of AEGIS [MAC]
functions. (Philbin of course implements AEGIS MACs as part of AEGIS
encryption routines.)
# Cargo Features
This crate exposes only one Cargo feature to users (`rand`) which is enabled
by default. This feature pulls in the [`rand`][rand] crate as a dependency.
**This feature is required by several APIs in the [`easy`] module,** not
least of which is [`easy::encrypt`].
Other than [`Nonce::generate()`][careful::Nonce::generate], no APIs in
[`careful`] depend on `rand`.
Philbin requires Rust's `std`; support for `no_std` is explicitly out-of-scope.
# MSRV
The crate's Minimum Supported Rust Version (MSRV) is `1.95`. **We do not
consider MSRV increments to be [SemVer][semver] breaking changes.** That being
said, we plan to increase MSRV rarely and for good reasons.
[aegis]: https://www.rfc-editor.org/rfc/rfc10032.html
[sym]: https://en.wikipedia.org/wiki/Symmetric-key_algorithm
[ceasar]: https://en.wikipedia.org/wiki/CAESAR_Competition
[aead]: https://en.wikipedia.org/wiki/Authenticated_encryption
[chacha]: https://en.wikipedia.org/wiki/ChaCha20-Poly1305
[simd]: https://en.wikipedia.org/wiki/Single_instruction,_multiple_data
[fuzzing]: https://en.wikipedia.org/wiki/Fuzzing
[diff-fuzz]: https://en.wikipedia.org/wiki/Differential_testing
[timing]: https://en.wikipedia.org/wiki/Timing_attack
[unsafe]: https://codeberg.org/Valloric/philbin/search/branch/main?path=philbin&q=unsafe+%7B&mode=exact
[nonce]: https://en.wikipedia.org/wiki/Cryptographic_nonce
[rand]: https://crates.io/crates/rand
[csrng]: https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator
[fork]: https://en.wikipedia.org/wiki/Fork_(system_call)
[aes]: https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
[gcm]: https://en.wikipedia.org/wiki/Galois/Counter_Mode
[aegis-crate]: https://crates.io/crates/aegis
[aegis-api]: https://docs.rs/aegis/latest/aegis/aegis128l/struct.Aegis128L.html
[libaegis]: https://github.com/aegis-aead/libaegis
[mac]: https://en.wikipedia.org/wiki/Message_authentication_code
[unsafe]: https://codeberg.org/Valloric/philbin/search/branch/main?path=philbin&q=unsafe+%7B&mode=exact
[rooterberg]: https://github.com/bleichenbacher-daniel/Rooterberg
[wycheproof]: https://github.com/C2SP/wycheproof
[oracle]: https://en.wikipedia.org/wiki/Padding_oracle_attack
[asan]: https://github.com/google/sanitizers/wiki/addresssanitizer
[semver]: https://semver.org/
[bench-charts]: https://codeberg.org/Valloric/philbin/media/branch/main/benchmarks/analysis/benchmarks.svg
[bench-data]: https://codeberg.org/Valloric/philbin/src/branch/main/benchmarks/data
[notebook]: https://codeberg.org/Valloric/philbin/src/branch/main/benchmarks/analysis/charts.jl