leona 0.1.0

LIONESS cipher construction
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
#![no_std]
//! 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](https://link.springer.com/content/pdf/10.1007/3-540-60865-6_48.pdf) 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`](https://crates.io/crates/cipher) crate), as well as the hash function
//! (using the [`digest`](https://crates.io/crates/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](https://docs.rs/chacha20/latest/chacha20/type.ChaCha20.html) and
//! [Blake2s256](https://docs.rs/blake2/latest/blake2/type.Blake2sMac256.html) (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](https://docs.rs/hmac/latest/hmac/index.html) 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:
//!
//! ```
//! # use leona::{Lioness, ZeroIv};
//! # use blake2::Blake2sMac256;
//! # use chacha20::ChaCha20;
//! # type Cipher = Lioness<ZeroIv<ChaCha20>, Blake2sMac256>;
//! 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](https://crates.io/crates/aez) or
//! [zears](https://crates.io/crates/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).
use core::marker::PhantomData;

use blake2::Blake2b;
use cipher::{
    InOutBuf,
    stream::{StreamCipher, StreamCipherError},
};
use crypto_common::{KeyInit, KeyIvInit, KeySizeUser, OutputSizeUser};
use digest::{CustomizedInit, FixedOutput, Update};
use hybrid_array::{Array, ArraySize};
use thiserror::Error;
use typenum::{IsLessOrEqual, True, U64, Unsigned};
use zerocopy::FromZeros;

const SALT_1: &[u8] = b"k1";
const SALT_2: &[u8] = b"k2";
const SALT_3: &[u8] = b"k3";
const SALT_4: &[u8] = b"k4";

/// Errors that can occur during encryption and decryption.
#[derive(Error, Debug, Clone)]
pub enum Error {
    /// The given input data is shorter than the hash output.
    #[error("given input data is too short")]
    InputTooShort,
}

/// Main struct implementing the LIONESS construction.
///
/// `S` represents the stream cipher and `H` represents the hash algorithm.
///
/// See the crate level documentation for more details.
pub struct Lioness<S, H>
where
    S: KeySizeUser,
    H: KeySizeUser,
{
    k_1: Array<u8, S::KeySize>,
    k_2: Array<u8, H::KeySize>,
    k_3: Array<u8, S::KeySize>,
    k_4: Array<u8, H::KeySize>,
}

fn hash_key<L>(salt: &[u8], input: &[u8]) -> Array<u8, L>
where
    L: ArraySize + IsLessOrEqual<U64, Output = True>,
{
    let mut hasher = Blake2b::new_customized(salt);
    hasher.update(input);
    hasher.finalize_fixed()
}

fn xor_assign<L: ArraySize>(a: &mut L::ArrayType<u8>, b: &L::ArrayType<u8>) {
    a.as_mut()
        .iter_mut()
        .zip(b.as_ref().iter())
        .for_each(|(x, y)| *x ^= *y);
}

type Key<K> = <<K as KeySizeUser>::KeySize as ArraySize>::ArrayType<u8>;

impl<S, H> Lioness<S, H>
where
    S: KeySizeUser<KeySize = H::OutputSize> + KeyInit + StreamCipher,
    H: KeySizeUser + KeyInit + FixedOutput + Update,
    S::KeySize: IsLessOrEqual<U64, Output = True>,
    H::KeySize: IsLessOrEqual<U64, Output = True>,
    Array<u8, S::KeySize>: FromZeros,
{
    /// Create a new [Lioness] instance from the given key.
    ///
    /// Unlike [Lioness::new], you are not required to pass the correct key sizes manually.
    /// Instead, `blake2` is used internally to derive keys of the correct length.
    ///
    /// Note that this only works for keys up to 64 bytes. If you have longer keys, you need to use
    /// [Lioness::new].
    pub fn new_dynamic(key: &[u8]) -> Self {
        Self::new(
            hash_key::<S::KeySize>(SALT_1, key).0,
            hash_key::<H::KeySize>(SALT_2, key).0,
            hash_key::<S::KeySize>(SALT_3, key).0,
            hash_key::<H::KeySize>(SALT_4, key).0,
        )
    }
}

