From f1d6beccb6c1aeaec0f4d516eaa57c9313a2ca11 Mon Sep 17 00:00:00 2001 From: Alexandr Kitaev Date: Tue, 20 Oct 2026 19:02:59 +0300 Subject: [PATCH] Implement `bash-prg-ae` --- .github/workflows/bash-prg-ae.yml | 84 ++++++++ Cargo.lock | 17 ++ Cargo.toml | 1 + bash-prg-ae/CHANGELOG.md | 8 + bash-prg-ae/Cargo.toml | 38 ++++ bash-prg-ae/LICENSE-APACHE | 201 +++++++++++++++++++ bash-prg-ae/LICENSE-MIT | 25 +++ bash-prg-ae/README.md | 51 +++++ bash-prg-ae/benches/mod.rs | 96 +++++++++ bash-prg-ae/src/lib.rs | 322 ++++++++++++++++++++++++++++++ bash-prg-ae/src/variants.rs | 22 ++ bash-prg-ae/tests/bash_prg_ae.rs | 265 ++++++++++++++++++++++++ bash-prg-ae/tests/common/mod.rs | 96 +++++++++ 13 files changed, 1226 insertions(+) create mode 100644 .github/workflows/bash-prg-ae.yml create mode 100644 bash-prg-ae/CHANGELOG.md create mode 100644 bash-prg-ae/Cargo.toml create mode 100644 bash-prg-ae/LICENSE-APACHE create mode 100644 bash-prg-ae/LICENSE-MIT create mode 100644 bash-prg-ae/README.md create mode 100644 bash-prg-ae/benches/mod.rs create mode 100644 bash-prg-ae/src/lib.rs create mode 100644 bash-prg-ae/src/variants.rs create mode 100644 bash-prg-ae/tests/bash_prg_ae.rs create mode 100644 bash-prg-ae/tests/common/mod.rs diff --git a/.github/workflows/bash-prg-ae.yml b/.github/workflows/bash-prg-ae.yml new file mode 100644 index 00000000..1b022ecc --- /dev/null +++ b/.github/workflows/bash-prg-ae.yml @@ -0,0 +1,84 @@ +name: bash-prg-ae + +on: + pull_request: + paths: + - ".github/workflows/bash-prg-ae.yml" + - "bash-prg-ae/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: bash-prg-ae + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +# Cancels CI jobs when new commits are pushed to a PR branch +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.85.0 # MSRV + - stable + target: + - armv7a-none-eabi + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v7 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.target }} + - run: cargo build --no-default-features --release --target ${{ matrix.target }} + + benches: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2025-07-31 + - run: cargo build --benches + + test: + runs-on: ubuntu-latest + strategy: + matrix: + include: + # 32-bit Linux + - target: i686-unknown-linux-gnu + rust: 1.85.0 # MSRV + deps: sudo apt update && sudo apt install gcc-multilib + - target: i686-unknown-linux-gnu + rust: stable + deps: sudo apt update && sudo apt install gcc-multilib + + # 64-bit Linux + - target: x86_64-unknown-linux-gnu + rust: 1.85.0 # MSRV + - target: x86_64-unknown-linux-gnu + rust: stable + steps: + - uses: actions/checkout@v7 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.target }} + - run: ${{ matrix.deps }} + - run: cargo test --target ${{ matrix.target }} --lib --no-default-features + - run: cargo test --target ${{ matrix.target }} --lib + - run: cargo test --target ${{ matrix.target }} --lib --features zeroize + - run: cargo test --target ${{ matrix.target }} --all-features --lib + - run: cargo test --target ${{ matrix.target }} --all-features --release + - run: cargo test --target ${{ matrix.target }} --all-features --doc diff --git a/Cargo.lock b/Cargo.lock index 2d9fe082..ec73ef1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,6 +100,23 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bash-f" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfed5cd32720841afa8cd58c89a317e9efb0c8083e1b349dae4098f022620353" + +[[package]] +name = "bash-prg-ae" +version = "0.1.0" +dependencies = [ + "aead", + "bash-f", + "ctutils", + "hex-literal", + "zeroize", +] + [[package]] name = "belt-block" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index afd4bfa9..22ce8217 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "aes-gcm-siv", "aes-siv", "ascon-aead128", + "bash-prg-ae", "belt-dwp", "ccm", "chacha20poly1305", diff --git a/bash-prg-ae/CHANGELOG.md b/bash-prg-ae/CHANGELOG.md new file mode 100644 index 00000000..fa000c8b --- /dev/null +++ b/bash-prg-ae/CHANGELOG.md @@ -0,0 +1,8 @@ +# 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 (UNRELEASED) +- Initial release diff --git a/bash-prg-ae/Cargo.toml b/bash-prg-ae/Cargo.toml new file mode 100644 index 00000000..f30a256c --- /dev/null +++ b/bash-prg-ae/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "bash-prg-ae" +version = "0.1.0" +description = "Pure Rust implementation of the bash-prg-ae authenticated encryption algorithm (STB 34.101.77-2020)" +edition = "2024" +license = "Apache-2.0 OR MIT" +readme = "README.md" +documentation = "https://docs.rs/bash-prg-ae" +repository = "https://github.com/RustCrypto/AEADs/tree/master/bash-prg-ae" +keywords = ["aead", "bash-prg-ae"] +categories = ["cryptography", "no-std"] +rust-version = "1.85" + +[dependencies] +aead = { version = "0.6", default-features = false } +bash-f = "0.1" +ctutils = "0.4" + +# optional dependencies +zeroize = { version = "1.8", default-features = false, optional = true } + +[dev-dependencies] +hex-literal = "1" + +[features] +default = ["alloc", "getrandom"] +alloc = ["aead/alloc"] +arrayvec = ["aead/arrayvec"] +bytes = ["aead/bytes"] +getrandom = ["aead/getrandom"] +rand_core = ["aead/rand_core"] +zeroize = ["dep:zeroize"] + +[lints] +workspace = true + +[package.metadata.docs.rs] +all-features = true diff --git a/bash-prg-ae/LICENSE-APACHE b/bash-prg-ae/LICENSE-APACHE new file mode 100644 index 00000000..16460804 --- /dev/null +++ b/bash-prg-ae/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2026 The RustCrypto Project Developers + +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/bash-prg-ae/LICENSE-MIT b/bash-prg-ae/LICENSE-MIT new file mode 100644 index 00000000..4697b3e7 --- /dev/null +++ b/bash-prg-ae/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2026 The RustCrypto Project Developers + +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/bash-prg-ae/README.md b/bash-prg-ae/README.md new file mode 100644 index 00000000..68a699fb --- /dev/null +++ b/bash-prg-ae/README.md @@ -0,0 +1,51 @@ +# RustCrypto: bash-prg-ae + +[![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] + +Pure Rust implementation of the `bash-prg-ae` [AEAD] algorithm +specified in the republic of Belarus standard [STB 34.101.77-2020]. + +## Security Notes + +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! + +## License + +Licensed under either of: + +* [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) +* [MIT license](https://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://buildstats.info/crate/bash-prg-ae +[crate-link]: https://crates.io/crates/bash-prg-ae +[docs-image]: https://docs.rs/bash-prg-ae/badge.svg +[docs-link]: https://docs.rs/bash-prg-ae/ +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.85+-blue.svg +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260038-AEADs +[downloads-image]: https://img.shields.io/crates/d/bash-prg-ae.svg +[build-image]: https://github.com/RustCrypto/AEADs/workflows/bash-prg-ae/badge.svg?branch=master&event=push +[build-link]: https://github.com/RustCrypto/AEADs/actions + +[//]: # (general links) + +[STB 34.101.77-2020]: https://apmi.bsu.by/assets/files/std/bash-spec243.pdf +[AEAD]: https://en.wikipedia.org/wiki/Authenticated_encryption diff --git a/bash-prg-ae/benches/mod.rs b/bash-prg-ae/benches/mod.rs new file mode 100644 index 00000000..e3731894 --- /dev/null +++ b/bash-prg-ae/benches/mod.rs @@ -0,0 +1,96 @@ +//! bash-prg-ae benchmarks. + +#![feature(test)] +extern crate test; + +macro_rules! bench { + ($name:ident, $aead:ty) => { + mod $name { + use bash_prg_ae::{AeadInOut, KeyInit}; + use test::Bencher; + + type Aead = $aead; + + #[bench] + fn encrypt_aad_only_16kb(b: &mut Bencher) { + let c = Aead::new(&Default::default()); + let nonce = Default::default(); + let aad = vec![0; 16 * 1024]; + let mut buf = []; + + b.iter(|| { + let (aad, buf, nonce) = test::black_box((&aad, &mut buf[..], &nonce)); + let res = c.encrypt_inout_detached(nonce, aad, buf.into()).unwrap(); + test::black_box(res); + }); + + b.bytes = 16 * 1024; + } + + #[bench] + fn encrypt_msg_only_16kb(b: &mut Bencher) { + let c = Aead::new(&Default::default()); + let nonce = Default::default(); + let aad = []; + let mut buf = vec![0; 16 * 1024]; + + b.iter(|| { + let (aad, buf, nonce) = test::black_box((&aad, &mut buf[..], &nonce)); + let res = c.encrypt_inout_detached(nonce, aad, buf.into()).unwrap(); + test::black_box(res); + }); + + b.bytes = 16 * 1024; + } + + #[bench] + fn decrypt_aad_only_16kb(b: &mut Bencher) { + let c = Aead::new(&Default::default()); + let nonce = Default::default(); + let aad = vec![0; 16 * 1024]; + let mut buf = []; + let tag = c + .encrypt_inout_detached(&nonce, &aad, (&mut buf[..]).into()) + .unwrap(); + + b.iter(|| { + let (aad, buf, nonce, tag) = + test::black_box((&aad, &mut buf[..], &nonce, &tag)); + let res = c.decrypt_inout_detached(nonce, aad, buf.into(), tag); + let _ = test::black_box(res); + }); + + b.bytes = 16 * 1024; + } + + #[bench] + fn decrypt_msg_only_16kb(b: &mut Bencher) { + let c = Aead::new(&Default::default()); + let nonce = Default::default(); + let aad = []; + let mut ct_buf = vec![0u8; 16 * 1024]; + let tag = c + .encrypt_inout_detached(&nonce, &aad, (&mut ct_buf[..]).into()) + .unwrap(); + + let mut buf = ct_buf.clone(); + b.iter(|| { + let (aad, buf, nonce, tag) = + test::black_box((&aad, &mut buf[..], &nonce, &tag)); + let res = c.decrypt_inout_detached(nonce, aad, buf.into(), tag); + let _ = test::black_box(res); + buf.copy_from_slice(&ct_buf); + }); + + b.bytes = 16 * 1024; + } + } + }; +} + +bench!(bash_prg_ae1281, bash_prg_ae::BashPrgAe1281); +bench!(bash_prg_ae1282, bash_prg_ae::BashPrgAe1282); +bench!(bash_prg_ae1921, bash_prg_ae::BashPrgAe1921); +bench!(bash_prg_ae1922, bash_prg_ae::BashPrgAe1922); +bench!(bash_prg_ae2561, bash_prg_ae::BashPrgAe2561); +bench!(bash_prg_ae2562, bash_prg_ae::BashPrgAe2562); diff --git a/bash-prg-ae/src/lib.rs b/bash-prg-ae/src/lib.rs new file mode 100644 index 00000000..8ce9365e --- /dev/null +++ b/bash-prg-ae/src/lib.rs @@ -0,0 +1,322 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg" +)] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![forbid(unsafe_code)] + +pub use aead::{self, AeadCore, AeadInOut, Error, Key, KeyInit, KeySizeUser}; + +use aead::{TagPosition, array::ArraySize, consts::U16, inout::InOutBuf}; +use bash_f::{STATE_WORDS, bash_f}; +use core::{fmt, marker::PhantomData}; +use ctutils::CtEq; + +mod variants; +pub use variants::*; + +/// Length of the state ๐‘† โˆˆ {0,1}^1536 in octets. +const STATE_BYTES: usize = 8 * STATE_WORDS; + +/// 6-bit data type codes, Table 3 of STB 34.101.77-2020. +const DATA: u8 = 0b000010; +const TEXT: u8 = 0b000011; +const OUT: u8 = 0b000100; + +/// `bash-prg-ae` authenticated encryption (STB 34.101.77-2020, 8.13) with +/// security level โ„“ = `8 * Size` bits and capacity ๐‘‘ = `D`. +/// +/// The key ๐พ and the tag ๐‘‡ are both โ„“ bits long. The announcement ๐ด (nonce) +/// is `NonceSize` octets long, a multiple of 4 not greater than 60. +pub struct BashPrgAe { + key: Key, + _pd: PhantomData<(Size, NonceSize)>, +} + +impl BashPrgAe { + /// Security level โ„“ in bits. + const LEVEL: usize = 8 * Size::USIZE; + + /// 8.13.1: ๐ด โˆˆ {0,1}^32* and |๐ด| โ‰ค 480. + const CHECK_NONCE: () = assert!( + NonceSize::USIZE % 4 == 0 && NonceSize::USIZE <= 60, + "nonce length must be a multiple of 4 octets not greater than 60" + ); +} + +impl KeySizeUser + for BashPrgAe +{ + type KeySize = Size; +} + +impl KeyInit + for BashPrgAe +{ + #[inline] + fn new(key: &Key) -> Self { + let () = Self::CHECK_NONCE; + Self { + key: key.clone(), + _pd: PhantomData, + } + } +} + +impl AeadCore + for BashPrgAe +{ + type NonceSize = NonceSize; + type TagSize = Size; + const TAG_POSITION: TagPosition = TagPosition::Postfix; +} + +// Note 1 of 8.13: if ๐ผ = โŠฅ, step 2.1 may be skipped; if ๐‘‹ = โŠฅ, step 2.2 may +// be simplified to ๐‘Œ โ† โŠฅ. This changes the tag; the test vectors of the +// STB 34.101.77 test methodology (BASH.PRG.AE*.2, *.3) require both. +impl AeadInOut + for BashPrgAe +{ + fn encrypt_inout_detached( + &self, + nonce: &aead::Nonce, + associated_data: &[u8], + buffer: InOutBuf<'_, '_, u8>, + ) -> Result, Error> { + // 1. ฮฑ.start[โ„“, ๐‘‘](๐ด, ๐พ) + let mut a = Automaton::start(Self::LEVEL, D, nonce, &self.key); + + // 2.1. ฮฑ.absorb(๐ผ) + if !associated_data.is_empty() { + a.absorb(associated_data); + } + + // 2.2. ๐‘Œ โ† ฮฑ.encrypt(๐‘‹) + if !buffer.is_empty() { + a.encrypt(buffer.into_out_with_copied_in()); + } + + // 2.3. ๐‘‡ โ† ฮฑ.squeeze(โ„“) + let mut tag = aead::Tag::::default(); + a.squeeze(&mut tag); + + // 2.4. Return (๐‘Œ, ๐‘‡) + Ok(tag) + } + + fn decrypt_inout_detached( + &self, + nonce: &aead::Nonce, + associated_data: &[u8], + buffer: InOutBuf<'_, '_, u8>, + tag: &aead::Tag, + ) -> Result<(), Error> { + // 1. ฮฑ.start[โ„“, ๐‘‘](๐ด, ๐พ) + let mut a = Automaton::start(Self::LEVEL, D, nonce, &self.key); + + // 2.1. ฮฑ.absorb(๐ผ) + if !associated_data.is_empty() { + a.absorb(associated_data); + } + let mut saved = a.clone(); + + // 2.2. ๐‘‹ โ† ฮฑ.decrypt(๐‘Œ) + let buf = buffer.into_out_with_copied_in(); + if !buf.is_empty() { + a.decrypt(buf); + } + + // 2.3. If ๐‘‡ โ‰  ฮฑ.squeeze(โ„“), return โŠฅ + let mut tag_exact = aead::Tag::::default(); + a.squeeze(&mut tag_exact); + if tag_exact.ct_eq(tag).into() { + // 2.4. Return ๐‘‹ + Ok(()) + } else { + // 8.13.2: ๐‘‹ is a variable until the tag is checked and must be + // cleared if the tag is wrong. Restore ๐‘Œ in its place. + saved.encrypt(buf); + Err(Error) + } + } +} + +impl Clone + for BashPrgAe +{ + fn clone(&self) -> Self { + Self::new(&self.key) + } +} + +impl fmt::Debug + for BashPrgAe +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "BashPrgAe{}{D} {{ ... }}", Self::LEVEL) + } +} + +impl Drop for BashPrgAe { + fn drop(&mut self) { + #[cfg(feature = "zeroize")] + zeroize::Zeroize::zeroize(self.key.as_mut_slice()); + } +} + +#[cfg(feature = "zeroize")] +impl zeroize::ZeroizeOnDrop + for BashPrgAe +{ +} + +/// Programmable automaton ฮฑ (STB 34.101.77-2020, 8.1โ€“8.9) in key mode. +/// +/// Lengths are in octets, while the standard counts them in bits. +#[derive(Clone)] +struct Automaton { + /// State ๐‘†: buffer ๐‘†[..๐‘Ÿ) followed by memory ๐‘†[๐‘Ÿ..). + s: [u8; STATE_BYTES], + /// Current offset in the buffer, ๐‘๐‘œ๐‘  โˆˆ {0, 1, โ€ฆ, ๐‘Ÿ โˆ’ 1}. + pos: usize, + /// Buffer length ๐‘Ÿ. + rate: usize, +} + +impl Automaton { + /// Command `start[โ„“, d](A, K)`, 8.3. + /// + /// Announcement and key lengths are fixed by the variant types and always + /// satisfy ๐ด, ๐พ โˆˆ {0,1}^32*, |๐ด|, |๐พ| โ‰ค 480 and |๐พ| โ‰ฅ โ„“. + fn start(l: usize, d: usize, ann: &[u8], key: &[u8]) -> Self { + // 1. If ๐พ โ‰  โŠฅ, then ๐‘Ÿ โ† 1536 โˆ’ โ„“ โˆ’ ๐‘‘โ„“/2 + let rate = STATE_BYTES - (l + d * l / 2) / 8; + + // 3. ๐‘๐‘œ๐‘  โ† 8 + |๐ด| + |๐พ| + let pos = 1 + ann.len() + key.len(); + + // 4. ๐‘†[..๐‘๐‘œ๐‘ ) โ† โŸจ|๐ด|/2 + |๐พ|/32โŸฉ_8 โ€– ๐ด โ€– ๐พ + // 5. ๐‘†[๐‘๐‘œ๐‘ ..1472) โ† 0^{1472โˆ’๐‘๐‘œ๐‘ } + let mut s = [0u8; STATE_BYTES]; + s[0] = u8::try_from(ann.len() * 4 + key.len() / 4).expect("lengths are at most 60"); + s[1..][..ann.len()].copy_from_slice(ann); + s[1 + ann.len()..][..key.len()].copy_from_slice(key); + + // 6. ๐‘†[1472..) โ† โŸจโ„“/4 + ๐‘‘โŸฉ_64 + s[STATE_BYTES - 8] = u8::try_from(l / 4 + d).expect("l <= 256, d <= 2"); + + // 7. Save (โ„“, ๐‘‘, ๐‘Ÿ, ๐‘๐‘œ๐‘ ) along with ๐‘† + Self { s, pos, rate } + } + + /// Command `commit(t)`, 8.4. + fn commit(&mut self, t: u8) { + // 1. ๐‘†[๐‘๐‘œ๐‘ ..๐‘๐‘œ๐‘ +8) โ† ๐‘†[๐‘๐‘œ๐‘ ..๐‘๐‘œ๐‘ +8) โŠ• (๐‘ก โ€– 01) + self.s[self.pos] ^= (t << 2) | 0b01; + + // 2. ๐‘†[๐‘Ÿ] โ† ๐‘†[๐‘Ÿ] โŠ• 1 (bit ๐‘†[๐‘Ÿ] is the high bit of octet ๐‘Ÿ/8) + self.s[self.rate] ^= 0x80; + + // 3. ๐‘† โ† bash-f(๐‘†) + self.permute(); + + // 4. ๐‘๐‘œ๐‘  โ† 0 + self.pos = 0; + } + + /// Loop shared by `absorb`, `squeeze`, `encrypt` and `decrypt`: + /// + /// 1. (๐‘‹โ‚, ๐‘‹โ‚‚, โ€ฆ, ๐‘‹โ‚™) = Split(๐‘‹, ๐‘Ÿ) + /// 2. For ๐‘– = 1, 2, โ€ฆ, ๐‘›: + /// 1. ๐‘๐‘œ๐‘  โ† |๐‘‹แตข| + /// 2. process ๐‘†[..๐‘๐‘œ๐‘ ) and ๐‘‹แตข with `f` + /// 3. if ๐‘๐‘œ๐‘  = ๐‘Ÿ, then ๐‘† โ† bash-f(๐‘†) and ๐‘๐‘œ๐‘  โ† 0 + /// + /// The data may be split across calls, so the blocks are taken from ๐‘๐‘œ๐‘  + /// on. `f` gets the state chunk and the offset of the block in the data. + fn duplex(&mut self, len: usize, mut f: impl FnMut(&mut [u8], usize)) { + let mut off = 0; + while off < len { + let n = (self.rate - self.pos).min(len - off); + f(&mut self.s[self.pos..][..n], off); + off += n; + self.pos += n; + if self.pos == self.rate { + self.permute(); + self.pos = 0; + } + } + } + + /// Command `absorb(X)`, 8.6. + fn absorb(&mut self, x: &[u8]) { + // 1. commit(DATA) + self.commit(DATA); + + // 2-3. ๐‘†[..๐‘๐‘œ๐‘ ) โ† ๐‘†[..๐‘๐‘œ๐‘ ) โŠ• ๐‘‹แตข + self.duplex(x.len(), |s, off| { + s.iter_mut().zip(&x[off..]).for_each(|(s, x)| *s ^= x); + }); + } + + /// Command `squeeze(n)`, 8.7, with ๐‘› = 8 * `y.len()`. + fn squeeze(&mut self, y: &mut [u8]) { + // 1. commit(OUT) + self.commit(OUT); + + // 2-5. ๐‘Œ โ† ๐‘Œ โ€– ๐‘†[..๐‘๐‘œ๐‘ ) + self.duplex(y.len(), |s, off| y[off..][..s.len()].copy_from_slice(s)); + } + + /// Command `encrypt(X)`, 8.8, in place. + fn encrypt(&mut self, buf: &mut [u8]) { + // 1. commit(TEXT) + self.commit(TEXT); + + // 2-4. ๐‘†[..๐‘๐‘œ๐‘ ) โ† ๐‘†[..๐‘๐‘œ๐‘ ) โŠ• ๐‘‹แตข, ๐‘Œ โ† ๐‘Œ โ€– ๐‘†[..๐‘๐‘œ๐‘ ) + self.duplex(buf.len(), |s, off| { + for (s, b) in s.iter_mut().zip(&mut buf[off..]) { + *s ^= *b; + *b = *s; + } + }); + } + + /// Command `decrypt(Y)`, 8.9, in place. + fn decrypt(&mut self, buf: &mut [u8]) { + // 1. commit(TEXT) + self.commit(TEXT); + + // 2-4. ๐‘‹ โ† ๐‘‹ โ€– (๐‘†[..๐‘๐‘œ๐‘ ) โŠ• ๐‘Œแตข), ๐‘†[..๐‘๐‘œ๐‘ ) โ† ๐‘Œแตข + self.duplex(buf.len(), |s, off| { + for (s, b) in s.iter_mut().zip(&mut buf[off..]) { + let y = *b; + *b ^= *s; + *s = y; + } + }); + } + + /// ๐‘† โ† bash-f(๐‘†), 6.2. Octets of ๐‘† map to 64-bit words little-endian. + fn permute(&mut self) { + let mut words = [0u64; STATE_WORDS]; + for (w, chunk) in words.iter_mut().zip(self.s.chunks_exact(8)) { + *w = u64::from_le_bytes(chunk.try_into().expect("8-byte chunk")); + } + bash_f(&mut words); + for (chunk, w) in self.s.chunks_exact_mut(8).zip(&words) { + chunk.copy_from_slice(&w.to_le_bytes()); + } + #[cfg(feature = "zeroize")] + zeroize::Zeroize::zeroize(&mut words); + } +} + +impl Drop for Automaton { + fn drop(&mut self) { + #[cfg(feature = "zeroize")] + zeroize::Zeroize::zeroize(&mut self.s); + } +} diff --git a/bash-prg-ae/src/variants.rs b/bash-prg-ae/src/variants.rs new file mode 100644 index 00000000..56fc5589 --- /dev/null +++ b/bash-prg-ae/src/variants.rs @@ -0,0 +1,22 @@ +//! Type aliases for bash-prg-ae variants + +use super::BashPrgAe; +use aead::consts::*; + +/// `bash-prg-ae` with โ„“ = 128 and d = 1 +pub type BashPrgAe1281 = BashPrgAe; + +/// `bash-prg-ae` with โ„“ = 128 and d = 2 +pub type BashPrgAe1282 = BashPrgAe; + +/// `bash-prg-ae` with โ„“ = 192 and d = 1 +pub type BashPrgAe1921 = BashPrgAe; + +/// `bash-prg-ae` with โ„“ = 192 and d = 2 +pub type BashPrgAe1922 = BashPrgAe; + +/// `bash-prg-ae` with โ„“ = 256 and d = 1 +pub type BashPrgAe2561 = BashPrgAe; + +/// `bash-prg-ae` with โ„“ = 256 and d = 2 +pub type BashPrgAe2562 = BashPrgAe; diff --git a/bash-prg-ae/tests/bash_prg_ae.rs b/bash-prg-ae/tests/bash_prg_ae.rs new file mode 100644 index 00000000..e87942ab --- /dev/null +++ b/bash-prg-ae/tests/bash_prg_ae.rs @@ -0,0 +1,265 @@ +//! bash-prg-ae test vectors from tests BASH.PRG.AE*.1-4 of the STB 34.101.77 +//! test methodology. +//! +//! BASH.PRG.AE256.4 is also Table A.6 of STB 34.101.77-2020. + +#![cfg(feature = "alloc")] + +#[macro_use] +mod common; + +use self::common::TestVector; +use bash_prg_ae::aead::consts::*; +use hex_literal::hex; + +/// BASH.PRG.AE128.1 +mod ae128_1 { + use super::*; + use bash_prg_ae::BashPrgAe1282; + + const TEST_VECTORS: &[TestVector<[u8; 16], [u8; 0], [u8; 16]>] = &[TestVector { + key: &hex!("2033394D6C320D0965201A166E62001D"), + nonce: &[], + aad: &hex!("5468697274792074776F2062797465206D657373616765202874657374203129"), + plaintext: &hex!( + "466966747920666F75722062797465206F7220666F75722068756E6472656420" + "7468697274792074776F20626974206D657373616765" + ), + ciphertext: &hex!( + "3DC15B5B5C44352D9DD48EB6AF1C7F25AC47F495E2F95DC98A395C88FC7BC0FE" + "8D2274F248B34183F7A19FD3EDF141B8130D3EDF3F13" + ), + tag: &hex!("2091C633C522E51B1EFF3D704BDFA289"), + }]; + + tests!(BashPrgAe1282, TEST_VECTORS); +} + +/// BASH.PRG.AE128.2 +mod ae128_2 { + use super::*; + use bash_prg_ae::BashPrgAe1282; + + const TEST_VECTORS: &[TestVector<[u8; 16], [u8; 8], [u8; 16]>] = &[TestVector { + key: &hex!("5BE3D61217B96181FE6786AD716B890B"), + nonce: &hex!("9DEADEC2621747A6"), + aad: &[], + plaintext: &hex!("5468697274792074776F2062797465206D657373616765202874657374203129"), + ciphertext: &hex!("85E004B72641B56795B8DA31BEE91AD7F82BB6DC05FE9216A4A71312885FFFB2"), + tag: &hex!("2B4C6067265F837A678EE9803F578817"), + }]; + + tests!(BashPrgAe1282, TEST_VECTORS); +} + +/// BASH.PRG.AE128.3 +mod ae128_3 { + use super::*; + use bash_prg_ae::BashPrgAe1281; + + const TEST_VECTORS: &[TestVector<[u8; 16], [u8; 0], [u8; 16]>] = &[TestVector { + key: &hex!("2033394D6C320D0965201A166E62001D"), + nonce: &[], + aad: &hex!("5468697274792074776F2062797465206D657373616765202874657374203129"), + plaintext: &[], + ciphertext: &[], + tag: &hex!("3501E8ADA1D710A5986175B5916B0B77"), + }]; + + tests!(BashPrgAe1281, TEST_VECTORS); +} + +/// BASH.PRG.AE128.4 +mod ae128_4 { + use super::*; + use bash_prg_ae::BashPrgAe1281; + + const TEST_VECTORS: &[TestVector<[u8; 16], [u8; 8], [u8; 16]>] = &[TestVector { + key: &hex!("5BE3D61217B96181FE6786AD716B890B"), + nonce: &hex!("B194BAC80A08F53B"), + aad: &hex!( + "E12BDC1AE28257EC703FCCF095EE8DF1C1AB76389FE678CAF7C6F860D5BB9C4F" + "F33C657B637C306ADD4EA7799EB23D313E" + ), + plaintext: &[0; 192], + ciphertext: &hex!( + "3833B2E4A51E8BFA91C4E6AC3CA7B3E15734F392B29474F7BD8120897A5D13A8" + "A09B32FAC052CE41D2BBD68D552A93A7AA0CCD9180D000CADBBD8BA00BAF9E05" + "308E7D559091503B18CE9303BCF8693EED776603BBB092C9C3A9C27A70192092" + "D08398846A2D390F53DA6938974EE3B4A500BC9DEE21A10100F51D4C3427B070" + "C5E35075598091960DE6AF44EDA361340E6020B397CB5256792A600D737C7A66" + "28001749CC5A5B7363FEFDAE20E4A5FD118A785A42A888121761C518D4ACABF0" + ), + tag: &hex!("73BF4BD53A2ED6B13B582BC70CA04E57"), + }]; + + tests!(BashPrgAe1281, TEST_VECTORS); +} + +/// BASH.PRG.AE192.1 +mod ae192_1 { + use super::*; + use bash_prg_ae::BashPrgAe1922; + + const TEST_VECTORS: &[TestVector<[u8; 24], [u8; 0], [u8; 24]>] = &[TestVector { + key: &hex!("2033394D6C320D0965201A166E62001D6779410674740E13"), + nonce: &[], + aad: &hex!("5468697274792074776F2062797465206D657373616765202874657374203129"), + plaintext: &hex!( + "466966747920666F75722062797465206F7220666F75722068756E6472656420" + "7468697274792074776F20626974206D657373616765" + ), + ciphertext: &hex!( + "7BFC37CEEA096D4B1A611B92219CCEABCF5B997ABFE1533C4CFBF4EB866E5A09" + "991C98C73B04A614BE45E471E9287E2D2A1D7C43C8DD" + ), + tag: &hex!("B7C89033F37FF0A501368542027AF891DAEC399D9B5A2AB8"), + }]; + + tests!(BashPrgAe1922, TEST_VECTORS); +} + +/// BASH.PRG.AE192.2 +mod ae192_2 { + use super::*; + use bash_prg_ae::BashPrgAe1922; + + const TEST_VECTORS: &[TestVector<[u8; 24], [u8; 12], [u8; 24]>] = &[TestVector { + key: &hex!("5BE3D61217B96181FE6786AD716B890B5CB0C0FF33C356B8"), + nonce: &hex!("9DEADEC2621747A62A80A7C3"), + aad: &[], + plaintext: &hex!("5468697274792074776F2062797465206D657373616765202874657374203129"), + ciphertext: &hex!("EC10AB97D0EB88F2FA32EBB780B2FD9C3E011523E53B07C4D603501D6D3D416A"), + tag: &hex!("2EA75655204A58F7E7E1AB46974692BC9BD2B8E3C00DF8A3"), + }]; + + tests!(BashPrgAe1922, TEST_VECTORS); +} + +/// BASH.PRG.AE192.3 +mod ae192_3 { + use super::*; + use bash_prg_ae::BashPrgAe1921; + + const TEST_VECTORS: &[TestVector<[u8; 24], [u8; 0], [u8; 24]>] = &[TestVector { + key: &hex!("2033394D6C320D0965201A166E62001D6779410674740E13"), + nonce: &[], + aad: &hex!("5468697274792074776F2062797465206D657373616765202874657374203129"), + plaintext: &[], + ciphertext: &[], + tag: &hex!("D140D605C4F6BE6881E9A32766ED2869C6F89F35E3B00542"), + }]; + + tests!(BashPrgAe1921, TEST_VECTORS); +} + +/// BASH.PRG.AE192.4 +mod ae192_4 { + use super::*; + use bash_prg_ae::BashPrgAe1921; + + const TEST_VECTORS: &[TestVector<[u8; 24], [u8; 12], [u8; 24]>] = &[TestVector { + key: &hex!("5BE3D61217B96181FE6786AD716B890B5CB0C0FF33C356B8"), + nonce: &hex!("B194BAC80A08F53B366D008E"), + aad: &hex!( + "E12BDC1AE28257EC703FCCF095EE8DF1C1AB76389FE678CAF7C6F860D5BB9C4F" + "F33C657B637C306ADD4EA7799EB23D313E" + ), + plaintext: &[0; 192], + ciphertext: &hex!( + "97198569936C6BC6ADE8B5BB087C997F076661797B88DB72DF764E1D0A5C917D" + "6CFB8733451BE071B52F9F6AEC1C5507F7B966369F767DE55E2BF0E7F7240540" + "4D617996D1E4972BFE29315F39F015217E9579EAEFCC6503F666ECC25ADBAFD4" + "BBE1E0A13F1B008A4C29DC153FEE7399D764FD2716A4E43BE52216E22AA0A036" + "ECBDD6426DA9D090064143941E888E1CCD0154A14116BD3B6F84F4BC400E6705" + "629DA6E6E1453D1CF706B05F0B095A43880476C494EF5FD687764EEDBED4A69A" + ), + tag: &hex!("8A10881AB42C5DD180D2970931296ED89353CBA62C30D7FD"), + }]; + + tests!(BashPrgAe1921, TEST_VECTORS); +} + +/// BASH.PRG.AE256.1 +mod ae256_1 { + use super::*; + use bash_prg_ae::BashPrgAe2562; + + const TEST_VECTORS: &[TestVector<[u8; 32], [u8; 0], [u8; 32]>] = &[TestVector { + key: &hex!("2033394D6C320D0965201A166E62001D6779410674740E136865160D3D730C11"), + nonce: &[], + aad: &hex!("5468697274792074776F2062797465206D657373616765202874657374203129"), + plaintext: &hex!( + "466966747920666F75722062797465206F7220666F75722068756E6472656420" + "7468697274792074776F20626974206D657373616765" + ), + ciphertext: &hex!( + "1DA58141375539D1903E4C06AB373859779355B3C30031A8918A9D6F97624BA4" + "ABAE88F2335378B0FD63295D1E32FCB72DF068130A9B" + ), + tag: &hex!("3DF50074177D5EFAE170E621E68029166E3EDC7B6A1DB888394F2C335211BC6C"), + }]; + + tests!(BashPrgAe2562, TEST_VECTORS); +} + +/// BASH.PRG.AE256.2 +mod ae256_2 { + use super::*; + use bash_prg_ae::BashPrgAe2562; + + const TEST_VECTORS: &[TestVector<[u8; 32], [u8; 16], [u8; 32]>] = &[TestVector { + key: &hex!("5BE3D61217B96181FE6786AD716B890B5CB0C0FF33C356B835C405AED8E07F99"), + nonce: &hex!("9DEADEC2621747A62A80A7C3FFA8E347"), + aad: &[], + plaintext: &hex!("5468697274792074776F2062797465206D657373616765202874657374203129"), + ciphertext: &hex!("94E1191BACD0D021EA896457DA109A7FBF10576FAAC3675BBD242AED036397A2"), + tag: &hex!("409BF97685E03A99EE832042ECCBE9BA370774DE1CBFB84BC7218B83A99EB846"), + }]; + + tests!(BashPrgAe2562, TEST_VECTORS); +} + +/// BASH.PRG.AE256.3 +mod ae256_3 { + use super::*; + use bash_prg_ae::BashPrgAe2561; + + const TEST_VECTORS: &[TestVector<[u8; 32], [u8; 0], [u8; 32]>] = &[TestVector { + key: &hex!("2033394D6C320D0965201A166E62001D6779410674740E136865160D3D730C11"), + nonce: &[], + aad: &hex!("5468697274792074776F2062797465206D657373616765202874657374203129"), + plaintext: &[], + ciphertext: &[], + tag: &hex!("839A7287D0BBEE0231BB909AC24C3FDAFC9EA34921D29CBF97CCFD0902EC37ED"), + }]; + + tests!(BashPrgAe2561, TEST_VECTORS); +} + +/// BASH.PRG.AE256.4 +mod ae256_4 { + use super::*; + use bash_prg_ae::BashPrgAe2561; + + const TEST_VECTORS: &[TestVector<[u8; 32], [u8; 16], [u8; 32]>] = &[TestVector { + key: &hex!("5BE3D61217B96181FE6786AD716B890B5CB0C0FF33C356B835C405AED8E07F99"), + nonce: &hex!("B194BAC80A08F53B366D008E584A5DE4"), + aad: &hex!( + "E12BDC1AE28257EC703FCCF095EE8DF1C1AB76389FE678CAF7C6F860D5BB9C4F" + "F33C657B637C306ADD4EA7799EB23D313E" + ), + plaintext: &[0; 192], + ciphertext: &hex!( + "690673766C3E848CAC7C05169FFB7B7751E52A011040E5602573FAF991044A00" + "4329EEF7BED8E6875830A91854D1BD2EDC6FC2FF37851DBAC249DF400A0549EA" + "2E0C811D499E1FF1E5E32FAE7F0532FA4051D0F9E300D9B1DBF119AC8CFFC48D" + "D3CBF1CA0DBA5DD97481C88DF0BE412785E40988B31585537948B80F5A9C49E0" + "8DD684A7DCA871C380DFDC4C4DFBE61F50D2D0FBD24D8B9D32974A347247D001" + "BAD5B168440025693967E77394DC088B0ECCFA8D291BA13D44F60B06E2EDB351" + ), + tag: &hex!("CDE5AF6EF9A14B7D0C191B869A6343ED6A4E9AAB4EE00A579E9E682D0EC051E3"), + }]; + + tests!(BashPrgAe2561, TEST_VECTORS); +} diff --git a/bash-prg-ae/tests/common/mod.rs b/bash-prg-ae/tests/common/mod.rs new file mode 100644 index 00000000..92647d25 --- /dev/null +++ b/bash-prg-ae/tests/common/mod.rs @@ -0,0 +1,96 @@ +//! Common functionality shared by tests + +/// AEAD test vector. +#[derive(Debug)] +pub struct TestVector { + pub key: &'static K, + pub nonce: &'static N, + pub aad: &'static [u8], + pub plaintext: &'static [u8], + pub ciphertext: &'static [u8], + pub tag: &'static T, +} + +/// Generate encryption, decryption and tampering tests for an AEAD. +#[macro_export] +macro_rules! tests { + ($aead:ty, $vectors:expr) => { + use bash_prg_ae::aead::{Aead, AeadInOut, KeyInit, Payload, array::Array}; + + #[test] + fn encrypt() { + for vector in $vectors { + let key = Array(*vector.key); + let nonce = Array(*vector.nonce); + let payload = Payload { + msg: vector.plaintext, + aad: vector.aad, + }; + + let cipher = <$aead>::new(&key); + let ciphertext = cipher.encrypt(&nonce, payload).unwrap(); + let (ct, tag) = ciphertext.split_at(ciphertext.len() - vector.tag.len()); + assert_eq!(vector.ciphertext, ct); + assert_eq!(vector.tag, tag); + } + } + + #[test] + fn decrypt() { + for vector in $vectors { + let key = Array(*vector.key); + let nonce = Array(*vector.nonce); + let mut ciphertext = Vec::from(vector.ciphertext); + ciphertext.extend_from_slice(vector.tag); + + let payload = Payload { + msg: &ciphertext, + aad: vector.aad, + }; + + let cipher = <$aead>::new(&key); + let plaintext = cipher.decrypt(&nonce, payload).unwrap(); + + assert_eq!(vector.plaintext, plaintext.as_slice()); + } + } + + #[test] + fn decrypt_modified() { + let vector = &$vectors[0]; + let key = Array(*vector.key); + let nonce = Array(*vector.nonce); + + let mut ciphertext = Vec::from(vector.ciphertext); + ciphertext.extend_from_slice(vector.tag); + ciphertext[0] ^= 0xaa; + + let payload = Payload { + msg: &ciphertext, + aad: vector.aad, + }; + + let cipher = <$aead>::new(&key); + assert!(cipher.decrypt(&nonce, payload).is_err()); + } + + #[test] + fn decrypt_in_place_detached_modified() { + let vector = &$vectors[$vectors.len() - 1]; + let key = Array(*vector.key); + let nonce = Array(*vector.nonce); + + let mut buffer = Vec::from(vector.ciphertext); + let mut tag = Array(*vector.tag); + tag[0] ^= 0xaa; + + let cipher = <$aead>::new(&key); + assert!( + cipher + .decrypt_inout_detached(&nonce, vector.aad, buffer.as_mut_slice().into(), &tag) + .is_err() + ); + assert_eq!(vector.ciphertext, buffer); + } + }; +}