├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── gimli_common.rs ├── gimli_decrypt.rs ├── gimli_encrypt.rs ├── lib.rs ├── main.rs └── tests └── cipher_test.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Build 13 | run: cargo build --verbose 14 | - name: Run tests 15 | run: cargo test --verbose 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gimli_rs" 3 | version = "0.2.0" 4 | authors = ["Jon Moroney "] 5 | edition = "2018" 6 | description = "A pure rust implementation of the gimli cipher" 7 | license = "MIT" 8 | homepage = "https://github.com/darakian/gimli" 9 | repository = "https://github.com/darakian/gimli" 10 | documentation = "https://docs.rs/gimli" 11 | readme = "README.md" 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [dependencies] 16 | structopt = "0.3" 17 | rand = "0.7.2" 18 | 19 | [profile.release] 20 | lto = true 21 | debug=false 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Jon Moroney 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gimli 2 | A pure rust implementation of the gimli cipher 3 | 4 | # Status 5 | Hash and cipher working with test vectors. 6 | Test vectors taken from both the paper on https://gimli.cr.yp.to 7 | and 8 | https://csrc.nist.gov/projects/lightweight-cryptography/round-2-candidates 9 | The cipher test vectors were pull from the `LWC_AEAD_KAT_256_128.txt` file in the gimli archive. 10 | 11 | Test with 12 | ``` 13 | cargo test 14 | ``` 15 | 16 | # Install 17 | You can install gimli directly via cargo with 18 | ``` 19 | cargo install --git https://github.com/darakian/gimli gimli_rs 20 | ``` 21 | You will then have the tool `gimli_rs` in your path. 22 | 23 | # References 24 | The gimli cipher is described here https://gimli.cr.yp.to/ by Daniel J. Bernstein, et al. 25 | This implementation began as a port of the reference C code and aims to be a pure rust version of the spec. 26 | 27 | # Papers 28 | https://gimli.cr.yp.to/papers.html#gimli-paper 29 | 30 | https://csrc.nist.gov/CSRC/media/Projects/lightweight-cryptography/documents/round-2/spec-doc-rnd2/gimli-spec-round2.pdf 31 | -------------------------------------------------------------------------------- /src/gimli_common.rs: -------------------------------------------------------------------------------- 1 | fn rotate(x: u32, bits: usize) -> u32 { 2 | if bits == 0 { 3 | return x; 4 | }; 5 | return (x << bits) | (x >> (32 - bits)); 6 | } 7 | 8 | pub fn gimli(state: &mut [u32; 12]) { 9 | //12*32bit = 384bit 10 | let mut x; 11 | let mut y; 12 | let mut z; 13 | 14 | for round in (1..=24).rev() { 15 | for column in 0..=3 { 16 | x = rotate(state[column], 24); 17 | y = rotate(state[4 + column], 9); 18 | z = state[8 + column]; 19 | 20 | state[8 + column] = x ^ (z << 1) ^ ((y & z) << 2); 21 | state[4 + column] = y ^ x ^ ((x | z) << 1); 22 | state[column] = z ^ y ^ ((x & y) << 3); 23 | } 24 | 25 | if (round & 3) == 0 { 26 | // small swap: pattern s...s...s... etc. 27 | x = state[0]; 28 | state[0] = state[1]; 29 | state[1] = x; 30 | x = state[2]; 31 | state[2] = state[3]; 32 | state[3] = x; 33 | } 34 | if (round & 3) == 2 { 35 | // big swap: pattern ..S...S...S. etc. 36 | x = state[0]; 37 | state[0] = state[2]; 38 | state[2] = x; 39 | x = state[1]; 40 | state[1] = state[3]; 41 | state[3] = x; 42 | } 43 | 44 | if (round & 3) == 0 { 45 | // add constant: pattern c...c...c... etc. 46 | state[0] ^= 0x9e377900 | round; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/gimli_decrypt.rs: -------------------------------------------------------------------------------- 1 | use crate::gimli_common::gimli; 2 | use std::io; 3 | 4 | pub struct GimliAeadDecryptIter{ 5 | state: [u32; 12], 6 | cipher_message_len: usize, 7 | cipher_message: Box>>, 8 | output_buffer: Vec, 9 | } 10 | 11 | impl GimliAeadDecryptIter{ 12 | pub fn new(key: [u8; 32], 13 | nonce: [u8; 16], 14 | cipher_text_len: usize, 15 | cipher_text: Box>>, 16 | mut associated_data: &[u8]) -> Self{ 17 | 18 | let message_len = cipher_text_len - 16; 19 | let mut state: [u32; 12] = [0; 12]; 20 | let state_8 = unsafe {std::slice::from_raw_parts_mut(state.as_mut_ptr() as *mut u8, 48)}; 21 | 22 | // Init state with key and nonce plus first permute 23 | state_8[..16].clone_from_slice(&nonce); 24 | state_8[16..48].clone_from_slice(&key); 25 | gimli(&mut state); 26 | 27 | // Handle associated data 28 | while associated_data.len() >= 16 { 29 | let state_8 = unsafe {std::slice::from_raw_parts_mut(state.as_mut_ptr() as *mut u8, 48)}; 30 | for i in 0..16 { 31 | state_8[i] ^= associated_data[i] 32 | } 33 | gimli(&mut state); 34 | associated_data = &associated_data[16 as usize..]; 35 | } 36 | let state_8 = unsafe {std::slice::from_raw_parts_mut(state.as_mut_ptr() as *mut u8, 48)}; 37 | for i in 0..associated_data.len() { 38 | state_8[i] ^= associated_data[i] 39 | } 40 | state_8[associated_data.len() as usize] ^= 1; 41 | state_8[47] ^= 1; 42 | gimli(&mut state); 43 | 44 | GimliAeadDecryptIter{ 45 | state: state, 46 | cipher_message_len: message_len, 47 | cipher_message: cipher_text, 48 | output_buffer: Vec::new(), 49 | } 50 | } 51 | } 52 | 53 | impl Iterator for GimliAeadDecryptIter{ 54 | type Item = u8; 55 | fn next(&mut self) -> Option { 56 | if self.output_buffer.len() > 0{ 57 | return Some(self.output_buffer.remove(0)) 58 | } 59 | let state_8 = unsafe {std::slice::from_raw_parts_mut(self.state.as_mut_ptr() as *mut u8, 48)}; 60 | 61 | if self.cipher_message_len >= 16 { 62 | for i in 0..16 { 63 | let current_byte = self.cipher_message.next().unwrap().expect("Read error on input"); 64 | self.output_buffer.push(state_8[i] ^ current_byte); 65 | state_8[i] = current_byte; 66 | self.cipher_message_len -=1; 67 | } 68 | gimli(&mut self.state); 69 | return Some(self.output_buffer.remove(0)) 70 | } 71 | 72 | if self.cipher_message_len <= 15 && self.cipher_message_len > 0 { 73 | for i in 0..self.cipher_message_len { 74 | let current_byte = self.cipher_message.next().unwrap().expect("Read error on input"); 75 | self.output_buffer.push(state_8[i] ^ current_byte); 76 | state_8[i] = current_byte; 77 | } 78 | state_8[self.cipher_message_len as usize] ^= 1; 79 | state_8[47] ^= 1; 80 | gimli(&mut self.state); 81 | let state_8 = unsafe {std::slice::from_raw_parts_mut(self.state.as_mut_ptr() as *mut u8, 48)}; 82 | self.cipher_message_len = 0; 83 | // Handle tag 84 | let mut result: u32 = 0; 85 | for i in 0..16 { 86 | let current_byte = self.cipher_message.next().unwrap().expect("Read error on input"); 87 | result |= (current_byte ^ state_8[i]) as u32; 88 | } 89 | result = result.overflowing_sub(1).0; 90 | result = result >> 16; 91 | assert_ne!(result, 0); // Need a better way to express an error than panic 92 | match self.output_buffer.len() { 93 | 0 => return None, 94 | _ => return Some(self.output_buffer.remove(0)), 95 | } 96 | } 97 | None 98 | } 99 | } 100 | 101 | pub fn gimli_aead_decrypt( 102 | mut cipher_text: impl Iterator>, 103 | cipher_text_len: usize, 104 | mut associated_data: &[u8], 105 | nonce: &[u8; 16], 106 | key: &[u8; 32], 107 | ) -> Result, &'static str> { 108 | if cipher_text_len < 16 { 109 | return Err("Cipher text too short"); 110 | } 111 | 112 | let mut cipher_message_len = cipher_text_len - 16; 113 | let mut output: Vec = Vec::new(); 114 | let mut state: [u32; 12] = [0; 12]; 115 | let state_8 = unsafe {std::slice::from_raw_parts_mut(state.as_mut_ptr() as *mut u8, 48)}; 116 | 117 | // Init state with key and nonce plus first permute 118 | state_8[..16].clone_from_slice(nonce); 119 | state_8[16..48].clone_from_slice(key); 120 | gimli(&mut state); 121 | 122 | // Handle associated data 123 | while associated_data.len() >= 16 { 124 | let state_8 = unsafe {std::slice::from_raw_parts_mut(state.as_mut_ptr() as *mut u8, 48)}; 125 | for i in 0..16 { 126 | state_8[i] ^= associated_data[i] 127 | } 128 | gimli(&mut state); 129 | associated_data = &associated_data[16 as usize..]; 130 | } 131 | let state_8 = unsafe {std::slice::from_raw_parts_mut(state.as_mut_ptr() as *mut u8, 48)}; 132 | for i in 0..associated_data.len() { 133 | state_8[i] ^= associated_data[i] 134 | } 135 | state_8[associated_data.len() as usize] ^= 1; 136 | state_8[47] ^= 1; 137 | gimli(&mut state); 138 | 139 | 140 | // Handle cipher text 141 | while cipher_message_len >= 16 { 142 | let state_8 = unsafe {std::slice::from_raw_parts_mut(state.as_mut_ptr() as *mut u8, 48)}; 143 | for j in 0..16 { 144 | let current_byte = cipher_text.next().unwrap().expect("Read error on input"); 145 | output.push(state_8[j] ^ current_byte); 146 | state_8[j] = current_byte; 147 | } 148 | gimli(&mut state); 149 | cipher_message_len-=16; 150 | } 151 | let state_8 = unsafe {std::slice::from_raw_parts_mut(state.as_mut_ptr() as *mut u8, 48)}; 152 | 153 | for i in 0..cipher_message_len { 154 | let current_byte = cipher_text.next().unwrap().expect("Read error on input"); 155 | output.push(state_8[i] ^ current_byte); 156 | state_8[i] = current_byte; 157 | } 158 | state_8[cipher_message_len as usize] ^= 1; 159 | state_8[47] ^= 1; 160 | gimli(&mut state); 161 | let state_8 = unsafe {std::slice::from_raw_parts_mut(state.as_mut_ptr() as *mut u8, 48)}; 162 | 163 | // Handle tag 164 | let mut result: u32 = 0; 165 | for i in 0..16 { 166 | let current_byte = cipher_text.next().unwrap().expect("Read error on input"); 167 | result |= (current_byte ^ state_8[i]) as u32 168 | } 169 | result = result.overflowing_sub(1).0; 170 | result = result >> 16; 171 | 172 | for i in 0..output.len() { 173 | output[i] &= result as u8; // Valid. Only the first 8 bits of result are possibly non-zero. 174 | } 175 | 176 | if result != 0 { 177 | return Ok(output); 178 | } else { 179 | return Err("Invalid result tag"); 180 | } 181 | } -------------------------------------------------------------------------------- /src/gimli_encrypt.rs: -------------------------------------------------------------------------------- 1 | use crate::gimli_common::gimli; 2 | use std::io; 3 | 4 | pub struct GimliAeadEncryptIter{ 5 | state: [u32; 12], 6 | message_len: usize, 7 | message: Box>>, 8 | output_buffer: Vec, 9 | complete: bool, 10 | last_blocksize: usize, 11 | } 12 | 13 | impl GimliAeadEncryptIter{ 14 | pub fn new(key: [u8; 32], 15 | nonce: [u8; 16], 16 | message_len: usize, 17 | message: Box>>, 18 | mut associated_data: &[u8]) -> Self{ 19 | let mut state: [u32; 12] = [0; 12]; 20 | let state_8 = unsafe {std::slice::from_raw_parts_mut(state.as_mut_ptr() as *mut u8, 48)}; 21 | state_8[..16].clone_from_slice(&nonce); 22 | state_8[16..48].clone_from_slice(&key); 23 | gimli(&mut state); 24 | 25 | while associated_data.len() >= 16 { 26 | let state_8 = unsafe {std::slice::from_raw_parts_mut(state.as_mut_ptr() as *mut u8, 48)}; 27 | for i in 0..16 { 28 | state_8[i] ^= associated_data[i] 29 | } 30 | gimli(&mut state); 31 | associated_data = &associated_data[16 as usize..]; 32 | } 33 | let state_8 = unsafe {std::slice::from_raw_parts_mut(state.as_mut_ptr() as *mut u8, 48)}; 34 | for i in 0..associated_data.len() { 35 | state_8[i] ^= associated_data[i] 36 | } 37 | state_8[associated_data.len() as usize] ^= 1; 38 | state_8[47] ^= 1; 39 | gimli(&mut state); 40 | 41 | GimliAeadEncryptIter{ 42 | state: state, 43 | message_len: message_len, 44 | message: message, 45 | output_buffer: Vec::new(), 46 | complete: false, 47 | last_blocksize: 0 48 | } 49 | } 50 | } 51 | 52 | impl Iterator for GimliAeadEncryptIter{ 53 | type Item = u8; 54 | fn next(&mut self) -> Option { 55 | if self.output_buffer.len() > 0{ 56 | return Some(self.output_buffer.remove(0)) 57 | } 58 | 59 | let state_8 = unsafe {std::slice::from_raw_parts_mut(self.state.as_mut_ptr() as *mut u8, 48)}; 60 | if self.message_len >= 16 { 61 | for i in 0..16 { 62 | state_8[i] ^= self.message.next().unwrap().expect("Read error on input"); 63 | self.output_buffer.push(state_8[i]); 64 | self.message_len -=1; 65 | } 66 | gimli(&mut self.state); 67 | return Some(self.output_buffer.remove(0)) 68 | } 69 | 70 | if self.message_len < 16 && self.message_len > 0 { 71 | self.last_blocksize = self.message_len; 72 | for i in 0..self.message_len { 73 | let foo = self.message.next().unwrap().expect("Read error on input"); 74 | state_8[i] ^= foo; 75 | self.output_buffer.push(state_8[i]); 76 | self.message_len -=1; 77 | } 78 | return Some(self.output_buffer.remove(0)) 79 | } 80 | 81 | if self.message_len == 0 && self.complete == false{ 82 | state_8[self.last_blocksize as usize] ^= 1; 83 | state_8[47] ^= 1; 84 | gimli(&mut self.state); 85 | let state_8 = unsafe {std::slice::from_raw_parts_mut(self.state.as_mut_ptr() as *mut u8, 48)}; 86 | for i in 0..16 { 87 | self.output_buffer.push(state_8[i]); 88 | } 89 | self.complete = true; 90 | return Some(self.output_buffer.remove(0)) 91 | } 92 | 93 | return None 94 | 95 | } 96 | } 97 | 98 | pub fn gimli_aead_encrypt( 99 | mut message: impl Iterator>, 100 | mut message_len: usize, 101 | mut associated_data: &[u8], 102 | nonce: &[u8; 16], 103 | key: &[u8; 32], 104 | ) -> Vec { 105 | let mut output: Vec = Vec::new(); 106 | let mut state: [u32; 12] = [0; 12]; 107 | let state_8 = unsafe {std::slice::from_raw_parts_mut(state.as_mut_ptr() as *mut u8, 48)}; 108 | 109 | // Init state with key and nonce plus first permute 110 | state_8[..16].clone_from_slice(nonce); 111 | state_8[16..48].clone_from_slice(key); 112 | gimli(&mut state); 113 | 114 | while associated_data.len() >= 16 { 115 | let state_8 = unsafe {std::slice::from_raw_parts_mut(state.as_mut_ptr() as *mut u8, 48)}; 116 | for i in 0..16 { 117 | state_8[i] ^= associated_data[i] 118 | } 119 | gimli(&mut state); 120 | associated_data = &associated_data[16 as usize..]; 121 | } 122 | let state_8 = unsafe {std::slice::from_raw_parts_mut(state.as_mut_ptr() as *mut u8, 48)}; 123 | for i in 0..associated_data.len() { 124 | state_8[i] ^= associated_data[i] 125 | } 126 | state_8[associated_data.len() as usize] ^= 1; 127 | state_8[47] ^= 1; 128 | gimli(&mut state); 129 | 130 | while message_len >= 16 { 131 | let state_8 = unsafe {std::slice::from_raw_parts_mut(state.as_mut_ptr() as *mut u8, 48)}; 132 | for i in 0..16 { 133 | state_8[i] ^= message.next().unwrap().expect("Read error on input"); 134 | output.push(state_8[i]); 135 | message_len -=1; 136 | } 137 | gimli(&mut state); 138 | } 139 | 140 | let state_8 = unsafe {std::slice::from_raw_parts_mut(state.as_mut_ptr() as *mut u8, 48)}; 141 | for i in 0..message_len { 142 | state_8[i] ^= message.next().unwrap().expect("Read error on input"); 143 | output.push(state_8[i]); 144 | } 145 | state_8[message_len as usize] ^= 1; 146 | state_8[47] ^= 1; 147 | gimli(&mut state); 148 | let state_8 = unsafe {std::slice::from_raw_parts_mut(state.as_mut_ptr() as *mut u8, 48)}; 149 | for i in 0..16 { 150 | output.push(state_8[i]); 151 | } 152 | 153 | return output; 154 | } -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use crate::gimli_common::gimli; 2 | use crate::gimli_decrypt::{gimli_aead_decrypt, GimliAeadDecryptIter}; 3 | use crate::gimli_encrypt::{gimli_aead_encrypt, GimliAeadEncryptIter}; 4 | use std::cmp::min; 5 | use std::io; 6 | 7 | pub mod gimli_encrypt; 8 | pub mod gimli_decrypt; 9 | pub mod gimli_common; 10 | 11 | static RATE_IN_BYTES: u64 = 16; 12 | 13 | pub fn gimli_hash(mut input: impl Iterator>, mut input_byte_len: u64, mut output_byte_len: u64) -> Vec { 14 | let mut state: [u32; 12] = [0; 12]; 15 | let mut block_size: u64 = 0; 16 | 17 | while input_byte_len > 0 { 18 | let state_8 = unsafe {std::slice::from_raw_parts_mut(state.as_mut_ptr() as *mut u8, 48)}; 19 | block_size = min(input_byte_len, RATE_IN_BYTES); 20 | for i in 0..block_size { 21 | state_8[i as usize] ^= input.next().unwrap().expect("Read error on input"); 22 | } 23 | input_byte_len -= block_size; 24 | 25 | if block_size == RATE_IN_BYTES { 26 | gimli(&mut state); 27 | block_size = 0; 28 | } 29 | } 30 | 31 | let state_8 = unsafe {std::slice::from_raw_parts_mut(state.as_mut_ptr() as *mut u8, 48)}; 32 | state_8[block_size as usize] ^= 0x1F; 33 | state_8[(RATE_IN_BYTES - 1) as usize] ^= 0x80; 34 | gimli(&mut state); // Calling gimli invalidates other references to state. ie stats_8 35 | 36 | let mut output: Vec = Vec::with_capacity(output_byte_len as usize); 37 | while output_byte_len > 0 { 38 | let state_8 = unsafe {std::slice::from_raw_parts_mut(state.as_mut_ptr() as *mut u8, 48)}; 39 | block_size = min(output_byte_len, RATE_IN_BYTES); 40 | output.extend_from_slice(&state_8[..block_size as usize]); 41 | output_byte_len -= block_size; 42 | if output_byte_len > 0 { 43 | gimli(&mut state); 44 | } 45 | } 46 | return output; 47 | } 48 | 49 | 50 | 51 | 52 | #[cfg(test)] 53 | mod tests{ 54 | use super::*; 55 | mod cipher_test; 56 | use crate::tests::cipher_test::cipher_test::get_cipher_vectors; 57 | 58 | #[test] 59 | fn hash_test(){ 60 | // (Plaintext, hash, hash_len) 61 | let hash_vectors = vec![ 62 | ("There's plenty for the both of us, may the best Dwarf win.", "4afb3ff784c7ad6943d49cf5da79facfa7c4434e1ce44f5dd4b28f91a84d22c8", 32, ), 63 | ("If anyone was to ask for my opinion, which I note they're not, I'd say we were taking the long way around.", "ba82a16a7b224c15bed8e8bdc88903a4006bc7beda78297d96029203ef08e07c", 32), 64 | ("Speak words we can all understand!", "8dd4d132059b72f8e8493f9afb86c6d86263e7439fc64cbb361fcbccf8b01267", 32), 65 | ("It's true you don't see many Dwarf-women. And in fact, they are so alike in voice and appearance, that they are often mistaken for Dwarf-men. And this in turn has given rise to the belief that there are no Dwarf-women, and that Dwarves just spring out of holes in the ground! Which is, of course, ridiculous.", "ebe9bfc05ce15c73336fc3c5b52b01f75cf619bb37f13bfc7f567f9d5603191a", 32), 66 | ("", "b0634b2c0b082aedc5c0a2fe4ee3adcfc989ec05de6f00addb04b3aaac271f67", 32) 67 | ]; 68 | 69 | for vec in hash_vectors.iter(){ 70 | let input_len = vec.0.len() as u64; 71 | assert_eq!(vec.1, gimli_hash( 72 | vec.0 73 | .to_string() 74 | .into_bytes() 75 | .into_iter() 76 | .map(|x| Ok(x)), 77 | input_len, 78 | vec.2).iter().map(|x| format!("{:02x?}", x)).collect::() 79 | ) 80 | } 81 | } 82 | 83 | #[test] 84 | fn test_cipher(){ 85 | 86 | // Test key = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F 87 | let key = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F]; 88 | // Test nonce = 000102030405060708090A0B0C0D0E0F 89 | let nonce = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F]; 90 | // (Plaintext, AD, Ciphertext) 91 | let cipher_vectors = get_cipher_vectors(); 92 | 93 | for vec in cipher_vectors.iter(){ 94 | let pt_len = vec.0.len(); 95 | let pt = vec.0.clone().into_iter().map(|x| Ok(x)); 96 | let assoc_d = &vec.1; 97 | let ct = vec.2.clone().into_iter().map(|x| Ok(x)); 98 | let ct_len = vec.2.len(); 99 | 100 | let ge_iter = GimliAeadEncryptIter::new( 101 | key, 102 | nonce, 103 | pt_len, 104 | Box::new(pt.clone()), 105 | assoc_d); 106 | let result: Vec = ge_iter.collect(); 107 | assert_eq!(vec.2, result); 108 | 109 | 110 | assert_eq!(vec.2, gimli_aead_encrypt( 111 | pt.clone(), 112 | pt_len, 113 | assoc_d, 114 | &nonce, 115 | &key)); 116 | 117 | let gd_iter = GimliAeadDecryptIter::new( 118 | key, 119 | nonce, 120 | ct_len, 121 | Box::new(ct.clone()), 122 | assoc_d, 123 | ); 124 | let pt: Vec = gd_iter.collect(); 125 | assert_eq!(pt, pt); 126 | assert_eq!(pt, gimli_aead_decrypt( 127 | ct, 128 | ct_len, 129 | &vec.1, 130 | &nonce, 131 | &key).expect("Error in test decryption")); 132 | } 133 | } 134 | } 135 | 136 | 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use gimli_rs::gimli_hash; 2 | use gimli_rs::gimli_encrypt::GimliAeadEncryptIter; 3 | use gimli_rs::gimli_decrypt::GimliAeadDecryptIter; 4 | use structopt::StructOpt; 5 | use structopt::clap::arg_enum; 6 | use std::fs::File; 7 | use std::fs; 8 | use std::io::{BufReader, BufWriter}; 9 | use std::io::prelude::*; 10 | use rand::prelude::*; 11 | 12 | arg_enum! { 13 | #[derive(Debug)] 14 | enum GimliMode { 15 | Hash, 16 | Encrypt, 17 | Decrypt, 18 | } 19 | } 20 | 21 | #[derive(Debug, StructOpt)] 22 | #[structopt( 23 | name = "Gimli-rs", 24 | about = "An implementation of the gimli cipher with hashing and AEAD functionality" 25 | )] 26 | 27 | 28 | struct Opt { 29 | /// Input. 30 | #[structopt( 31 | short = "i", 32 | long = "input" 33 | )] 34 | input: String, 35 | 36 | /// Enable to parse input as a file path 37 | #[structopt( 38 | short = "f", 39 | long = "is_file" 40 | )] 41 | is_file: bool, 42 | 43 | /// Mode of operation. 44 | #[structopt( 45 | short = "m", 46 | long = "mode", 47 | possible_values = &GimliMode::variants(), 48 | default_value = "hash", 49 | case_insensitive = true 50 | )] 51 | mode: GimliMode, 52 | 53 | /// Output. Defaults to std out. 54 | #[structopt( 55 | short = "o", 56 | long = "out", 57 | )] 58 | output: Option, 59 | 60 | /// Crypto Key. 61 | #[structopt( 62 | short = "k", 63 | long = "key", 64 | required_if("mode", "encrypt"), 65 | required_if("mode", "decrypt"), 66 | default_value = "", 67 | )] 68 | key: String, 69 | 70 | /// Associated data. 71 | #[structopt( 72 | short = "a", 73 | long = "associated_data", 74 | required_if("mode", "encrypt"), 75 | required_if("mode", "decrypt"), 76 | default_value = "", 77 | )] 78 | ad: String, 79 | 80 | /// Hash length. Required for Hash mode. 81 | #[structopt( 82 | short = "l", 83 | long = "length", 84 | default_value = "32", 85 | requires_if("mode", "hash"), 86 | )] 87 | out_length: u64, 88 | } 89 | 90 | 91 | 92 | 93 | fn main() { 94 | let opt = Opt::from_args(); 95 | println!("{:?}", opt); 96 | 97 | match opt.mode { 98 | GimliMode::Hash => { 99 | match opt.is_file { 100 | true => { 101 | let f = File::open(opt.input).expect("Error opening file for hashing."); 102 | let file_len = f.metadata().expect("Error reading input file length").len(); 103 | let reader = BufReader::new(f); 104 | let result = gimli_hash( 105 | reader.bytes(), 106 | file_len, 107 | opt.out_length); 108 | match opt.output { 109 | Some(file_path) => { 110 | let mut file = File::create(file_path).expect("Failed to open output file"); 111 | file.write_all(&result).expect("Error writing to output file"); 112 | }, 113 | None => { 114 | for byte in result.iter(){ 115 | print!("{:02x?}", byte); 116 | } 117 | }, 118 | } 119 | } 120 | false => { 121 | let input_len = opt.input.as_bytes().len() as u64; 122 | let result = gimli_hash( 123 | opt.input.into_bytes().into_iter().map(|x| Ok(x)), 124 | input_len, 125 | opt.out_length); 126 | match opt.output { 127 | Some(file_path) => { 128 | let mut file = File::create(file_path).expect("Failed to open output file"); 129 | file.write_all(&result).expect("Error writing to output file"); 130 | }, 131 | None => { 132 | for byte in result.iter(){ 133 | print!("{:02x?}", byte); 134 | } 135 | }, 136 | } 137 | } 138 | } 139 | }, 140 | GimliMode::Encrypt => { 141 | let mut rng = rand::thread_rng(); 142 | let mut nonce = [0u8; 16]; 143 | rng.fill_bytes(&mut nonce); 144 | let key_len = opt.key.as_bytes().len() as u64; 145 | let key_hash = gimli_hash( 146 | opt.key.into_bytes().into_iter().map(|x| Ok(x)), 147 | key_len, 148 | 32); 149 | let mut key_array = [0; 32]; 150 | key_array.copy_from_slice(&key_hash); 151 | match opt.is_file { 152 | true => { 153 | let input_file = File::open(&opt.input).expect("Error opening input file."); 154 | let bufreader = BufReader::new(input_file); 155 | let contents_len = fs::metadata(&opt.input).expect("Error reading metadata").len(); 156 | let cipher_text = GimliAeadEncryptIter::new( 157 | key_array, 158 | nonce, 159 | contents_len as usize, 160 | Box::new(bufreader.bytes()), 161 | opt.ad.as_bytes() 162 | ); 163 | match opt.output { 164 | Some(file_path) => { 165 | write_encrypted_file(file_path, &nonce, cipher_text.into_iter()); 166 | }, 167 | None => { 168 | for byte in cipher_text.into_iter(){ 169 | print!("{:02x?}", byte); 170 | } 171 | }, 172 | } 173 | } 174 | false => { 175 | let input_len = opt.input.as_bytes().len(); 176 | let cipher_text = GimliAeadEncryptIter::new( 177 | key_array, 178 | nonce, 179 | input_len, 180 | Box::new(opt.input.into_bytes().into_iter().map(|x| Ok(x))), 181 | opt.ad.as_bytes() 182 | ); 183 | match opt.output { 184 | Some(file_path) => { 185 | write_encrypted_file(file_path, &nonce, cipher_text.into_iter()); 186 | }, 187 | None => { 188 | for byte in cipher_text.into_iter(){ 189 | print!("{:02x?}", byte); 190 | } 191 | }, 192 | } 193 | } 194 | } 195 | 196 | }, 197 | GimliMode::Decrypt => { 198 | let key_len = opt.key.as_bytes().len() as u64; 199 | let key_hash = gimli_hash( 200 | opt.key.into_bytes().into_iter().map(|x| Ok(x)), 201 | key_len, 202 | 32); 203 | let mut key_array = [0; 32]; 204 | key_array.copy_from_slice(&key_hash); 205 | match opt.is_file { 206 | true => { 207 | let input_file = File::open(&opt.input).expect("Error opening input file."); 208 | let mut bufreader = BufReader::new(input_file); 209 | let mut nonce = [0; 16]; 210 | bufreader.read_exact(&mut nonce).expect("Error reading input file"); 211 | let contents_len = fs::metadata(&opt.input).expect("Error reading metadata").len() - 16; 212 | let plain_text = GimliAeadDecryptIter::new( 213 | key_array, 214 | nonce, 215 | contents_len as usize, 216 | Box::new(bufreader.bytes()), 217 | opt.ad.as_bytes() 218 | ); 219 | match opt.output { 220 | Some(file_path) => { 221 | let file = File::create(file_path).expect("Failed to open output file"); 222 | let mut writer = BufWriter::new(file); 223 | for byte in plain_text { 224 | writer.write(&[byte]).expect("Error writing to output file"); 225 | } 226 | }, 227 | None => { 228 | for byte in plain_text.into_iter(){ 229 | print!("{:02x?}", byte); 230 | } 231 | }, 232 | } 233 | } 234 | false => { 235 | let mut input_bytes = opt.input.into_bytes(); 236 | let mut nonce = [0; 16]; 237 | nonce.copy_from_slice(&input_bytes[..16]); 238 | let input_len = input_bytes.len(); 239 | input_bytes = input_bytes[16..].to_vec(); 240 | let plain_text = GimliAeadDecryptIter::new( 241 | key_array, 242 | nonce, 243 | input_len, 244 | Box::new(input_bytes.into_iter().map(|x| Ok(x))), 245 | opt.ad.as_bytes() 246 | ); 247 | match opt.output { 248 | Some(file_path) => { 249 | let mut file = File::create(file_path).expect("Failed to open output file"); 250 | for byte in plain_text.into_iter(){ 251 | file.write_all(&[byte]).expect("Error writing to output file"); 252 | } 253 | }, 254 | None => { 255 | for byte in plain_text.into_iter(){ 256 | print!("{:02x?}", byte); 257 | } 258 | }, 259 | } 260 | } 261 | } 262 | 263 | }, 264 | 265 | } 266 | 267 | fn write_encrypted_file>(path: String, nonce: &[u8; 16], ciphertext: T) -> (){ 268 | let file = File::create(path).expect("Failed to open output file"); 269 | let mut writer = BufWriter::new(file); 270 | writer.write_all(nonce).expect("Error writing to output file"); 271 | for byte in ciphertext { 272 | writer.write(&[byte]).expect("Error writing to output file"); 273 | 274 | } 275 | } 276 | 277 | } 278 | --------------------------------------------------------------------------------