diff --git a/.github/workflows/xtea.yml b/.github/workflows/xtea.yml new file mode 100644 index 00000000..a6875806 --- /dev/null +++ b/.github/workflows/xtea.yml @@ -0,0 +1,60 @@ +name: xtea + +on: + pull_request: + paths: + - "xtea/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: xtea + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.65.0 # MSRV + - 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 # MSRV + - 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 a3a26479..0cdf9cc3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -236,6 +236,13 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "xtea" +version = "0.1.0-pre" +dependencies = [ + "cipher", +] + [[package]] name = "zeroize" version = "1.7.0" diff --git a/Cargo.toml b/Cargo.toml index d6de64cd..cbf5fa31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ members = [ "cast5", "cast6", "des", + "gift", "idea", "kuznyechik", "magma", @@ -19,7 +20,7 @@ members = [ "speck", "twofish", "threefish", - "gift", + "xtea", ] [profile.dev] diff --git a/README.md b/README.md index bc6d0044..27980166 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ It's generally recommended not to use other cipher implementations in this repos | [Speck] | [`speck-cipher`] | [![crates.io](https://img.shields.io/crates/v/speck-cipher.svg)](https://crates.io/crates/speck-cipher) | [![Documentation](https://docs.rs/speck-cipher/badge.svg)](https://docs.rs/speck-cipher) | ![MSRV 1.65][msrv-1.65] | | [Threefish] | [`threefish`] | [![crates.io](https://img.shields.io/crates/v/threefish.svg)](https://crates.io/crates/threefish) | [![Documentation](https://docs.rs/threefish/badge.svg)](https://docs.rs/threefish) | ![MSRV 1.65][msrv-1.65] | | [Twofish] | [`twofish`] | [![crates.io](https://img.shields.io/crates/v/twofish.svg)](https://crates.io/crates/twofish) | [![Documentation](https://docs.rs/twofish/badge.svg)](https://docs.rs/twofish) | ![MSRV 1.65][msrv-1.65] | +| [XTEA] | [`xtea`] | [![crates.io](https://img.shields.io/crates/v/xtea.svg)](https://crates.io/crates/xtea) | [![Documentation](https://docs.rs/xtea/badge.svg)](https://docs.rs/xtea) | ![MSRV 1.65][msrv-1.65] | ### Minimum Supported Rust Version (MSRV) Policy @@ -105,6 +106,7 @@ dual licensed as above, without any additional terms or conditions. [`speck-cipher`]: ./speck [`threefish`]: ./threefish [`twofish`]: ./twofish +[`xtea`]: ./xtea [//]: # (links) @@ -131,3 +133,4 @@ dual licensed as above, without any additional terms or conditions. [Speck]: https://en.wikipedia.org/wiki/Speck_(cipher) [Threefish]: https://en.wikipedia.org/wiki/Threefish [Twofish]: https://en.wikipedia.org/wiki/Twofish +[XTEA]: https://en.wikipedia.org/wiki/XTEA diff --git a/xtea/CHANGELOG.md b/xtea/CHANGELOG.md new file mode 100644 index 00000000..f14e3560 --- /dev/null +++ b/xtea/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## 0.1.0 (2024-05-11) +- Initial release diff --git a/xtea/Cargo.toml b/xtea/Cargo.toml new file mode 100644 index 00000000..81587af5 --- /dev/null +++ b/xtea/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "xtea" +version = "0.1.0-pre" +description = "XTEA block cipher" +authors = ["RustCrypto Developers"] +license = "MIT OR Apache-2.0" +edition = "2021" +rust-version = "1.65" +readme = "README.md" +documentation = "https://docs.rs/xtea" +repository = "https://github.com/RustCrypto/block-ciphers" +keywords = ["crypto", "xtea", "block-cipher"] +categories = ["cryptography", "no-std"] + +[dependencies] +cipher = "=0.5.0-pre.4" + +[dev-dependencies] +cipher = { version = "=0.5.0-pre.4", features = ["dev"] } + +[features] +zeroize = ["cipher/zeroize"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/xtea/LICENSE-APACHE b/xtea/LICENSE-APACHE new file mode 100644 index 00000000..0de24dde --- /dev/null +++ b/xtea/LICENSE-APACHE @@ -0,0 +1,13 @@ +Copyright 2024 Kevin Ludwig + +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/xtea/LICENSE-MIT b/xtea/LICENSE-MIT new file mode 100644 index 00000000..95e7d3c6 --- /dev/null +++ b/xtea/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2024 Kevin Ludwig + +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/xtea/README.md b/xtea/README.md new file mode 100644 index 00000000..54839dfc --- /dev/null +++ b/xtea/README.md @@ -0,0 +1,71 @@ +# RustCrypto: XTEA Cipher + +[![crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +![Apache2/MIT licensed][license-image] +![Rust Version][rustc-image] +[![Project Chat][chat-image]][chat-link] +[![Build Status][build-image]][build-link] +[![HAZMAT][hazmat-image]][hazmat-link] + +Pure Rust implementation of the [XTEA block cipher][1]. + +[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.65** 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/xtea.svg +[crate-link]: https://crates.io/crates/xtea +[docs-image]: https://docs.rs/xtea/badge.svg +[docs-link]: https://docs.rs/xtea/ +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.65+-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/workflows/xtea/badge.svg?branch=master&event=push +[build-link]: https://github.com/RustCrypto/block-ciphers/actions?query=workflow%3Axtea + +[//]: # (general links) + +[1]: https://en.wikipedia.org/wiki/XTEA diff --git a/xtea/benches/mod.rs b/xtea/benches/mod.rs new file mode 100644 index 00000000..1105734d --- /dev/null +++ b/xtea/benches/mod.rs @@ -0,0 +1,8 @@ +#![feature(test)] +extern crate test; + +use cipher::{block_decryptor_bench, block_encryptor_bench}; +use xtea::Xtea; + +block_encryptor_bench!(Key: Xtea, xtea_encrypt_block, xtea_encrypt_blocks); +block_decryptor_bench!(Key: Xtea, xtea_decrypt_block, xtea_decrypt_blocks); diff --git a/xtea/src/consts.rs b/xtea/src/consts.rs new file mode 100644 index 00000000..d5bba793 --- /dev/null +++ b/xtea/src/consts.rs @@ -0,0 +1,2 @@ +pub const DELTA: u32 = 0x9e3779b9; +pub const ROUNDS: usize = 32; diff --git a/xtea/src/lib.rs b/xtea/src/lib.rs new file mode 100644 index 00000000..1e83dc39 --- /dev/null +++ b/xtea/src/lib.rs @@ -0,0 +1,156 @@ +//! Pure Rust implementation of the [Extended Tiny Encryption Algorithm][XTEA]. +//! +//! # ⚠️ Security Warning: Hazmat! +//! +//! This crate implements only the low-level block cipher function, and is intended +//! for use for implementing higher-level constructions *only*. It is NOT +//! intended for direct use in applications. +//! +//! USE AT YOUR OWN RISK! +//! +//! [XTEA]: https://en.wikipedia.org/wiki/XTEA + +#![no_std] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg" +)] +#![deny(unsafe_code)] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![warn(missing_docs, rust_2018_idioms)] + +pub use cipher; + +use cipher::{ + consts::{U16, U8}, + AlgorithmName, BlockCipher, InvalidLength, Key, KeyInit, KeySizeUser, +}; +use core::fmt; + +#[cfg(feature = "zeroize")] +use cipher::zeroize::{Zeroize, ZeroizeOnDrop}; + +mod consts; +use consts::{DELTA, ROUNDS}; + +/// XTEA block cipher. +pub struct Xtea { + k: [u32; 4], +} + +impl BlockCipher for Xtea {} + +impl KeySizeUser for Xtea { + type KeySize = U16; +} + +impl KeyInit for Xtea { + fn new(key: &Key) -> Self { + Self::new_from_slice(key).unwrap() + } + + fn new_from_slice(key: &[u8]) -> Result { + if key.len() != 16 { + return Err(InvalidLength); + } + let key = [ + u32::from_le_bytes(key[0..4].try_into().unwrap()), + u32::from_le_bytes(key[4..8].try_into().unwrap()), + u32::from_le_bytes(key[8..12].try_into().unwrap()), + u32::from_le_bytes(key[12..16].try_into().unwrap()), + ]; + Ok(Xtea { k: key }) + } +} + +impl fmt::Debug for Xtea { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("XTEA { ... }") + } +} + +impl AlgorithmName for Xtea { + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("XTEA") + } +} + +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl Drop for Xtea { + fn drop(&mut self) { + self.k.zeroize(); + } +} + +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl ZeroizeOnDrop for Xtea {} + +cipher::impl_simple_block_encdec!( + Xtea, U8, cipher, block, + encrypt: { + let v = block.get_in(); + let mut v0 = u32::from_le_bytes(v[0..4].try_into().unwrap()); + let mut v1 = u32::from_le_bytes(v[4..8].try_into().unwrap()); + let mut sum = 0u32; + + // Use 4 loops as otherwise unrolling will not be performed by default + for _ in 0..8 { + v0 = v0.wrapping_add((((v1 << 4) ^ (v1 >> 5)).wrapping_add(v1)) ^ sum.wrapping_add(cipher.k[(sum & 3) as usize])); + sum = sum.wrapping_add(DELTA); + v1 = v1.wrapping_add((((v0 << 4) ^ (v0 >> 5)).wrapping_add(v0)) ^ sum.wrapping_add(cipher.k[((sum >> 11) & 3) as usize])); + } + for _ in 0..8 { + v0 = v0.wrapping_add((((v1 << 4) ^ (v1 >> 5)).wrapping_add(v1)) ^ sum.wrapping_add(cipher.k[(sum & 3) as usize])); + sum = sum.wrapping_add(DELTA); + v1 = v1.wrapping_add((((v0 << 4) ^ (v0 >> 5)).wrapping_add(v0)) ^ sum.wrapping_add(cipher.k[((sum >> 11) & 3) as usize])); + } + for _ in 0..8 { + v0 = v0.wrapping_add((((v1 << 4) ^ (v1 >> 5)).wrapping_add(v1)) ^ sum.wrapping_add(cipher.k[(sum & 3) as usize])); + sum = sum.wrapping_add(DELTA); + v1 = v1.wrapping_add((((v0 << 4) ^ (v0 >> 5)).wrapping_add(v0)) ^ sum.wrapping_add(cipher.k[((sum >> 11) & 3) as usize])); + } + for _ in 0..8 { + v0 = v0.wrapping_add((((v1 << 4) ^ (v1 >> 5)).wrapping_add(v1)) ^ sum.wrapping_add(cipher.k[(sum & 3) as usize])); + sum = sum.wrapping_add(DELTA); + v1 = v1.wrapping_add((((v0 << 4) ^ (v0 >> 5)).wrapping_add(v0)) ^ sum.wrapping_add(cipher.k[((sum >> 11) & 3) as usize])); + } + + let v = block.get_out(); + v[0..4].copy_from_slice(&v0.to_le_bytes()); + v[4..8].copy_from_slice(&v1.to_le_bytes()); + } + decrypt: { + let v = block.get_in(); + let mut v0 = u32::from_le_bytes(v[0..4].try_into().unwrap()); + let mut v1 = u32::from_le_bytes(v[4..8].try_into().unwrap()); + let mut sum = DELTA.wrapping_mul(ROUNDS as u32); + + // Same as encrypt, just in reverse + for _ in 0..8 { + v1 = v1.wrapping_sub((((v0 << 4) ^ (v0 >> 5)).wrapping_add(v0)) ^ sum.wrapping_add(cipher.k[((sum >> 11) & 3) as usize])); + sum = sum.wrapping_sub(DELTA); + v0 = v0.wrapping_sub((((v1 << 4) ^ (v1 >> 5)).wrapping_add(v1)) ^ sum.wrapping_add(cipher.k[(sum & 3) as usize])); + } + for _ in 0..8 { + v1 = v1.wrapping_sub((((v0 << 4) ^ (v0 >> 5)).wrapping_add(v0)) ^ sum.wrapping_add(cipher.k[((sum >> 11) & 3) as usize])); + sum = sum.wrapping_sub(DELTA); + v0 = v0.wrapping_sub((((v1 << 4) ^ (v1 >> 5)).wrapping_add(v1)) ^ sum.wrapping_add(cipher.k[(sum & 3) as usize])); + } + for _ in 0..8 { + v1 = v1.wrapping_sub((((v0 << 4) ^ (v0 >> 5)).wrapping_add(v0)) ^ sum.wrapping_add(cipher.k[((sum >> 11) & 3) as usize])); + sum = sum.wrapping_sub(DELTA); + v0 = v0.wrapping_sub((((v1 << 4) ^ (v1 >> 5)).wrapping_add(v1)) ^ sum.wrapping_add(cipher.k[(sum & 3) as usize])); + } + for _ in 0..8 { + v1 = v1.wrapping_sub((((v0 << 4) ^ (v0 >> 5)).wrapping_add(v0)) ^ sum.wrapping_add(cipher.k[((sum >> 11) & 3) as usize])); + sum = sum.wrapping_sub(DELTA); + v0 = v0.wrapping_sub((((v1 << 4) ^ (v1 >> 5)).wrapping_add(v1)) ^ sum.wrapping_add(cipher.k[(sum & 3) as usize])); + } + + let v = block.get_out(); + v[0..4].copy_from_slice(&v0.to_le_bytes()); + v[4..8].copy_from_slice(&v1.to_le_bytes()); + } +); diff --git a/xtea/tests/mod.rs b/xtea/tests/mod.rs new file mode 100644 index 00000000..0d6747a3 --- /dev/null +++ b/xtea/tests/mod.rs @@ -0,0 +1,18 @@ +use cipher::{array::Array, BlockCipherDecrypt, BlockCipherEncrypt, KeyInit}; +use xtea::Xtea; + +#[test] +fn xtea() { + // https://web.archive.org/web/20231115163347/https://asecuritysite.com/encryption/xtea + let key = b"0123456789012345"; + let plaintext = b"ABCDEFGH"; + let ciphertext = [0xea, 0x0c, 0x3d, 0x7c, 0x1c, 0x22, 0x55, 0x7f]; + let cipher = Xtea::new_from_slice(key).unwrap(); + + let mut block = Array::clone_from_slice(plaintext); + cipher.encrypt_block(&mut block); + assert_eq!(ciphertext, block.as_slice()); + + cipher.decrypt_block(&mut block); + assert_eq!(plaintext, block.as_slice()); +}