From d4ad1672404745c68096c700edc0816051f6db3f Mon Sep 17 00:00:00 2001 From: Daniel Schadt Date: Tue, 8 Apr 2025 22:08:05 +0200 Subject: add documentation --- Cargo.toml | 3 ++ LICENSE | 7 ++++ README.md | 29 +++++++++++++++ Spec.pdf | Bin 0 -> 252515 bytes src/lib.rs | 117 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 156 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 Spec.pdf diff --git a/Cargo.toml b/Cargo.toml index d574ec5..aa191ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,9 @@ name = "zears" version = "0.1.0" edition = "2024" +license = "MIT" +description = "Rust implementation of the AEZ v5 cipher." +readme = "README.md" [dependencies] aes = { version = "0.8.4", features = ["hazmat"] } diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..dce97cf --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +Copyright 2025 Daniel Schadt + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f4d867b --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# zears + +Implementation of [AEZ v5](https://www.cs.ucdavis.edu/~rogaway/aez/index.html) in Rust. Works without hardware AES support. + +## ☣️ Cryptographic hazmat ☣️ + +This crate is not battle tested and not audited. It exists as a learning exercise. Use it at your own risk. + +## AEZ encryption + +From the AEZ website: + +> AEZ is an authenticated-encryption (AE) scheme optimized for ease of correct use ("AE made EZ"). It was invented by Viet Tung Hoang, Ted Krovetz, and Phillip Rogaway. The algorithm encrypts a plaintext by appending to it a fixed authentication block (some zero bits) and then enciphering the resulting string with an arbitrary-input-length blockcipher, this tweaked by the nonce, AD, and authenticator length. The approach results in strong security and usability properties, including nonce-reuse misuse resistance, automatic exploitation of decryption-verified redundancy, and arbitrary, user-selectable length expansion. + +## Example use + +This crate provides an easy-to-use interface for AEZ: + +```rust +use zears::Aez; +let aez = Aez::new(b"my key"); +let ciphertext = aez.encrypt(b"nonce", &[b"associated data"], 16, b"message"); +let plaintext = aez.decrypt(b"nonce", &["associated data"], 16, &ciphertext); +assert_eq!(plaintext.unwrap(), b"message"); +``` + +## License + +This crate is licensed under the terms of the MIT license. You can find the full license text in LICENSE. diff --git a/Spec.pdf b/Spec.pdf new file mode 100644 index 0000000..07e4a76 Binary files /dev/null and b/Spec.pdf differ diff --git a/src/lib.rs b/src/lib.rs index f5dd3ab..20ee5a3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,87 @@ +//! AEZ *\[sic!\]* v5 encryption implemented in Rust. +//! +//! # ☣️ Cryptographic hazmat ☣️ +//! +//! This crate is not battle tested, nor is it audited. Its usage for critical systems is strongly +//! discouraged. It mainly exists as a learning exercise. +//! +//! # AEZ encryption +//! +//! [AEZ](https://www.cs.ucdavis.edu/~rogaway/aez/index.html) is an authenticated encryption +//! scheme. It works in two steps: +//! +//! * First, a known authentication block (a fixed number of zeroes) is appended to the message. +//! * Second, the message is enciphered with an arbitrary-length blockcipher. +//! +//! The blockcipher is tweaked with the key, the nonce and additional data. +//! +//! The [paper](https://www.cs.ucdavis.edu/~rogaway/aez/aez.pdf) explains the security concepts of +//! AEZ in more detail. +//! +//! # AEZ encryption (for laypeople) +//! +//! The security property of encryption schemes says that an adversary without key must not learn +//! the content of a message, but the adversary might still be able to modify the message. For +//! example, in AES-CTR, flipping a bit in the ciphertext means that the same bit will be flipped +//! in the plaintext once the message is decrypted. +//! +//! Authenticated encryption solves this problem by including a mechanism to detect changes. This +//! can be done for example by including a MAC, or using a mode like GCM (Galois counter mode). In +//! many cases, not only the integrity of the ciphertext can be verified, but additional data can +//! be provided during encryption and decryption which will also be included in the integrity +//! check. This results in an *authenticated encryption with associated data* scheme, AEAD for +//! short. +//! +//! AEZ employs a nifty technique in order to realize an AEAD scheme: The core of AEZ is an +//! enciphering scheme, which in addition to "hiding" its input is also very "unpredictable", +//! similar to a hash function. That means that if a ciphertext is changed slightly (by flipping a +//! bit), the resulting plaintext will be unpredictably and completely different. +//! +//! With this property, authenticated encryption can be realized implicitly: The message is padded +//! with a known string before enciphering it. If, after deciphering, this known string is not +//! present, the message has been tampered with. Since the enciphering scheme is parametrized by +//! the key, a nonce and arbitrary additional data, we can verify the integrity of associated data +//! as well. +//! +//! # Other implementations +//! +//! As this library is a learning exercise, if you want to use AEZ in practice, it is suggested to +//! use the [`aez`](https://crates.io/crates/aez) crate which provides bindings to the C reference +//! implementation of AEZ. +//! +//! `zears` differs from `aez` in that ... +//! +//! * it works on platforms without hardware AES support, using the "soft" backend of +//! [`aes`](https://crates.io/crates/aes). +//! * it does not inherit the limitations of the reference implementation in regards to nonce +//! length, authentication tag length, or the maximum of one associated data item. +//! +//! `zears` is tested with test vectors generated from the reference implementation using [Nick +//! Mathewson's tool](https://github.com/nmathewson/aez_test_vectors). +//! +//! # Example usage +//! +//! The core of this crate is the [Aez] struct, which provides the high-level API. There is usually +//! not a lot more that you need: +//! +//! ``` +//! # use zears::*; +//! let aez = Aez::new(b"my secret key!"); +//! let cipher = aez.encrypt(b"nonce", &[b"associated data"], 16, b"message"); +//! let plaintext = aez.decrypt(b"nonce", &[b"associated data"], 16, &cipher); +//! assert_eq!(plaintext.unwrap(), b"message"); +//! +//! // Flipping a bit leads to decryption failure +//! let mut cipher = aez.encrypt(b"nonce", &[], 16, b"message"); +//! cipher[0] ^= 0x02; +//! let plaintext = aez.decrypt(b"nonce", &[], 16, &cipher); +//! assert!(plaintext.is_none()); +//! +//! // Similarly, modifying the associated data leads to failure +//! let cipher = aez.encrypt(b"nonce", &[b"foo"], 16, b"message"); +//! let plaintext = aez.decrypt(b"nonce", &[b"bar"], 16, &cipher); +//! assert!(plaintext.is_none()); +//! ``` use std::iter; mod block; @@ -8,15 +92,39 @@ use block::Block; type Key = [u8; 48]; type Tweak<'a> = &'a [&'a [u8]]; +/// AEZ encryption scheme. pub struct Aez { key: Key, } impl Aez { + /// Create a new AEZ instance. + /// + /// The key is expanded using Blake2b, according to the AEZ specification. + /// + /// If you provide a key of the correct length (48 bytes), no expansion is done and the key is + /// taken as-is. pub fn new(key: &[u8]) -> Self { Aez { key: extract(key) } } + /// Encrypt the given data. + /// + /// Parameters: + /// + /// * `nonce` -- the nonce to use. Each nonce should only be used once, as re-using the nonce + /// (without chaning the key) will lead to the same ciphertext being produced, potentially + /// making it re-identifiable. + /// * `associated_data` -- additional data to be included in the integrity check. Note that + /// this data will *not* be contained in the ciphertext, but it must be provided on + /// decryption. + /// * `tau` -- number of *bytes* (not bits) to use for integrity checking. A value of `tau = + /// 16` gives 128 bits of security. Passing a value of 0 is valid and leads to no integrity + /// checking. + /// * `data` -- actual data to encrypt. Can be empty, in which case the returned ciphertext + /// provides a "hash" that verifies the integrity of the associated data will be + /// + /// Returns the ciphertext, which will be of length `data.len() + tau`. pub fn encrypt( &self, nonce: &[u8], @@ -27,6 +135,15 @@ impl Aez { encrypt(&self.key, nonce, associated_data, tau, data) } + /// Decrypts the given ciphertext. + /// + /// Parameters: + /// + /// * `nonce`, `associated_data` and `tau` are as for [`Aez::encrypt`]. + /// * `data` -- the ciphertext to decrypt. + /// + /// Returns the decrypted content. If the authentication check fails, returns `None` instead. + /// The returned vector has length `data.len() - tau`. pub fn decrypt( &self, nonce: &[u8], -- cgit v1.2.3