Skip to main content

Crate philbin

Crate philbin 

Source
Expand description

(Every part of this crate was written by a human being.)

Philbin is a Rust library implementing the AEGIS algorithm1 for symmetric-key encryption which was one of the winners of the CAESAR Competition. AEGIS is significantly faster and more secure than other popular AEAD algorithms (AES-GCM and ChaCha20-Poly1305) on CPUs with AES hardware acceleration.

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 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 provided by your operating system and safe even if your app calls 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);

Notice the usage of Plaintext, Ciphertext and AssociatedData types. These are wrappers around &[u8] and &mut [u8] that help prevent accidental parameter swaps (e.g. like swapping associated data and plaintext).

Key256 is merely an alias for Key<32> which encrypt and decrypt use.

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 defines six AEGIS encryption algorithm variants which are categorized in the following table:

128 bit key/nonce256 bit key/nonce
1x rateAEGIS-128LAEGIS-256
2x rateAEGIS-128X2AEGIS-256X2
4x rateAEGIS-128X4AEGIS-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()?)?;

§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.2
  • Has full test coverage, including:
    • All RFC 10032 test vectors.
    • Rooterberg test vectors.3 (Rooterberg is a continuation of Wycheproof.)
    • Custom tests for interesting boundary conditions.
  • Is thoroughly fuzzed under AddressSanitizer.
  • Is differentially fuzzed against libaegis to prove identical output.3
  • Contains two lines of code in unsafe blocks which are guarded by both compile-time and runtime checks.
  • Contains a custom Test Vector Leakage Assessment (TVLA)4 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 content in Debug output.
    • easy::Error variants are carefully designed to avoid attacks similar to Padding Oracle.
  • Zeroizes Keys 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.

§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, as is the Julia Pluto notebook used to analyze the data and produce the charts.

§Comparison with aegis crate

  • Pure Rust. Philbin is 100% Rust. The aegis crate is just a thin wrapper around libaegis5, a raw C library.
  • Safer implementation:
    • libaegis is 100% unsafe (in Rust terms) while Philbin has two lines of code in unsafe blocks which are guarded by both compile-time and runtime checks.
    • Philbin is fully fuzzed while aegis/libaegis contain no fuzzing infrastructure and make no fuzzing claims.
    • Philbin includes a TVLA harness that checks for constant-time behavior in CI. aegis/libaegis include no constant-time tests.
    • Philbin does not panic.
    • 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() 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 messages6.
  • 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 and 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 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 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(), 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 breaking changes. That being said, we plan to increase MSRV rarely and for good reasons.


  1. Specifically, Philbin implements AEGIS according to RFC 10032. ↩

  2. This maintainer has seen too many critical security issues that could have been prevented had the cryptographic library rejected all-zero keys and nonces.

    There are no valid production use-cases for an all-zero key. An all-zero nonce is sometimes used as the first value of a counter-based nonce; instead start your counter from 1 or a random number. Even better, use a randomly generated 256-bit nonce which AEGIS supports. (Nonce::generate() will securely do this for you.) ↩

  3. Philbin’s explicit design choices like rejection of all-zero keys and nonces lead to a handful of differences that are fully accounted for. ↩ 1 2

  4. See “Test Vector Leakage Assessment (TVLA) methodology in practice”, Becker, G. et al., 2013. ↩

  5. The aegis crate includes a pure-rust Cargo feature (disabled by default), but warns that “[enabling this feature] will substantially degrade performance and some features may not be available”. The pure-rust implementation in aegis does not use runtime CPU detection. ↩

  6. See e.g. struct Aegis128L API. You are meant to call new(key, nonce) and then encrypt(m, ad). Users without cryptographic expertise would call new() once and encrypt() N times for N plaintexts, which is baaaad.

    Philbin’s easy APIs intentionally don’t expose nonces, while careful APIs take a Nonce by-value to prevent nonce reuse. ↩

Modules§

careful
Low-level APIs that should be used with great care.
easy
Easy, high-level APIs that are hard to misuse.