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:
easymodule with simple, high-level APIs,carefulmodule 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::encryptandeasy::decrypt. They’re at the “sweet spot” of security and performance for almost every use-case and never a bad choice. - Correct nonce handling?
easyAPIs handle nonces internally. (Using a nonce in a subtly wrong way is common and catastrophic to security.) - Secure random number generation?
easyAPIs internally use cryptographically secure random number generators provided by your operating system and safe even if your app callsfork()or similar. - Which authentication tag size?
easyAPIs 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::encryptandeasy::decryptalready handle it. You get aVec<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.
Key::generate()will securely create a brand newKey.Key::new()andKey::from_bytes()can create one from an existing byte array and slice, respectively.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 defines six AEGIS encryption algorithm variants which are categorized in the following table:
| 128 bit key/nonce | 256 bit key/nonce | |
|---|---|---|
| 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:
encryptreturns the ciphertext and authentication tag concatenated in aVec<u8>.encrypt_detachedreturns the ciphertext in aVec<u8>and the authentication tag as a separate (“detached”) byte array.encrypt_to_slice_detachedis similar toencrypt_detached, but writes the ciphertext to a user-provided byte slice. It does not allocate heap memory.encrypt_in_place_detachedis similar toencrypt_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
libaegisto prove identical output.3 - Contains two lines of code in
unsafeblocks 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::Erroris 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::Errormessages. - Redacting
Keycontent inDebugoutput.
- Never including any dynamic content in
easy::Errorvariants are carefully designed to avoid attacks similar to Padding Oracle.
- Does not panic and is fuzzed to validate this.
- Zeroizes
Keys onDropand this behavior cannot be disabled.
Philbin has not received a professional, third-party security review.
§Benchmarks
§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_detachedand equivalent APIs in other libraries to avoid all heap memory allocation during execution. - Benchmark execution time was normalized to the performance of either
ChaCha20-Poly1305or the equivalent AEGIS variant in theaegiscrate (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
aegiscrate is just a thin wrapper aroundlibaegis5, a raw C library. - Safer implementation:
libaegisis 100%unsafe(in Rust terms) while Philbin has two lines of code inunsafeblocks which are guarded by both compile-time and runtime checks.- Philbin is fully fuzzed while
aegis/libaegiscontain no fuzzing infrastructure and make no fuzzing claims. - Philbin includes a TVLA harness that checks
for constant-time behavior in CI.
aegis/libaegisinclude 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 ineasyAPIs; strong types make it impossible to accidentally swap nonce/key/plaintext/associated data parameters; all-zero keys and nonces are detected and rejected, etc.aegishas none of this and makes it easy to re-use a nonce across messages6. - Higher-level API. Philbin includes an
easymodule with high-level APIs that make many mistakes impossible by construction. Thecarefulmodule exposes low-level access while still providing a significantly less error-prone API thanaegis. - Simpler API. Philbin’s
easymodule providesencryptanddecryptplain functions. EvencarefulAPIs 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,aegisis< 1.0.0. - Slightly faster. See the Benchmarks section.
Some cons compared to aegis/libaegis:
- Rust’s
stdis required. Supportingno_stdis explicitly out-of-scope for Philbin. If you needno_std, useaegis. - Software-only path (no hardware acceleration) for the
powerpcarchitecture, unlikeaegis. - 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.
Specifically, Philbin implements AEGIS according to RFC 10032. ↩
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.) ↩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
See “Test Vector Leakage Assessment (TVLA) methodology in practice”, Becker, G. et al., 2013. ↩
The
aegiscrate includes apure-rustCargo feature (disabled by default), but warns that “[enabling this feature] will substantially degrade performance and some features may not be available”. Thepure-rustimplementation inaegisdoes not use runtime CPU detection. ↩See e.g. struct
Aegis128LAPI. You are meant to callnew(key, nonce)and thenencrypt(m, ad). Users without cryptographic expertise would callnew()once andencrypt()N times for N plaintexts, which is baaaad.Philbin’s
easyAPIs intentionally don’t expose nonces, whilecarefulAPIs take aNonceby-value to prevent nonce reuse. ↩