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
//! A Rust implementation of the Cipher Block Chaining (CBC) mode of the SPECK cipher.
//!
//! Don't use this unless you know what you are doing, as practical attacks exist against
//! CBC mode.
#![feature(i128_type, const_fn)]
#![no_std]
#![forbid(unsafe_code)]

extern crate speck;
extern crate byteorder;

use speck::Key;
use byteorder::ByteOrder;

/// The size of SPECK's blocks and keys.
pub const BLOCK_SIZE: usize = core::mem::size_of::<u128>();

/// Encrypt data with SPECK-CBC.
///
/// `Endian` is the endianness of `key`, `iv`, and the blocks.
///
/// `key` and `iv`'s length must be `BLOCK_SIZE`.
///
/// `input`'s length must be a multiple of `BLOCK_SIZE`.
///
/// `output`'s length must be the same as `input`'s.
pub fn encrypt<Endian: ByteOrder>(input: &[u8], mut output: &mut [u8], key: &[u8], iv: &[u8]) {
    // Made sure that the input is padded.
    assert_eq!(input.len() % BLOCK_SIZE, 0);

    // Make sure that the output buffer is the right size.
    assert_eq!(output.len(), input.len());

    // Make sure that the key length is correct.
    assert_eq!(key.len(), BLOCK_SIZE);

    // Make sure that the IV length is correct.
    assert_eq!(iv.len(), BLOCK_SIZE);

    // Precompute the key schedule.
    let key_schedule = Key::new(Endian::read_u128(key));

    // Keep the last ciphertext around to avoid a possible byte swap.
    let mut last_block_ciphertext = 0;

    for (block_index, (block_input, mut block_output)) in input
        .chunks(BLOCK_SIZE)
        .zip(output.chunks_mut(BLOCK_SIZE))
        .enumerate()
    {
        // Read block plaintext from input.
        let block_plaintext = Endian::read_u128(block_input);

        // XOR the plaintext with the appropriate data.
        let block_xor = block_plaintext ^ if block_index == 0 {
            Endian::read_u128(iv)
        } else {
            last_block_ciphertext
        };

        // Perform the actual encryption.
        let block_ciphertext = key_schedule.encrypt_block(block_xor);

        // Update the last ciphertext.
        last_block_ciphertext = block_ciphertext;

        // Write the encrypted block to output.
        Endian::write_u128(block_output, block_ciphertext);
    }
}

/// Decrypt data with SPECK-CBC.
///
/// `Endian` is the endianness of `key`, `iv`, and the blocks.
///
/// `key` and `iv`'s length must be `BLOCK_SIZE`.
///
/// `input`'s length must be a multiple of `BLOCK_SIZE`.
///
/// `output`'s length must be the same as `input`'s.
pub fn decrypt<Endian: ByteOrder>(input: &[u8], mut output: &mut [u8], key: &[u8], iv: &[u8]) {
    // Made sure that the input is padded.
    assert_eq!(input.len() % BLOCK_SIZE, 0);

    // Make sure that the output buffer is the right size.
    assert_eq!(output.len(), input.len());

    // Make sure that the key length is correct.
    assert_eq!(key.len(), BLOCK_SIZE);

    // Make sure that the IV length is correct.
    assert_eq!(iv.len(), BLOCK_SIZE);

    // Precompute the key schedule.
    let key_schedule = Key::new(Endian::read_u128(key));

    // Keep the last ciphertext around to avoid a possible byte swap.
    let mut last_block_ciphertext = 0;

    for (block_index, (block_input, mut block_output)) in input
        .chunks(BLOCK_SIZE)
        .zip(output.chunks_mut(BLOCK_SIZE))
        .enumerate()
    {
        // Read block ciphertext from input.
        let block_ciphertext = Endian::read_u128(block_input);

        // Perform the actual decryption.
        let block_xor = key_schedule.decrypt_block(block_ciphertext);

        // XOR the plaintext with the appropriate data.
        let block_plaintext = block_xor ^ if block_index == 0 {
            Endian::read_u128(iv)
        } else {
            last_block_ciphertext
        };

        // Update the last ciphertext.
        last_block_ciphertext = block_ciphertext;

        // Write the decrypted block to output.
        Endian::write_u128(block_output, block_plaintext);
    }
}