impl<S, H> Lioness<S, H>
where
    S: KeySizeUser<KeySize = H::OutputSize> + KeyInit + StreamCipher,
    H: KeySizeUser + KeyInit + FixedOutput + Update,
    Array<u8, S::KeySize>: FromZeros,
{
    /// Create a new [Lioness] instance from the given round keys.
    ///
    /// `k_1` and `k_3` are used for the stream cipher and must match its key length, while `k_2`
    /// and `k_4` are used for the hash function.
    ///
    /// Don't be scared of the long type signatures. They are just normal arrays of `u8`:
    ///
    /// ```
    /// # use leona::{Lioness, ZeroIv};
    /// # use blake2::Blake2sMac256;
    /// # use chacha20::ChaCha20;
    /// type Cipher = Lioness<ZeroIv<ChaCha20>, Blake2sMac256>;
    /// Cipher::new([1; 32], [2; 32], [3; 32], [4; 32]);
    /// ```
    pub fn new(k_1: Key<S>, k_2: Key<H>, k_3: Key<S>, k_4: Key<H>) -> Self {
        Self {
            k_1: k_1.into(),
            k_2: k_2.into(),
            k_3: k_3.into(),
            k_4: k_4.into(),
        }
    }

    /// Encrypt the given buffer in-place.
    ///
    /// If the buffer is too short (shorter than the hash output), an error is returned.
    pub fn encrypt(&self, data: &mut [u8]) -> Result<(), Error> {
        let l_len = S::KeySize::USIZE;
        if data.len() < l_len {
            return Err(Error::InputTooShort);
        }

        assert!(data.len() >= l_len);

        // R = R + S(L + K_1)
        let mut key = <Array<u8, S::KeySize> as FromZeros>::new_zeroed();
        key.copy_from_slice(&data[..l_len]);
        xor_assign::<S::KeySize>(&mut key.0, &self.k_1.0);
        Self::apply_keystream(key.into(), &mut data[l_len..]);

        // L = L + H(K_2, R)
        let hash = Self::hash(&self.k_2, &data[l_len..]);
        data[..l_len]
            .iter_mut()
            .zip(hash)
            .for_each(|(x, y)| *x ^= y);

        // R = R + S(L + K_3)
        let mut key = <Array<u8, S::KeySize> as FromZeros>::new_zeroed();
        key.copy_from_slice(&data[..l_len]);
        xor_assign::<S::KeySize>(&mut key.0, &self.k_3.0);
        Self::apply_keystream(key.into(), &mut data[l_len..]);

        // L = L + H(K_4, R)
        let hash = Self::hash(&self.k_4, &data[l_len..]);
        data[..l_len]
            .iter_mut()
            .zip(hash)
            .for_each(|(x, y)| *x ^= y);

        Ok(())
    }

    /// Decrypt the given buffer in-place.
    ///
    /// If the buffer is too short (shorter than the hash output), an error is returned.
    pub fn decrypt(&self, data: &mut [u8]) -> Result<(), Error> {
        let l_len = S::KeySize::USIZE;
        if data.len() < l_len {
            return Err(Error::InputTooShort);
        }

        assert!(data.len() >= l_len);

        // L = L + H(K_4, R)
        let hash = Self::hash(&self.k_4, &data[l_len..]);
        data[..l_len]
            .iter_mut()
            .zip(hash)
            .for_each(|(x, y)| *x ^= y);

        // R = R + S(L + K_3)
        let mut key = <Array<u8, S::KeySize> as FromZeros>::new_zeroed();
        key.copy_from_slice(&data[..l_len]);
        xor_assign::<S::KeySize>(&mut key.0, &self.k_3.0);
        Self::apply_keystream(key.into(), &mut data[l_len..]);

        // L = L + H(K_2, R)
        let hash = Self::hash(&self.k_2, &data[l_len..]);
        data[..l_len]
            .iter_mut()
            .zip(hash)
            .for_each(|(x, y)| *x ^= y);

        // R = R + S(L + K_1)
        let mut key = <Array<u8, S::KeySize> as FromZeros>::new_zeroed();
        key.copy_from_slice(&data[..l_len]);
        xor_assign::<S::KeySize>(&mut key.0, &self.k_1.0);
        Self::apply_keystream(key.into(), &mut data[l_len..]);

        Ok(())
    }

    fn apply_keystream(key: Key<S>, data: &mut [u8]) {
        let mut cipher = S::new(&Array(key));
        cipher.apply_keystream(data);
    }

    fn hash(key: &Array<u8, H::KeySize>, data: &[u8]) -> Array<u8, H::OutputSize> {
        let mut hasher = H::new(key);
        hasher.update(data);
        hasher.finalize_fixed()
    }
}

