diff --git a/.github/workflows/rc6.yml b/.github/workflows/rc6.yml new file mode 100644 index 00000000..fbdf9035 --- /dev/null +++ b/.github/workflows/rc6.yml @@ -0,0 +1,59 @@ +name: rc6 +on: + pull_request: + paths: + - "rc6/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: rc6 + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.65.0 # MSRVs + - stable + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v3 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.target }} + - run: cargo build --no-default-features --release --target ${{ matrix.target }} + + minimal-versions: + uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master + with: + working-directory: ${{ github.workflow }} + + test: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.65.0 # MSRVs + - stable + steps: + - uses: actions/checkout@v3 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + - run: cargo check --all-features + - run: cargo test --no-default-features + - run: cargo test + - run: cargo test --all-features diff --git a/Cargo.lock b/Cargo.lock index 57e8b41e..7fc01f51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -190,6 +190,13 @@ dependencies = [ "cipher", ] +[[package]] +name = "rc6" +version = "0.1.0" +dependencies = [ + "cipher", +] + [[package]] name = "serpent" version = "0.6.0-pre" diff --git a/Cargo.toml b/Cargo.toml index cbf5fa31..7585a409 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ members = [ "magma", "rc2", "rc5", + "rc6", "serpent", "sm4", "speck", diff --git a/rc6/Cargo.toml b/rc6/Cargo.toml new file mode 100644 index 00000000..1e6a880d --- /dev/null +++ b/rc6/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "rc6" +version = "0.1.0" +description = "RC6 block cipher" +authors = ["RustCrypto Developers"] +edition = "2021" +license = "MIT OR Apache-2.0" +readme = "README.md" +repository = "https://github.com/RustCrypto/block-ciphers" +keywords = ["crypto", "rc6", "block-cipher"] +categories = ["cryptography"] + +[dependencies] +cipher = { version = "0.5.0-pre.6", features = ["zeroize"] } + +[dev-dependencies] +cipher = { version = "0.5.0-pre.6", features = ["dev"] } + +[features] +zeroize = [] diff --git a/rc6/LICENSE-APACHE b/rc6/LICENSE-APACHE new file mode 100644 index 00000000..652489d7 --- /dev/null +++ b/rc6/LICENSE-APACHE @@ -0,0 +1,13 @@ +Copyright 2017 Damian Czaja + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/rc6/LICENSE-MIT b/rc6/LICENSE-MIT new file mode 100644 index 00000000..069bb21e --- /dev/null +++ b/rc6/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2017 Damian Czaja + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/rc6/README.md b/rc6/README.md new file mode 100644 index 00000000..7b54516e --- /dev/null +++ b/rc6/README.md @@ -0,0 +1,72 @@ +# [RustCrypto]: RC6 Cipher + +[![crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +[![Build Status][build-image]][build-link] +![Apache2/MIT licensed][license-image] +![Rust Version][rustc-image] +[![Project Chat][chat-image]][chat-link] +[![HAZMAT][hazmat-image]][hazmat-link] + +Pure Rust implementation of the [RC6] block cipher. + +[Documentation][docs-link] + +## ⚠️ Security Warning: [Hazmat!][hazmat-link] + +This crate does not ensure ciphertexts are authentic (i.e. by using a MAC to +verify ciphertext integrity), which can lead to serious vulnerabilities +if used incorrectly! + +No security audits of this crate have ever been performed, and it has not been +thoroughly assessed to ensure its operation is constant-time on common CPU +architectures. + +USE AT YOUR OWN RISK! + +## Minimum Supported Rust Version + +Rust **1.56** or higher. + +Minimum supported Rust version can be changed in the future, but it will be +done with a minor version bump. + +## SemVer Policy + +- All on-by-default features of this library are covered by SemVer +- MSRV is considered exempt from SemVer as noted above + +## License + +Licensed under either of: + + * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) + * [MIT license](http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[//]: # (badges) + +[crate-image]: https://img.shields.io/crates/v/rc6.svg +[crate-link]: https://crates.io/crates/rc6 +[docs-image]: https://docs.rs/rc6/badge.svg +[docs-link]: https://docs.rs/rc6/ +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.56+-blue.svg +[hazmat-image]: https://img.shields.io/badge/crypto-hazmat%E2%9A%A0-red.svg +[hazmat-link]: https://github.com/RustCrypto/meta/blob/master/HAZMAT.md +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260039-block-ciphers +[build-image]: https://github.com/RustCrypto/block-ciphers/actions/workflows/rc6.yml/badge.svg +[build-link]: https://github.com/RustCrypto/block-ciphers/actions/workflows/rc6.yml + +[//]: # (general links) + +[RustCrypto]: https://github.com/RustCrypto/ +[RC6]: https://en.wikipedia.org/wiki/RC6 diff --git a/rc6/src/block_cipher.rs b/rc6/src/block_cipher.rs new file mode 100644 index 00000000..e5ea04a3 --- /dev/null +++ b/rc6/src/block_cipher.rs @@ -0,0 +1,352 @@ +use core::ops::{Add, Div, Mul, Sub}; + +use cipher::{ + array::ArraySize, + crypto_common::BlockSizes, + inout::InOut, + typenum::{Diff, IsLess, Le, NonZero, Sum, Unsigned, U1, U12, U16, U2, U20, U24, U256, U4, U8}, + AlgorithmName, Block, BlockBackend, BlockCipher, BlockCipherDecrypt, BlockCipherEncrypt, + BlockSizeUser, KeyInit, KeySizeUser, ParBlocksSizeUser, +}; + +use crate::core::{BlockSize, ExpandedKeyTableSize, KeyAsWordsSize, Word, RC6}; + +impl KeyInit for RC6 +where + W: Word, + // Block size + W::Bytes: Mul, + BlockSize: BlockSizes, + // Rounds range + R: Unsigned, + R: IsLess, + Le: NonZero, + // ExpandedKeyTableSize + R: Add, + Sum: Mul, + ExpandedKeyTableSize: ArraySize, + // Key range + B: ArraySize, + B: IsLess, + Le: NonZero, + // KeyAsWordsSize + B: Add, + Sum: Sub, + Diff, U1>: Div, + KeyAsWordsSize: ArraySize, +{ + fn new(key: &cipher::Key) -> Self { + Self::new(key) + } +} + +impl KeySizeUser for RC6 +where + W: Word, + // Block size + W::Bytes: Mul, + BlockSize: BlockSizes, + // Rounds range + R: Unsigned, + R: IsLess, + Le: NonZero, + // ExpandedKeyTableSize + R: Add, + Sum: Mul, + ExpandedKeyTableSize: ArraySize, + B: ArraySize, +{ + type KeySize = B; +} + +impl BlockCipher for RC6 +where + W: Word, + // Block size + W::Bytes: Mul, + BlockSize: BlockSizes, + // Rounds range + R: Unsigned, + R: IsLess, + Le: NonZero, + // ExpandedKeyTableSize + R: Add, + Sum: Mul, + ExpandedKeyTableSize: ArraySize, +{ +} + +impl BlockSizeUser for RC6 +where + W: Word, + // Block size + W::Bytes: Mul, + BlockSize: BlockSizes, + // Rounds range + R: Unsigned, + R: IsLess, + Le: NonZero, + // ExpandedKeyTableSize + R: Add, + Sum: Mul, + ExpandedKeyTableSize: ArraySize, +{ + type BlockSize = BlockSize; +} + +impl BlockCipherEncrypt for RC6 +where + W: Word, + // Block size + W::Bytes: Mul, + BlockSize: BlockSizes, + // Rounds range + R: Unsigned, + R: IsLess, + Le: NonZero, + // ExpandedKeyTableSize + R: Add, + Sum: Mul, + ExpandedKeyTableSize: ArraySize, + // Key range + B: BlockSizes, + B: IsLess, + Le: NonZero, + // KeyAsWordsSize + B: Add, + Sum: Sub, + Diff, U1>: Div, + KeyAsWordsSize: ArraySize, +{ + fn encrypt_with_backend(&self, f: impl cipher::BlockClosure) { + f.call(&mut RC6EncryptBackend { enc_dec: self }) + } +} + +struct RC6EncryptBackend<'a, W, R, B> +where + W: Word, + // Block size + W::Bytes: Mul, + BlockSize: BlockSizes, + // Rounds range + R: Unsigned, + R: IsLess, + Le: NonZero, + // ExpandedKeyTableSize + R: Add, + Sum: Mul, + ExpandedKeyTableSize: ArraySize, +{ + enc_dec: &'a RC6, +} +impl<'a, W, R, B> BlockSizeUser for RC6EncryptBackend<'a, W, R, B> +where + W: Word, + // Block size + W::Bytes: Mul, + BlockSize: BlockSizes, + // Rounds range + R: Unsigned, + R: IsLess, + Le: NonZero, + // ExpandedKeyTableSize + R: Add, + Sum: Mul, + ExpandedKeyTableSize: ArraySize, +{ + type BlockSize = BlockSize; +} + +impl<'a, W, R, B> ParBlocksSizeUser for RC6EncryptBackend<'a, W, R, B> +where + W: Word, + // Block size + W::Bytes: Mul, + BlockSize: BlockSizes, + // Rounds range + R: Unsigned, + R: IsLess, + Le: NonZero, + // ExpandedKeyTableSize + R: Add, + Sum: Mul, + ExpandedKeyTableSize: ArraySize, +{ + type ParBlocksSize = U1; +} + +impl<'a, W, R, B> BlockBackend for RC6EncryptBackend<'a, W, R, B> +where + W: Word, + // Block size + W::Bytes: Mul, + BlockSize: BlockSizes, + // Rounds range + R: Unsigned, + R: IsLess, + Le: NonZero, + // ExpandedKeyTableSize + R: Add, + Sum: Mul, + ExpandedKeyTableSize: ArraySize, + // Key range + B: BlockSizes, + B: IsLess, + Le: NonZero, + // KeyAsWordsSize + B: Add, + Sum: Sub, + Diff, U1>: Div, + KeyAsWordsSize: ArraySize, +{ + #[inline(always)] + fn proc_block(&mut self, block: InOut<'_, '_, Block>) { + let backend = self.enc_dec; + backend.encrypt(block) + } +} + +impl BlockCipherDecrypt for RC6 +where + W: Word, + // Block size + W::Bytes: Mul, + BlockSize: BlockSizes, + // Rounds range + R: Unsigned, + R: IsLess, + Le: NonZero, + // ExpandedKeyTableSize + R: Add, + Sum: Mul, + ExpandedKeyTableSize: ArraySize, + // Key range + B: BlockSizes, + B: IsLess, + Le: NonZero, + // KeyAsWordsSize + B: Add, + Sum: Sub, + Diff, U1>: Div, + KeyAsWordsSize: ArraySize, +{ + fn decrypt_with_backend(&self, f: impl cipher::BlockClosure) { + f.call(&mut RC6DecryptBackend { enc_dec: self }) + } +} + +struct RC6DecryptBackend<'a, W, R, B> +where + W: Word, + // Block size + W::Bytes: Mul, + BlockSize: BlockSizes, + // Rounds range + R: Unsigned, + R: IsLess, + Le: NonZero, + // ExpandedKeyTableSize + R: Add, + Sum: Mul, + ExpandedKeyTableSize: ArraySize, +{ + enc_dec: &'a RC6, +} +impl<'a, W, R, B> BlockSizeUser for RC6DecryptBackend<'a, W, R, B> +where + W: Word, + // Block size + W::Bytes: Mul, + BlockSize: BlockSizes, + // Rounds range + R: Unsigned, + R: IsLess, + Le: NonZero, + // ExpandedKeyTableSize + R: Add, + Sum: Mul, + ExpandedKeyTableSize: ArraySize, +{ + type BlockSize = BlockSize; +} + +impl<'a, W, R, B> ParBlocksSizeUser for RC6DecryptBackend<'a, W, R, B> +where + W: Word, + // Block size + W::Bytes: Mul, + BlockSize: BlockSizes, + // Rounds range + R: Unsigned, + R: IsLess, + Le: NonZero, + // ExpandedKeyTableSize + R: Add, + Sum: Mul, + ExpandedKeyTableSize: ArraySize, +{ + type ParBlocksSize = U1; +} + +impl<'a, W, R, B> BlockBackend for RC6DecryptBackend<'a, W, R, B> +where + W: Word, + // Block size + W::Bytes: Mul, + BlockSize: BlockSizes, + // Rounds range + R: Unsigned, + R: IsLess, + Le: NonZero, + // ExpandedKeyTableSize + R: Add, + Sum: Mul, + ExpandedKeyTableSize: ArraySize, + // Key range + B: ArraySize, + B: IsLess, + Le: NonZero, + // KeyAsWordsSize + B: Add, + Sum: Sub, + Diff, U1>: Div, + KeyAsWordsSize: ArraySize, +{ + #[inline(always)] + fn proc_block(&mut self, block: InOut<'_, '_, Block>) { + let backend = self.enc_dec; + backend.decrypt(block) + } +} + +impl AlgorithmName for RC6 +where + W: Word, + // Block size + W::Bytes: Mul, + BlockSize: BlockSizes, + // Rounds range + R: Unsigned, + R: IsLess, + Le: NonZero, + // ExpandedKeyTableSize + R: Add, + Sum: Mul, + ExpandedKeyTableSize: ArraySize, +{ + fn write_alg_name(f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "RC6 - {}/{}/{}", + core::any::type_name::(), + ::to_u8(), + ::to_u8(), + ) + } +} + +pub type RC6_8_12_4 = RC6; +pub type RC6_16_16_8 = RC6; +pub type RC6_32_20_16 = RC6; +pub type RC6_64_24_24 = RC6; diff --git a/rc6/src/core/backend.rs b/rc6/src/core/backend.rs new file mode 100644 index 00000000..7bfbc785 --- /dev/null +++ b/rc6/src/core/backend.rs @@ -0,0 +1,232 @@ +use core::{ + cmp::max, + marker::PhantomData, + ops::{Add, Div, Mul, Sub}, +}; + +use cipher::{ + array::{Array, ArraySize}, + crypto_common::BlockSizes, + inout::InOut, + typenum::{Diff, IsLess, Le, NonZero, Sum, Unsigned, U1, U2, U256, U4}, +}; + +use super::{ + Block, BlockSize, ExpandedKeyTable, ExpandedKeyTableSize, Key, KeyAsWords, KeyAsWordsSize, Word, +}; + +pub struct RC6 +where + W: Word, + R: Unsigned, + R: IsLess, + // ExpandedKeyTableSize + R: Add, + Sum: Mul, + ExpandedKeyTableSize: ArraySize, +{ + key_table: ExpandedKeyTable, + _key_size: PhantomData, +} + +impl RC6 +where + W: Word, + // Block size + W::Bytes: Mul, + BlockSize: BlockSizes, + // Rounds range + R: Unsigned, + R: IsLess, + Le: NonZero, + // ExpandedKeyTableSize + R: Add, + Sum: Mul, + ExpandedKeyTableSize: ArraySize, + // Key range + B: ArraySize, + B: IsLess, + Le: NonZero, + // KeyAsWordsSize + B: Add, + Sum: Sub, + Diff, U1>: Div, + KeyAsWordsSize: ArraySize, +{ + pub fn new(key: &Key) -> RC6 { + Self { + key_table: Self::substitute_key(key), + _key_size: PhantomData, + } + } + + fn substitute_key(key: &Key) -> ExpandedKeyTable { + let key_as_words = Self::key_into_words(key); + let expanded_key_table = Self::initialize_expanded_key_table(); + + Self::mix_in(expanded_key_table, key_as_words) + } + + fn key_into_words(key: &Key) -> KeyAsWords { + // can be uninitialized + let mut key_as_words: Array> = Array::default(); + + for i in (0..B::USIZE).rev() { + key_as_words[i / W::Bytes::USIZE] = + key_as_words[i / W::Bytes::USIZE].rotate_left(W::EIGHT) + key[i].into(); + // no need for wrapping addition since we are adding a byte sized uint onto an uint with its lsb byte zeroed + } + + key_as_words + } + + fn initialize_expanded_key_table() -> ExpandedKeyTable { + // must be zero initialized + let mut expanded_key_table: Array> = Array::from_fn(|_| W::ZERO); + + expanded_key_table[0] = W::P; + for i in 1..expanded_key_table.len() { + expanded_key_table[i] = expanded_key_table[i - 1].wrapping_add(W::Q); + } + + expanded_key_table + } + + fn mix_in( + mut key_table: ExpandedKeyTable, + mut key_as_words: KeyAsWords, + ) -> ExpandedKeyTable { + let (mut expanded_key_index, mut key_as_words_index) = (0, 0); + let (mut a, mut b) = (W::ZERO, W::ZERO); + + for _ in 0..3 * max(key_as_words.len(), key_table.len()) { + key_table[expanded_key_index] = key_table[expanded_key_index] + .wrapping_add(a) + .wrapping_add(b) + .rotate_left(W::THREE); + + a = key_table[expanded_key_index]; + + key_as_words[key_as_words_index] = key_as_words[key_as_words_index] + .wrapping_add(a) + .wrapping_add(b) + .rotate_left(a.wrapping_add(b)); + + b = key_as_words[key_as_words_index]; + + expanded_key_index = (expanded_key_index + 1) % key_table.len(); + key_as_words_index = (key_as_words_index + 1) % key_as_words.len(); + } + + key_table + } +} + +impl RC6 +where + W: Word, + // Block size + W::Bytes: Mul, + BlockSize: BlockSizes, + // Rounds range + R: Unsigned, + R: IsLess, + Le: NonZero, + // ExpandedKeyTableSize + R: Add, + Sum: Mul, + ExpandedKeyTableSize: ArraySize, +{ + pub fn encrypt(&self, mut block: InOut<'_, '_, Block>) { + let (mut a, mut b, mut c, mut d) = Self::words_from_block(block.get_in()); + let key = &self.key_table; + let log_w = W::from((usize::BITS - 1 - (W::Bytes::USIZE * 8).leading_zeros()) as u8); + + b = b.wrapping_add(key[0]); + d = d.wrapping_add(key[1]); + + for i in 1..=R::USIZE { + let t = b + .wrapping_mul(b.wrapping_mul(W::from(2)).wrapping_add(W::from(1))) + .rotate_left(log_w); + let u = d + .wrapping_mul(d.wrapping_mul(W::from(2)).wrapping_add(W::from(1))) + .rotate_left(log_w); + a = a.bitxor(t).rotate_left(u).wrapping_add(key[2 * i]); + c = c.bitxor(u).rotate_left(t).wrapping_add(key[2 * i + 1]); + let tmp = a; + a = b; + b = c; + c = d; + d = tmp; + } + + a = a.wrapping_add(key[2 * R::USIZE + 2]); + c = c.wrapping_add(key[2 * R::USIZE + 3]); + + Self::block_from_words(a, b, c, d, block.get_out()) + } + + pub fn decrypt(&self, mut block: InOut<'_, '_, Block>) { + let (mut a, mut b, mut c, mut d) = Self::words_from_block(block.get_in()); + let key = &self.key_table; + let log_w = W::from((usize::BITS - 1 - (W::Bytes::USIZE * 8).leading_zeros()) as u8); + + c = c.wrapping_sub(key[2 * R::USIZE + 3]); + a = a.wrapping_sub(key[2 * R::USIZE + 2]); + + for i in (1..=R::USIZE).rev() { + let tmp = d; + d = c; + c = b; + b = a; + a = tmp; + let u = d + .wrapping_mul(d.wrapping_mul(W::from(2)).wrapping_add(W::from(1))) + .rotate_left(log_w); + let t = b + .wrapping_mul(b.wrapping_mul(W::from(2)).wrapping_add(W::from(1))) + .rotate_left(log_w); + c = c.wrapping_sub(key[2 * i + 1]).rotate_right(t).bitxor(u); + a = a.wrapping_sub(key[2 * i]).rotate_right(u).bitxor(t); + } + + d = d.wrapping_sub(key[1]); + b = b.wrapping_sub(key[0]); + + Self::block_from_words(a, b, c, d, block.get_out()) + } + + fn words_from_block(block: &Block) -> (W, W, W, W) { + // Block size is 4 * word::BYTES so the unwrap is safe + let a = W::from_le_bytes(block[..W::Bytes::USIZE].try_into().unwrap()); + let b = W::from_le_bytes( + block[W::Bytes::USIZE..W::Bytes::USIZE * 2] + .try_into() + .unwrap(), + ); + let c = W::from_le_bytes( + block[W::Bytes::USIZE * 2..W::Bytes::USIZE * 3] + .try_into() + .unwrap(), + ); + let d = W::from_le_bytes( + block[W::Bytes::USIZE * 3..W::Bytes::USIZE * 4] + .try_into() + .unwrap(), + ); + + (a, b, c, d) + } + + fn block_from_words(a: W, b: W, c: W, d: W, out_block: &mut Block) { + let (left, right) = out_block.split_at_mut(W::Bytes::USIZE * 2); + let (l_l, l_h) = left.split_at_mut(W::Bytes::USIZE); + let (r_l, r_h) = right.split_at_mut(W::Bytes::USIZE); + + l_l.copy_from_slice(&a.to_le_bytes()); + l_h.copy_from_slice(&b.to_le_bytes()); + r_l.copy_from_slice(&c.to_le_bytes()); + r_h.copy_from_slice(&d.to_le_bytes()); + } +} diff --git a/rc6/src/core/mod.rs b/rc6/src/core/mod.rs new file mode 100644 index 00000000..80ed305c --- /dev/null +++ b/rc6/src/core/mod.rs @@ -0,0 +1,8 @@ +//! Implementation according to the [RC6 paper] +//! [RC6 paper]: https://www.grc.com/r&d/rc6.pdf + +mod backend; +mod primitives; + +pub use backend::RC6; +pub use primitives::*; diff --git a/rc6/src/core/primitives.rs b/rc6/src/core/primitives.rs new file mode 100644 index 00000000..99044949 --- /dev/null +++ b/rc6/src/core/primitives.rs @@ -0,0 +1,246 @@ +use cipher::{ + array::{Array, ArraySize}, + typenum::{Diff, Prod, Quot, Sum, U1, U2, U4, U8}, + zeroize::DefaultIsZeroes, +}; +use core::ops::{Add, BitXor}; + +pub type BlockSize = Prod<::Bytes, U4>; +pub type Block = Array>; + +pub type Key = Array; + +pub type ExpandedKeyTable = Array>; +pub type ExpandedKeyTableSize = Prod, U2>; + +pub type KeyAsWords = Array>; +pub type KeyAsWordsSize = Quot::Bytes>, U1>, ::Bytes>; + +pub trait Word: Default + Copy + From + Add + DefaultIsZeroes { + type Bytes: ArraySize; + + const ZERO: Self; + const THREE: Self; + const EIGHT: Self; + + const P: Self; + const Q: Self; + + fn wrapping_add(self, rhs: Self) -> Self; + fn wrapping_sub(self, rhs: Self) -> Self; + fn wrapping_mul(self, rhs: Self) -> Self; + + fn rotate_left(self, n: Self) -> Self; + fn rotate_right(self, n: Self) -> Self; + + fn from_le_bytes(bytes: &Array) -> Self; + fn to_le_bytes(self) -> Array; + + fn bitxor(self, other: Self) -> Self; +} + +impl Word for u8 { + type Bytes = U1; + + const ZERO: Self = 0; + const THREE: Self = 3; + const EIGHT: Self = 8; + + const P: Self = 0xb7; + const Q: Self = 0x9f; + + #[inline(always)] + fn wrapping_add(self, rhs: Self) -> Self { + u8::wrapping_add(self, rhs) + } + + #[inline(always)] + fn wrapping_sub(self, rhs: Self) -> Self { + u8::wrapping_sub(self, rhs) + } + + #[inline(always)] + fn wrapping_mul(self, rhs: Self) -> Self { + u8::wrapping_mul(self, rhs) + } + + #[inline(always)] + fn rotate_left(self, n: Self) -> Self { + u8::rotate_left(self, n as u32) + } + + #[inline(always)] + fn rotate_right(self, n: Self) -> Self { + u8::rotate_right(self, n as u32) + } + + #[inline(always)] + fn from_le_bytes(bytes: &Array) -> Self { + u8::from_le_bytes(bytes.as_slice().try_into().unwrap()) + } + + #[inline(always)] + fn to_le_bytes(self) -> Array { + u8::to_le_bytes(self).into() + } + + #[inline(always)] + fn bitxor(self, other: Self) -> Self { + ::bitxor(self, other) + } +} + +impl Word for u16 { + type Bytes = U2; + + const ZERO: Self = 0; + const THREE: Self = 3; + const EIGHT: Self = 8; + + const P: Self = 0xb7e1; + const Q: Self = 0x9e37; + + #[inline(always)] + fn wrapping_add(self, rhs: Self) -> Self { + u16::wrapping_add(self, rhs) + } + + #[inline(always)] + fn wrapping_sub(self, rhs: Self) -> Self { + u16::wrapping_sub(self, rhs) + } + + #[inline(always)] + fn wrapping_mul(self, rhs: Self) -> Self { + u16::wrapping_mul(self, rhs) + } + + #[inline(always)] + fn rotate_left(self, n: Self) -> Self { + u16::rotate_left(self, n as u32) + } + + #[inline(always)] + fn rotate_right(self, n: Self) -> Self { + u16::rotate_right(self, n as u32) + } + + #[inline(always)] + fn from_le_bytes(bytes: &Array) -> Self { + u16::from_le_bytes(bytes.as_slice().try_into().unwrap()) + } + + #[inline(always)] + fn to_le_bytes(self) -> Array { + u16::to_le_bytes(self).into() + } + + #[inline(always)] + fn bitxor(self, other: Self) -> Self { + ::bitxor(self, other) + } +} + +impl Word for u32 { + type Bytes = U4; + + const ZERO: Self = 0; + const THREE: Self = 3; + const EIGHT: Self = 8; + + const P: Self = 0xb7e15163; + const Q: Self = 0x9e3779b9; + + #[inline(always)] + fn wrapping_add(self, rhs: Self) -> Self { + u32::wrapping_add(self, rhs) + } + + #[inline(always)] + fn wrapping_sub(self, rhs: Self) -> Self { + u32::wrapping_sub(self, rhs) + } + + #[inline(always)] + fn wrapping_mul(self, rhs: Self) -> Self { + u32::wrapping_mul(self, rhs) + } + + #[inline(always)] + fn rotate_left(self, n: Self) -> Self { + u32::rotate_left(self, n) + } + + #[inline(always)] + fn rotate_right(self, n: Self) -> Self { + u32::rotate_right(self, n) + } + + #[inline(always)] + fn from_le_bytes(bytes: &Array) -> Self { + u32::from_le_bytes(bytes.as_slice().try_into().unwrap()) + } + + #[inline(always)] + fn to_le_bytes(self) -> Array { + u32::to_le_bytes(self).into() + } + + #[inline(always)] + fn bitxor(self, other: Self) -> Self { + ::bitxor(self, other) + } +} + +impl Word for u64 { + type Bytes = U8; + + const ZERO: Self = 0; + const THREE: Self = 3; + const EIGHT: Self = 8; + + const P: Self = 0xb7e151628aed2a6b; + const Q: Self = 0x9e3779b97f4a7c15; + + #[inline(always)] + fn wrapping_add(self, rhs: Self) -> Self { + u64::wrapping_add(self, rhs) + } + + #[inline(always)] + fn wrapping_sub(self, rhs: Self) -> Self { + u64::wrapping_sub(self, rhs) + } + + #[inline(always)] + fn wrapping_mul(self, rhs: Self) -> Self { + u64::wrapping_mul(self, rhs) + } + + #[inline(always)] + fn rotate_left(self, n: Self) -> Self { + let size = Self::BITS; + u64::rotate_left(self, (n % size as u64) as u32) + } + + #[inline(always)] + fn rotate_right(self, n: Self) -> Self { + let size = Self::BITS; + u64::rotate_right(self, (n % size as u64) as u32) + } + + #[inline(always)] + fn from_le_bytes(bytes: &Array) -> Self { + u64::from_le_bytes(bytes.as_slice().try_into().unwrap()) + } + + #[inline(always)] + fn to_le_bytes(self) -> Array { + u64::to_le_bytes(self).into() + } + + #[inline(always)] + fn bitxor(self, other: Self) -> Self { + ::bitxor(self, other) + } +} diff --git a/rc6/src/lib.rs b/rc6/src/lib.rs new file mode 100644 index 00000000..14f2f735 --- /dev/null +++ b/rc6/src/lib.rs @@ -0,0 +1,7 @@ +#![no_std] + +mod block_cipher; +mod core; + +pub use crate::core::RC6; +pub use block_cipher::*; diff --git a/rc6/tests/mod.rs b/rc6/tests/mod.rs new file mode 100644 index 00000000..af61f44a --- /dev/null +++ b/rc6/tests/mod.rs @@ -0,0 +1,97 @@ +/// generated using the code in: https://www.ietf.org/archive/id/draft-krovetz-rc6-rc5-vectors-00.txt +#[cfg(test)] +#[allow(deprecated)] // uses `clone_from_slice` +mod tests { + use cipher::consts::*; + use cipher::{array::Array, BlockCipherDecrypt, BlockCipherEncrypt, KeyInit}; + use rc6::RC6; + + #[test] + fn enc_dec_8_12_4() { + let key = [0x00, 0x01, 0x02, 0x03]; + + let pt = [0x00, 0x01, 0x02, 0x03]; + let ct = [0xAE, 0xFC, 0x46, 0x12]; + + let rc6 = as KeyInit>::new_from_slice(&key).unwrap(); + + let mut block = Array::clone_from_slice(&pt); + rc6.encrypt_block(&mut block); + + assert_eq!(ct, block[..]); + + rc6.decrypt_block(&mut block); + assert_eq!(pt, block[..]); + } + + #[test] + fn enc_dec_16_16_8() { + let key = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]; + + let pt = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]; + let ct = [0x2F, 0xF0, 0xB6, 0x8E, 0xAE, 0xFF, 0xAD, 0x5B]; + + let rc6 = as KeyInit>::new_from_slice(&key).unwrap(); + let mut block = Array::clone_from_slice(&pt); + rc6.encrypt_block(&mut block); + + assert_eq!(ct, block[..]); + + rc6.decrypt_block(&mut block); + assert_eq!(pt, block[..]); + } + + #[test] + fn enc_dec_32_20_16() { + let key = [ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0x0E, 0x0F, + ]; + + let pt = [ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0x0E, 0x0F, + ]; + let ct = [ + 0x3A, 0x96, 0xF9, 0xC7, 0xF6, 0x75, 0x5C, 0xFE, 0x46, 0xF0, 0x0E, 0x3D, 0xCD, 0x5D, + 0x2A, 0x3C, + ]; + + let rc6 = as KeyInit>::new_from_slice(&key).unwrap(); + let mut block = Array::clone_from_slice(&pt); + rc6.encrypt_block(&mut block); + + assert_eq!(ct, block[..]); + + rc6.decrypt_block(&mut block); + assert_eq!(pt, block[..]); + } + + #[test] + fn enc_dec_64_24_24() { + 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, + ]; + + let pt = [ + 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, + ]; + let ct = [ + 0xC0, 0x02, 0xDE, 0x05, 0x0B, 0xD5, 0x5E, 0x5D, 0x36, 0x86, 0x4A, 0xB9, 0x85, 0x33, + 0x38, 0xE6, 0xDC, 0x4A, 0x13, 0x26, 0xC6, 0xBD, 0xAA, 0xEB, 0x1B, 0xC9, 0xE4, 0xFD, + 0x67, 0x88, 0x66, 0x17, + ]; + + let rc6 = as KeyInit>::new_from_slice(&key).unwrap(); + let mut block = Array::clone_from_slice(&pt); + rc6.encrypt_block(&mut block); + + assert_eq!(ct, block[..]); + + rc6.decrypt_block(&mut block); + assert_eq!(pt, block[..]); + } +}