Expand description
LIONESS implementation 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.
§LIONESS
LIONESS is a block cipher based on the Luby-Rackoff construction. It combines a hash function and a stream cipher to produce a block permutation that allows for blocks of arbitrary(*) length to be encrypted.
Internally, the cipher uses two instantiations of the keyed hash function, and two instantiations of the stream cipher, leading to four separate round keys. The stream cipher and the hash must be compatible: The output size of the hash must be the same as the key size of the cipher.
(*): The construction does not specify how short inputs (smaller than the hash output) should be encrypted. As such, Lioness will error on such inputs, and you have to pad the input manually.
§Implementation
The cipher is implemented as the Lioness struct, and is kept generic over the stream cipher
(using the cipher crate), as well as the hash function
(using the digest crate). Rust’s type system is used to
ensure that the cipher is compatible with the hash (the hash’s output size must match the
cipher’s key size).
The construction is implemented in-place and works without allocations.
This crate implements several convenience methods:
To prevent users from having to manually create four round keys, we provide
Lioness::new_dynamic. This method internally uses blake2 to create distinct round keys from
a given (potentially short) input key.
To use ciphers that require initialization vectors (IVs), we provide ZeroIv. This struct wraps a cipher that requires an IV, and supplies a zero-IV.
To use unkeyed hash functions (like SHA), we provide KeyedHash. This struct wraps a hash function and turns it into a keyed hash by prepending the key to the input data.
§Examples
A simple working combination for Lioness is to use ChaCha20 and Blake2s256 (32 byte key/hash output). Note that we use the MAC variant of Blake2 to get a keyed version of the hash directly:
use leona::{Lioness, ZeroIv};
use blake2::Blake2sMac256;
use chacha20::ChaCha20;
type Cipher = Lioness<ZeroIv<ChaCha20>, Blake2sMac256>;
let mut data = [0u8; 64];
data[..11].copy_from_slice(b"hello world");
let cipher = Cipher::new_dynamic(b"secret");
cipher.encrypt(&mut data);
assert_ne!(&data[..11], b"hello world");
cipher.decrypt(&mut data);
assert_eq!(&data[..11], b"hello world");You can also use AES in counter mode as a stream cipher, and SHA256 as a hash. Note how we have
to use the Ctr32BE wrapper to turn AES into a stream cipher, and KeyedHash to turn SHA into
a keyed hash. We choose Aes256, since its key size matches SHA256’s output size (32 bytes):
use leona::{KeyedHash, Lioness, ZeroIv};
use aes::Aes256;
use ctr::Ctr32BE;
use sha2::Sha256;
use typenum::U32;
type Cipher = Lioness<ZeroIv<Ctr32BE<Aes256>>, KeyedHash<U32, Sha256>>;
let mut data = [0u8; 64];
data[..11].copy_from_slice(b"hello world");
let cipher = Cipher::new_dynamic(b"secret");
cipher.encrypt(&mut data);
assert_ne!(&data[..11], b"hello world");
cipher.decrypt(&mut data);
assert_eq!(&data[..11], b"hello world");Alternatively, you can use Hmac to turn unkeyed hash functions into keyed hashes:
use leona::{KeyedHash, Lioness, ZeroIv};
use aes::Aes256;
use ctr::Ctr32BE;
use hmac::Hmac;
use sha2::Sha256;
type Cipher = Lioness<ZeroIv<Ctr32BE<Aes256>>, Hmac<Sha256>>;
let mut data = [0u8; 64];
data[..11].copy_from_slice(b"hello world");
let cipher = Cipher::new_dynamic(b"secret");
cipher.encrypt(&mut data);
assert_ne!(&data[..11], b"hello world");
cipher.decrypt(&mut data);
assert_eq!(&data[..11], b"hello world");If the input data is shorter than the hash output, encryption will fail:
let mut data = [0u8; 16];
data[..11].copy_from_slice(b"hello world");
let cipher = Cipher::new_dynamic(b"secret");
assert!(cipher.encrypt(&mut data).is_err());If you need more control over the keys, use the Lioness::new constructor. In this case, you can provide all round keys separately.
§Alternatives
LIONESS is slow, and it cannot encrypt short data. If you need a wide-block cipher, consider a
different construction such as aez (via aez or
zears):
aes256 | chacha20 | |
|---|---|---|
sha256 | 645.58 MiB/s | 495.01 MiB/s |
blake2 | 285.56 MiB/s | 251.19 MiB/s |
For comparison, zears achieves 5.8170 GiB/s (+simd, target-cpu=native).
Structs§
- Keyed
Hash - A helper struct that turns unkeyed hashes into keyed hashes.
- Lioness
- Main struct implementing the LIONESS construction.
- ZeroIv
- A helper struct that turns ciphers with IV into ciphers without IV.
Enums§
- Error
- Errors that can occur during encryption and decryption.