/// A helper struct that turns ciphers with IV into ciphers without IV.
///
/// This turns ciphers that require an IV (like `ChaCha20`), represented by
/// [KeyIvInit](https://docs.rs/cipher/latest/cipher/trait.KeyIvInit.html), into ciphers that only
/// require a key, represented by
/// [KeyInit](https://docs.rs/cipher/latest/cipher/trait.KeyInit.html). To do so, an all-zero IV is
/// supplied.
#[derive(Debug, Clone)]
pub struct ZeroIv<C>(C);

impl<C: KeySizeUser> KeySizeUser for ZeroIv<C> {
    type KeySize = C::KeySize;
}

impl<C: KeyIvInit> KeyInit for ZeroIv<C>
where
    Array<u8, C::IvSize>: FromZeros,
{
    fn new(key: &Array<u8, Self::KeySize>) -> Self {
        let iv = <Array<u8, C::IvSize> as FromZeros>::new_zeroed();
        Self(C::new(key, &iv))
    }
}

impl<C: StreamCipher> StreamCipher for ZeroIv<C> {
    fn check_remaining(&self, data_len: usize) -> Result<(), StreamCipherError> {
        self.0.check_remaining(data_len)
    }

    fn unchecked_apply_keystream_inout(&mut self, buf: InOutBuf<'_, '_, u8>) {
        self.0.unchecked_apply_keystream_inout(buf)
    }

    fn unchecked_write_keystream(&mut self, buf: &mut [u8]) {
        self.0.unchecked_write_keystream(buf)
    }
}

/// A helper struct that turns unkeyed hashes into keyed hashes.
///
/// This turns hashes that have no key (like SHA256) into hashes that have a key. Note that the
/// construction is rather simple: the key is prepended to the input data. This method is suggested
/// in the LIONESS paper
///
/// > The keyed hash function H_K(M):
/// > (a) is based on an unkeyed hash function H'(M), in which we append and/or prepend the key to
/// > the message [...]
#[derive(Debug, Clone)]
pub struct KeyedHash<L, H>(H, PhantomData<L>);

impl<L: ArraySize, H> KeySizeUser for KeyedHash<L, H> {
    type KeySize = L;
}

impl<L: ArraySize, H: Default + Update> KeyInit for KeyedHash<L, H> {
    fn new(key: &Array<u8, Self::KeySize>) -> Self {
        let mut hasher = H::default();
        hasher.update(key);
        KeyedHash(hasher, PhantomData)
    }
}

impl<L: ArraySize, H: OutputSizeUser> OutputSizeUser for KeyedHash<L, H> {
    type OutputSize = H::OutputSize;
}

impl<L: ArraySize, H: Update> Update for KeyedHash<L, H> {
    fn update(&mut self, data: &[u8]) {
        self.0.update(data)
    }
}

impl<L: ArraySize, H: FixedOutput> FixedOutput for KeyedHash<L, H> {
    fn finalize_into(self, buffer: &mut Array<u8, Self::OutputSize>) {
        self.0.finalize_into(buffer)
    }
}