1 //! 5peck: instantiations of the Speck family of ciphers with weird word sizes and key handling.
3 //! These are NOT suitable for general cryptography. They are designed for this particular
4 //! application and nothing else. The smaller block sizes especially would certainly be unsuitable
5 //! for their vulnerability to birthday attacks when used in typical cryptographic modes.
6 //! But those vulnerabilities don’t apply to TESID which doesn’t use such modes.
8 //! These are NOT dependable. Speck’s parameters were chosen by cryptographers based on deep
9 //! understanding, real cryptanalysis, and simulation. Meanwhile, I wrote 5peck, non-cryptographer
10 //! that I am and not *particularly* knowledgeable in the field (especially about cryptanalysis).
11 //! I have taken guidance from their [original paper](https://eprint.iacr.org/2013/404.pdf) and
12 //! [subsequent design notes](https://eprint.iacr.org/2017/560.pdf), but I’ve had to deviate quite
13 //! significantly from their design in some elements, most significantly in the treatment of the
14 //! key for small word sizes. The parameters I’ve settled on are largely based on interpolation
15 //! and a little extrapolation, with only some very basic cryptanalytic simulations; but I believe
16 //! what I have should hold up well with the best of ’em.
18 //! The main two alternatives that I could think of were: Hasty Pudding, which with its bit-level
19 //! blocksize flexibility would work just fine but is *considerably* more complex, slower, and more
20 //! shallowly studied; and something like NIST’s FF1 or FF3-1 ciphers, which are patent-encumbered
21 //! which makes me stay right away from them. There are surprisingly few practical options for this
22 //! sort of thing that I’m doing, really.
24 const fn mask(word_bits
: u32) -> u64 {
25 2_u64.pow(word_bits
) - 1
28 /// Rotate a *bits*-bit integer left, WITHOUT masking or checking rhs < bits.
29 /// (That is, the input must be masked and masking the output is your responsibility.)
30 const fn rotate_left(lhs
: u64, rhs
: u32, bits
: u32) -> u64 {
31 (lhs
<< rhs
) | (lhs
>> (bits
- rhs
))
34 /// Rotate a *bits*-bit integer right, WITHOUT masking or checking rhs < bits.
35 /// (That is, the input must be masked and masking the output is your responsibility.)
36 const fn rotate_right(lhs
: u64, rhs
: u32, bits
: u32) -> u64 {
37 (lhs
<< (bits
- rhs
)) | (lhs
>> rhs
)
40 pub fn expand(key
: u128
) -> [u64; 30] {
42 let mut y
= key
as u64;
44 let mut x
= (key
>> 64) as u64; // m=2 means ℓ can be a single value rather than an array
46 x
= x
.rotate_right(9).wrapping_add(y
) ^
(i
as u64);
47 y
= y
.rotate_left(2) ^ x
;
53 // On endianness: I’ve followed Speck and gone little-endian. As I’m only encrypting numbers, I
54 // take a u128 rather than messing around with [u8; 16] or such. The least significant bits are
55 // taken as x (the first word), the most significant as y (the second word).
57 pub fn encrypt(k
: &[u64; 30], n
: u32, plain_text
: u128
) -> u128
{
58 debug_assert_eq!(plain_text
>> (2 * n
), 0);
60 let mut x
= (plain_text
as u64) & mask
;
61 let mut y
= ((plain_text
>> n
) as u64) & mask
;
64 x
= (rotate_right(x
, 9, n
).wrapping_add(y
) ^ k
) & mask
;
65 y
= (rotate_left(y
, 2, n
) ^ x
) & mask
;
68 ((y
as u128
) << n
) | (x
as u128
)
71 pub fn decrypt(k
: &[u64; 30], n
: u32, cipher_text
: u128
) -> u128
{
72 debug_assert_eq!(cipher_text
>> (2 * n
), 0);
74 let mut x
= (cipher_text
as u64) & mask
;
75 let mut y
= ((cipher_text
>> n
) as u64) & mask
;
77 for &k
in k
.iter().rev() {
78 y
= rotate_right(x ^ y
, 2, n
) & mask
;
79 x
= rotate_left((x ^ k
).wrapping_sub(y
) & mask
, 9, n
) & mask
;
82 ((y
as u128
) << n
) | (x
as u128
)
86 fn test_5peck_n_10() {
87 let k
= expand(0x000102030405060708090a0b0c0d0e0f);
88 assert_eq!(encrypt(&k
, 10, 0), 917905);
89 assert_eq!(decrypt(&k
, 10, 917905), 0);
90 assert_eq!(encrypt(&k
, 10, 0b1111111111), 314126);
91 assert_eq!(decrypt(&k
, 10, 314126), 0b1111111111);
92 // Look, anything else can be covered by the TESID tests.
93 // They cover at least two values from each n.
96 #[cfg(feature = "std")]
98 #[cfg_attr(debug_assertions, ignore)] // Takes a second or two in debug mode (<1ms in release).
99 fn show_5peck_n_10_is_a_permutation() {
100 let k
= expand(0x000102030405060708090a0b0c0d0e0f);
101 let mut seen
= std
::collections
::HashSet
::new();
102 for i
in 0..0b100000000000000000000 {
103 let encrypted
= encrypt(&k
, 10, i
);
104 assert!(encrypted
< 0b100000000000000000000);
105 assert!(seen
.insert(encrypted
));
106 assert_eq!(decrypt(&k
, 10, encrypted
), i
);
112 fn bench_5peck_n_10_all(b
: &mut test
::Bencher
) {
113 let k
= expand(0x000102030405060708090a0b0c0d0e0f);
114 // Yeah, let’s just encrypt all 2²⁰ values (just over one million); why not?
115 // Takes a little under one millisecond on my laptop—so each encryption is taking a bit under
116 // one nanosecond. Of course, if we did other stuff in between, then accessing the expanded key
117 // might slow things down a bit.
118 b
.iter(|| for i
in 0..0b100000000000000000000 {
119 // Gotta black-box it or the entire thing takes 0ns! 🙂
120 encrypt(&k
, 10, test
::black_box(i
));