From 7591f5845b61a0fd06f599bc2ed70302966e3f6c Mon Sep 17 00:00:00 2001 From: sanketh Date: Fri, 15 Sep 2023 23:46:52 -0400 Subject: [PATCH 1/7] first commit --- Cargo.lock | 13 + Cargo.toml | 1 + ocb3/CHANGELOG.md | 10 + ocb3/Cargo.toml | 42 +++ ocb3/LICENSE-APACHE | 201 +++++++++++++++ ocb3/LICENSE-MIT | 25 ++ ocb3/README.md | 51 ++++ ocb3/src/lib.rs | 604 ++++++++++++++++++++++++++++++++++++++++++++ ocb3/src/util.rs | 76 ++++++ ocb3/tests/kats.rs | 250 ++++++++++++++++++ 10 files changed, 1273 insertions(+) create mode 100644 ocb3/CHANGELOG.md create mode 100644 ocb3/Cargo.toml create mode 100644 ocb3/LICENSE-APACHE create mode 100644 ocb3/LICENSE-MIT create mode 100644 ocb3/README.md create mode 100644 ocb3/src/lib.rs create mode 100644 ocb3/src/util.rs create mode 100644 ocb3/tests/kats.rs diff --git a/Cargo.lock b/Cargo.lock index f7ec63af..3cd3e453 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -422,6 +422,19 @@ dependencies = [ "subtle", ] +[[package]] +name = "ocb3" +version = "0.1.0" +dependencies = [ + "aead", + "aes", + "cipher 0.4.4", + "ctr", + "hex-literal 0.3.4", + "subtle", + "zeroize", +] + [[package]] name = "opaque-debug" version = "0.3.0" diff --git a/Cargo.toml b/Cargo.toml index 4b726a48..6c40803d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,4 +9,5 @@ members = [ "deoxys", "eax", "mgm", + "ocb3", ] diff --git a/ocb3/CHANGELOG.md b/ocb3/CHANGELOG.md new file mode 100644 index 00000000..f0c2c862 --- /dev/null +++ b/ocb3/CHANGELOG.md @@ -0,0 +1,10 @@ +# 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 (2023-XX-XX) + +- Initial release diff --git a/ocb3/Cargo.toml b/ocb3/Cargo.toml new file mode 100644 index 00000000..b8502bd7 --- /dev/null +++ b/ocb3/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "ocb3" +version = "0.1.0" +description = """ +Pure Rust implementation of the AES-OCB3 +Authenticated Encryption with Associated Data (AEAD) Cipher +""" +authors = ["RustCrypto Developers"] +edition = "2021" +license = "Apache-2.0 OR MIT" +readme = "README.md" +documentation = "https://docs.rs/ocb3" +repository = "https://github.com/RustCrypto/AEADs" +keywords = ["aead", "aes", "encryption", "ocb", "ocb3"] +categories = ["cryptography", "no-std"] +rust-version = "1.56" + +[dependencies] +aead = { version = "0.5", default-features = false } +aes = { version = "0.8", optional = true } +cipher = "0.4" +ctr = "0.9" +subtle = { version = "2", default-features = false } +zeroize = { version = "1", optional = true, default-features = false } + +[dev-dependencies] +aead = { version = "0.5", features = ["dev"], default-features = false } +hex-literal = "0.3" + +[features] +default = ["aes", "alloc", "getrandom"] +std = ["aead/std", "alloc"] +alloc = ["aead/alloc"] +arrayvec = ["aead/arrayvec"] +getrandom = ["aead/getrandom", "rand_core"] +heapless = ["aead/heapless"] +rand_core = ["aead/rand_core"] +stream = ["aead/stream"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/ocb3/LICENSE-APACHE b/ocb3/LICENSE-APACHE new file mode 100644 index 00000000..78173fa2 --- /dev/null +++ b/ocb3/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 [yyyy] [name of copyright owner] + +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/ocb3/LICENSE-MIT b/ocb3/LICENSE-MIT new file mode 100644 index 00000000..50c61807 --- /dev/null +++ b/ocb3/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2023 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/ocb3/README.md b/ocb3/README.md new file mode 100644 index 00000000..730e35bb --- /dev/null +++ b/ocb3/README.md @@ -0,0 +1,51 @@ +# RustCrypto: OCB3 + +[![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 **OCB3** ([RFC 7253][rfc7253])[Authenticated Encryption with Associated Data (AEAD)][aead] cipher. + +[Documentation][docs-link] + +## 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](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://buildstats.info/crate/ocb3 +[crate-link]: https://crates.io/crates/chacha20poly1305 +[docs-image]: https://docs.rs/ocb3/badge.svg +[docs-link]: https://docs.rs/ocb3/ +[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 +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260038-AEADs +[build-image]: https://github.com/RustCrypto/AEADs/workflows/ocb3/badge.svg?branch=master&event=push +[build-link]: https://github.com/RustCrypto/AEADs/actions + +[//]: # (general links) + +[rfc7253]: https://datatracker.ietf.org/doc/rfc7253/ +[aead]: https://en.wikipedia.org/wiki/Authenticated_encryption diff --git a/ocb3/src/lib.rs b/ocb3/src/lib.rs new file mode 100644 index 00000000..a34aa3b5 --- /dev/null +++ b/ocb3/src/lib.rs @@ -0,0 +1,604 @@ +#![no_std] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc = include_str!("../README.md")] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg" +)] +#![deny(unsafe_code)] +#![warn(missing_docs, rust_2018_idioms)] +#![allow(non_snake_case)] + +use core::marker::PhantomData; + +pub use aead::{ + self, generic_array::GenericArray, AeadCore, AeadInPlace, Error, KeyInit, KeySizeUser, +}; +use aes::{self, Aes128, Aes256, Block}; +use cipher::{ + consts::{U0, U12, U16}, + BlockDecrypt, BlockEncrypt, BlockSizeUser, +}; + +mod util; +use crate::util::{double, inplace_xor, ntz}; +use subtle::ConstantTimeEq; + +/// Number of L values to be precomputed. Precomputing m values, allows +/// processing inputs of length up to 2^m blocks (2^m * 16 bytes) without +/// needing to calculate L values at runtime. +/// +/// By setting this to 32, we can process inputs of length up to 1 terabyte. +const L_TABLE_SIZE: usize = 32; + +/// Max associated data. +pub const A_MAX: usize = 1 << (L_TABLE_SIZE + 4); +/// Max plaintext. +pub const P_MAX: usize = 1 << (L_TABLE_SIZE + 4); +/// Max ciphertext. +pub const C_MAX: usize = 1 << (L_TABLE_SIZE + 4); + +/// OCB3 nonce +pub type Nonce = GenericArray; + +/// OCB3 tag +pub type Tag = GenericArray; + +/// AES-OCB3 with a 128-bit key, 96-bit nonce, and 128-bit tag. +pub type Aes128Ocb3 = AesOcb3; + +/// AES-OCB3 with a 256-bit key, 96-bit nonce, and 128-bit tag. +pub type Aes256Ocb3 = AesOcb3; + +/// Trait implemented for valid tag sizes +pub trait TagSize: private::SealedTagSize {} +impl TagSize for T {} +/// Trait implemented for valid nonce sizes +pub trait NonceSize: private::SealedNonceSize {} +impl NonceSize for T {} + +// Adapted from https://github.com/sgmenda/AEADs/blob/2209bcaa9edc65e9a60498e7ece5b50e66f32ebf/aes-gcm/src/lib.rs#L143-L157 +mod private { + use aead::generic_array::ArrayLength; + use cipher::{consts, Unsigned}; + + // Sealed traits stop other crates from implementing any traits that use it. + pub trait SealedTagSize: ArrayLength + Unsigned {} + pub trait SealedNonceSize: ArrayLength + Unsigned {} + + // Tags are <= 128 bits + impl SealedTagSize for consts::U8 {} + impl SealedTagSize for consts::U12 {} + impl SealedTagSize for consts::U16 {} + + // Nonces are <= 120 bits + impl SealedNonceSize for consts::U12 {} +} +/// AES-OCB3: generic over an AES implementation, nonce size, and tag size. +/// +/// WARNING: Unless absolutely necessary, prefer the aliases Aes128Ocb3 and +/// Aes256Ocb3. +#[derive(Clone)] +pub struct AesOcb3 +where + NonceSize: self::NonceSize, + TagSize: self::TagSize, +{ + cipher: Aes, + nonce_size: PhantomData, + tag_size: PhantomData, + // precomputed key-dependent variables + Lstar: Block, + Ldollar: Block, + // list of pre-computed L values + L: [Block; L_TABLE_SIZE], +} + +/// Output of the HASH function defined in https://www.rfc-editor.org/rfc/rfc7253.html#section-4.1 +type SumSize = U16; +type Sum = GenericArray; + +impl KeySizeUser for AesOcb3 +where + Aes: KeySizeUser, + TagSize: self::TagSize, + NonceSize: self::NonceSize, +{ + type KeySize = Aes::KeySize; +} + +impl KeyInit for AesOcb3 +where + Aes: BlockSizeUser + BlockEncrypt + KeyInit + BlockDecrypt, + TagSize: self::TagSize, + NonceSize: self::NonceSize, +{ + fn new(key: &aead::Key) -> Self { + Aes::new(key).into() + } +} + +impl AeadCore for AesOcb3 +where + NonceSize: self::NonceSize, + TagSize: self::TagSize, +{ + type NonceSize = NonceSize; + type TagSize = TagSize; + type CiphertextOverhead = U0; +} + +impl From for AesOcb3 +where + Aes: BlockSizeUser + BlockEncrypt + BlockDecrypt, + TagSize: self::TagSize, + NonceSize: self::NonceSize, +{ + fn from(cipher: Aes) -> Self { + let (Lstar, Ldollar, L) = key_dependent_variables(&cipher); + + Self { + cipher, + nonce_size: PhantomData, + tag_size: PhantomData, + Lstar, + Ldollar, + L, + } + } +} + +/// Computes key-dependent variables defined in +/// https://www.rfc-editor.org/rfc/rfc7253.html#section-4.1 +fn key_dependent_variables + BlockEncrypt>( + cipher: &Aes, +) -> (Block, Block, [Block; L_TABLE_SIZE]) { + let mut zeros = [0u8; 16]; + let Lstar = Block::from_mut_slice(&mut zeros); + cipher.encrypt_block(Lstar); + let Ldollar = double(Lstar); + + let mut L = [Block::default(); L_TABLE_SIZE]; + let mut Li = Ldollar; + #[allow(clippy::needless_range_loop)] + for i in 0..L_TABLE_SIZE { + Li = double(&Li); + L[i] = Li + } + (*Lstar, Ldollar, L) +} + +impl AeadInPlace for AesOcb3 +where + Aes: BlockSizeUser + BlockEncrypt + BlockDecrypt, + TagSize: self::TagSize, + NonceSize: self::NonceSize, +{ + fn encrypt_in_place_detached( + &self, + nonce: &Nonce, + associated_data: &[u8], + buffer: &mut [u8], + ) -> aead::Result> { + if (buffer.len() > P_MAX) || (associated_data.len() > A_MAX) { + unimplemented!() + } + + // First, try to process many blocks at once. + let (processed_bytes, mut Offset_i, mut Checksum_i) = self.wide_encrypt(nonce, buffer); + + let mut i = (processed_bytes / 16) + 1; + + // Then, process the remaining blocks. + for P_i in buffer[processed_bytes..].chunks_exact_mut(16) { + let P_i = Block::from_mut_slice(P_i); + // Offset_i = Offset_{i-1} xor L_{ntz(i)} + inplace_xor(&mut Offset_i, &self.L[ntz(i)]); + // Checksum_i = Checksum_{i-1} xor P_i + inplace_xor(&mut Checksum_i, P_i); + // C_i = Offset_i xor ENCIPHER(K, P_i xor Offset_i) + let C_i = P_i; + inplace_xor(C_i, &Offset_i); + self.cipher.encrypt_block(C_i); + inplace_xor(C_i, &Offset_i); + + i += 1; + } + + // Process any partial blocks. + if (buffer.len() % 16) != 0 { + let processed_bytes = (i - 1) * 16; + let remaining_bytes = buffer.len() - processed_bytes; + + // Offset_* = Offset_m xor L_* + inplace_xor(&mut Offset_i, &self.Lstar); + // Pad = ENCIPHER(K, Offset_*) + let mut Pad = Block::default(); + inplace_xor(&mut Pad, &Offset_i); + self.cipher.encrypt_block(&mut Pad); + // Checksum_* = Checksum_m xor (P_* || 1 || zeros(127-bitlen(P_*))) + let Checksum_rhs = &mut [0u8; 16]; + Checksum_rhs[..remaining_bytes].copy_from_slice(&buffer[processed_bytes..]); + Checksum_rhs[remaining_bytes] = 0b1000_0000; + inplace_xor(&mut Checksum_i, Block::from_slice(Checksum_rhs)); + // C_* = P_* xor Pad[1..bitlen(P_*)] + let P_star = &mut buffer[processed_bytes..]; + let Pad = &mut Pad[..P_star.len()]; + for (aa, bb) in P_star.iter_mut().zip(Pad) { + *aa ^= *bb; + } + } + + let tag = self.compute_tag(associated_data, &mut Checksum_i, &Offset_i); + + Ok(tag) + } + + fn decrypt_in_place_detached( + &self, + nonce: &Nonce, + associated_data: &[u8], + buffer: &mut [u8], + tag: &aead::Tag, + ) -> aead::Result<()> { + let expected_tag = self.decrypt_in_place_return_tag(nonce, associated_data, buffer); + if expected_tag.ct_eq(tag).into() { + Ok(()) + } else { + Err(Error) + } + } +} + +impl AesOcb3 +where + Aes: BlockSizeUser + BlockEncrypt + BlockDecrypt, + TagSize: self::TagSize, + NonceSize: self::NonceSize, +{ + /// Decrypts in place and returns expected tag. + pub(crate) fn decrypt_in_place_return_tag( + &self, + nonce: &Nonce, + associated_data: &[u8], + buffer: &mut [u8], + ) -> aead::Tag { + if (buffer.len() > C_MAX) || (associated_data.len() > A_MAX) { + unimplemented!() + } + + // First, try to process many blocks at once. + let (processed_bytes, mut Offset_i, mut Checksum_i) = self.wide_decrypt(nonce, buffer); + + let mut i = (processed_bytes / 16) + 1; + + // Then, process the remaining blocks. + for C_i in buffer[processed_bytes..].chunks_exact_mut(16) { + let C_i = Block::from_mut_slice(C_i); + // Offset_i = Offset_{i-1} xor L_{ntz(i)} + inplace_xor(&mut Offset_i, &self.L[ntz(i)]); + // P_i = Offset_i xor DECIPHER(K, C_i xor Offset_i) + let P_i = C_i; + inplace_xor(P_i, &Offset_i); + self.cipher.decrypt_block(P_i); + inplace_xor(P_i, &Offset_i); + // Checksum_i = Checksum_{i-1} xor P_i + inplace_xor(&mut Checksum_i, P_i); + + i += 1; + } + + // Process any partial blocks. + if (buffer.len() % 16) != 0 { + let processed_bytes = (i - 1) * 16; + let remaining_bytes = buffer.len() - processed_bytes; + + // Offset_* = Offset_m xor L_* + inplace_xor(&mut Offset_i, &self.Lstar); + // Pad = ENCIPHER(K, Offset_*) + let mut Pad = Block::default(); + inplace_xor(&mut Pad, &Offset_i); + self.cipher.encrypt_block(&mut Pad); + // P_* = C_* xor Pad[1..bitlen(C_*)] + let C_star = &mut buffer[processed_bytes..]; + let Pad = &mut Pad[..C_star.len()]; + for (aa, bb) in C_star.iter_mut().zip(Pad) { + *aa ^= *bb; + } + // Checksum_* = Checksum_m xor (P_* || 1 || zeros(127-bitlen(P_*))) + let Checksum_rhs = &mut [0u8; 16]; + Checksum_rhs[..remaining_bytes].copy_from_slice(&buffer[processed_bytes..]); + Checksum_rhs[remaining_bytes] = 0b1000_0000; + inplace_xor(&mut Checksum_i, Block::from_slice(Checksum_rhs)); + } + + self.compute_tag(associated_data, &mut Checksum_i, &Offset_i) + } + + /// Encrypts plaintext in groups of WIDTH. + /// + /// Adapted from https://www.cs.ucdavis.edu/~rogaway/ocb/news/code/ocb.c + #[inline(never)] + fn wide_encrypt(&self, nonce: &Nonce, buffer: &mut [u8]) -> (usize, Block, Block) { + #[cfg(not(target_feature = "avx512vaes"))] + const WIDTH: usize = 2; + #[cfg(not(target_feature = "avx512vaes"))] + let split_into_blocks = crate::util::split_into_two_blocks; + + #[cfg(target_feature = "avx512vaes")] + const WIDTH: usize = 4; + #[cfg(target_feature = "avx512vaes")] + let split_into_blocks = crate::util::split_into_four_blocks; + + let mut i = 1; + + let mut Offset_i = [Block::default(); WIDTH]; + Offset_i[Offset_i.len() - 1] = initial_offset(&self.cipher, nonce, TagSize::to_u32()); + let mut Checksum_i = Block::default(); + #[allow(unsafe_code)] + unsafe { + for wide_blocks in buffer.chunks_exact_mut(16 * WIDTH) { + let P_i = split_into_blocks(wide_blocks); + + // Checksum_i = Checksum_{i-1} xor P_i + for P_ij in &P_i { + inplace_xor(&mut Checksum_i, P_ij); + } + + // Offset_i = Offset_{i-1} xor L_{ntz(i)} + Offset_i[0] = Offset_i[Offset_i.len() - 1]; + inplace_xor(&mut Offset_i[0], &self.L[ntz(i)]); + for j in 1..P_i.len() { + Offset_i[j] = Offset_i[j - 1]; + inplace_xor(&mut Offset_i[j], &self.L[ntz(i + j)]); + } + + // C_i = Offset_i xor ENCIPHER(K, P_i xor Offset_i) + for j in 0..P_i.len() { + inplace_xor(P_i[j], &Offset_i[j]); + self.cipher.encrypt_block(P_i[j]); + inplace_xor(P_i[j], &Offset_i[j]) + } + + i += WIDTH; + } + } + + let processed_bytes = (buffer.len() / (WIDTH * 16)) * (WIDTH * 16); + + (processed_bytes, Offset_i[Offset_i.len() - 1], Checksum_i) + } + + /// Decrypts plaintext in groups of WIDTH. + /// + /// Adapted from https://www.cs.ucdavis.edu/~rogaway/ocb/news/code/ocb.c + #[inline(never)] + fn wide_decrypt(&self, nonce: &Nonce, buffer: &mut [u8]) -> (usize, Block, Block) { + #[cfg(not(target_feature = "avx512vaes"))] + const WIDTH: usize = 2; + #[cfg(not(target_feature = "avx512vaes"))] + let split_into_blocks = crate::util::split_into_two_blocks; + + #[cfg(target_feature = "avx512vaes")] + const WIDTH: usize = 4; + #[cfg(target_feature = "avx512vaes")] + let split_into_blocks = crate::util::split_into_four_blocks; + + let mut i = 1; + + let mut Offset_i = [Block::default(); WIDTH]; + Offset_i[Offset_i.len() - 1] = initial_offset(&self.cipher, nonce, TagSize::to_u32()); + let mut Checksum_i = Block::default(); + #[allow(unsafe_code)] + unsafe { + for wide_blocks in buffer.chunks_exact_mut(16 * WIDTH) { + let C_i = split_into_blocks(wide_blocks); + + // Offset_i = Offset_{i-1} xor L_{ntz(i)} + Offset_i[0] = Offset_i[Offset_i.len() - 1]; + inplace_xor(&mut Offset_i[0], &self.L[ntz(i)]); + for j in 1..C_i.len() { + Offset_i[j] = Offset_i[j - 1]; + inplace_xor(&mut Offset_i[j], &self.L[ntz(i + j)]); + } + + // P_i = Offset_i xor DECIPHER(K, C_i xor Offset_i) + // Checksum_i = Checksum_{i-1} xor P_i + for j in 0..C_i.len() { + inplace_xor(C_i[j], &Offset_i[j]); + self.cipher.decrypt_block(C_i[j]); + inplace_xor(C_i[j], &Offset_i[j]); + inplace_xor(&mut Checksum_i, C_i[j]); + } + + i += WIDTH; + } + } + + let processed_bytes = (buffer.len() / (WIDTH * 16)) * (WIDTH * 16); + + (processed_bytes, Offset_i[Offset_i.len() - 1], Checksum_i) + } +} + +/// Computes nonce-dependent variables as defined +/// in https://www.rfc-editor.org/rfc/rfc7253.html#section-4.2 +/// +/// Assumes a 96-bit nonce and 128-bit tag. +fn nonce_dependent_variables< + Aes: BlockSizeUser + BlockEncrypt, + NonceSize: self::NonceSize, +>( + cipher: &Aes, + N: &Nonce, + tag_len: u32, +) -> (usize, [u8; 24]) { + let mut Nonce = [0u8; 16]; + Nonce[4..16].copy_from_slice(N.as_slice()); + let mut Nonce = u128::from_be_bytes(Nonce); + // Nonce = num2str(TAGLEN mod 128,7) || zeros(120-bitlen(N)) || 1 || N + Nonce |= 1 << 96; + if tag_len == 16 { + // do nothing because 128 mod 128 = 0 + } else if tag_len < 16 { + Nonce |= (u128::from(tag_len) * 8) << (128 - 7); + } else { + unreachable!(); + } + + // Separate the last 6 bits into `bottom`, and the rest into `top`. + let bottom = usize::try_from(Nonce & 0b111111).unwrap(); + let top = Nonce & !0b111111; + + let mut Ktop = Block::from(top.to_be_bytes()); + cipher.encrypt_block(&mut Ktop); + let Ktop = Ktop.as_mut_slice(); + + // Stretch = Ktop || (Ktop[1..64] xor Ktop[9..72]) + let mut Stretch = [0u8; 24]; + Stretch[..16].copy_from_slice(Ktop); + for i in 0..8 { + Ktop[i] ^= Ktop[i + 1]; + } + Stretch[16..].copy_from_slice(&Ktop[..8]); + + (bottom, Stretch) +} + +/// Computes the initial offset as defined +/// in https://www.rfc-editor.org/rfc/rfc7253.html#section-4.2 +/// +/// Assumes a 96-bit nonce and 128-bit tag. +fn initial_offset< + Aes: BlockSizeUser + BlockEncrypt, + NonceSize: self::NonceSize, +>( + cipher: &Aes, + N: &Nonce, + tag_size: u32, +) -> Block { + let (bottom, Stretch) = nonce_dependent_variables(cipher, N, tag_size); + let Stretch_low = u128::from_be_bytes((&Stretch[..16]).try_into().unwrap()); + let Stretch_hi = u64::from_be_bytes((&Stretch[16..24]).try_into().unwrap()); + let Stretch_hi = u128::from(Stretch_hi); + + // Offset_0 = Stretch[1+bottom..128+bottom] + let Offset = (Stretch_low << bottom) | (Stretch_hi >> (64 - bottom)); + Offset.to_be_bytes().into() +} + +impl AesOcb3 +where + Aes: BlockSizeUser + BlockEncrypt, + TagSize: self::TagSize, + NonceSize: self::NonceSize, +{ + /// Computes HASH function defined in https://www.rfc-editor.org/rfc/rfc7253.html#section-4.1 + fn hash(&self, associated_data: &[u8]) -> Sum { + let mut Offset_i = Block::default(); + let mut Sum_i = Block::default(); + + let mut i = 1; + for A_i in associated_data.chunks_exact(16) { + // Offset_i = Offset_{i-1} xor L_{ntz(i)} + inplace_xor(&mut Offset_i, &self.L[ntz(i)]); + // Sum_i = Sum_{i-1} xor ENCIPHER(K, A_i xor Offset_i) + let mut A_i = *Block::from_slice(A_i); + inplace_xor(&mut A_i, &Offset_i); + self.cipher.encrypt_block(&mut A_i); + inplace_xor(&mut Sum_i, &A_i); + + i += 1; + } + + // Process any partial blocks. + if (associated_data.len() % 16) != 0 { + let processed_bytes = (i - 1) * 16; + let remaining_bytes = associated_data.len() - processed_bytes; + + // Offset_* = Offset_m xor L_* + inplace_xor(&mut Offset_i, &self.Lstar); + // CipherInput = (A_* || 1 || zeros(127-bitlen(A_*))) xor Offset_* + let CipherInput = &mut [0u8; 16]; + CipherInput[..remaining_bytes].copy_from_slice(&associated_data[processed_bytes..]); + CipherInput[remaining_bytes] = 0b1000_0000; + let CipherInput = Block::from_mut_slice(CipherInput); + inplace_xor(CipherInput, &Offset_i); + // Sum = Sum_m xor ENCIPHER(K, CipherInput) + self.cipher.encrypt_block(CipherInput); + inplace_xor(&mut Sum_i, CipherInput); + } + + Sum_i + } + + fn compute_tag( + &self, + associated_data: &[u8], + Checksum_m: &mut Block, + Offset_m: &Block, + ) -> Tag { + // Tag = ENCIPHER(K, Checksum_m xor Offset_m xor L_$) xor HASH(K,A) + let full_tag = Checksum_m; + inplace_xor(full_tag, Offset_m); + inplace_xor(full_tag, &self.Ldollar); + self.cipher.encrypt_block(full_tag); + inplace_xor(full_tag, &self.hash(associated_data)); + + // truncate the tag to the required length + Tag::clone_from_slice(&full_tag[..TagSize::to_usize()]) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use hex_literal::hex; + + #[test] + fn double_basic_test() { + let zero = Block::from(hex!("00000000000000000000000000000000")); + assert_eq!(zero, double(&zero)); + let one = Block::from(hex!("00000000000000000000000000000001")); + let two = Block::from(hex!("00000000000000000000000000000002")); + assert_eq!(two, double(&one)); + } + + #[test] + fn rfc7253_key_dependent_constants() { + // Test vector from page 17 of https://www.rfc-editor.org/rfc/rfc7253.html + let key = hex!("000102030405060708090A0B0C0D0E0F"); + let expected_Lstar = Block::from(hex!("C6A13B37878F5B826F4F8162A1C8D879")); + let expected_Ldollar = Block::from(hex!("8D42766F0F1EB704DE9F02C54391B075")); + let expected_L0 = Block::from(hex!("1A84ECDE1E3D6E09BD3E058A8723606D")); + let expected_L1 = Block::from(hex!("3509D9BC3C7ADC137A7C0B150E46C0DA")); + + let cipher = Aes128::new(GenericArray::from_slice(&key)); + let (Lstar, Ldollar, L) = key_dependent_variables(&cipher); + + assert_eq!(Lstar, expected_Lstar); + assert_eq!(Ldollar, expected_Ldollar); + assert_eq!(L[0], expected_L0); + assert_eq!(L[1], expected_L1); + } + + #[test] + fn rfc7253_nonce_dependent_constants() { + // Test vector from page 17 of https://www.rfc-editor.org/rfc/rfc7253.html + let key = hex!("000102030405060708090A0B0C0D0E0F"); + let nonce = hex!("BBAA9988776655443322110F"); + let expected_bottom = usize::try_from(15).unwrap(); + let expected_Stretch = hex!("9862B0FDEE4E2DD56DBA6433F0125AA2FAD24D13A063F8B8"); + let expected_Offset_0 = Block::from(hex!("587EF72716EAB6DD3219F8092D517D69")); + + const TAGLEN: u32 = 16; + + let cipher = Aes128::new(GenericArray::from_slice(&key)); + let (bottom, Stretch) = nonce_dependent_variables(&cipher, &Nonce::from(nonce), TAGLEN); + let Offset_0 = initial_offset(&cipher, &Nonce::from(nonce), TAGLEN); + + assert_eq!(bottom, expected_bottom); + assert_eq!(Stretch, expected_Stretch); + assert_eq!(Offset_0, expected_Offset_0); + } +} diff --git a/ocb3/src/util.rs b/ocb3/src/util.rs new file mode 100644 index 00000000..a24c237b --- /dev/null +++ b/ocb3/src/util.rs @@ -0,0 +1,76 @@ +#![allow(dead_code)] + +use core::slice::from_raw_parts_mut; + +use aead::generic_array::{ArrayLength, GenericArray}; +use aes::Block; + +#[inline] +pub(crate) fn inplace_xor(a: &mut GenericArray, b: &GenericArray) +where + U: ArrayLength, + T: core::ops::BitXor + Copy, +{ + for (aa, bb) in a.as_mut_slice().iter_mut().zip(b.as_slice()) { + *aa = *aa ^ *bb; + } +} + +/// Doubles a block, in GF(2^128). +/// +/// Adapted from https://github.com/RustCrypto/universal-hashes/blob/9b0ac5d1/polyval/src/mulx.rs#L5-L18 +#[inline] +pub(crate) fn double(block: &Block) -> Block { + let mut v = u128::from_be_bytes((*block).into()); + let v_hi = v >> 127; + + // If v_hi = 0, return (v << 1) + // If v_hi = 1, return (v << 1) xor (0b0...010000111) + v <<= 1; + v ^= v_hi ^ (v_hi << 1) ^ (v_hi << 2) ^ (v_hi << 7); + v.to_be_bytes().into() +} + +/// Counts the number of non-trailing zeros in the binary representation. +/// +/// Defined in https://www.rfc-editor.org/rfc/rfc7253.html#section-2 +#[inline] +pub(crate) fn ntz(n: usize) -> usize { + n.trailing_zeros().try_into().unwrap() +} + +const BLOCK_SIZE: usize = 16; + +/// Adapted from https://doc.rust-lang.org/std/primitive.slice.html#method.split_at_mut_unchecked +/// +/// SAFETY: Assumes that `two_blocks` is exactly two blocks. +#[inline] +#[allow(unsafe_code)] +pub(crate) unsafe fn split_into_two_blocks(two_blocks: &mut [u8]) -> [&mut Block; 2] { + let ptr = two_blocks.as_mut_ptr(); + + unsafe { + [ + from_raw_parts_mut(ptr, BLOCK_SIZE).into(), + from_raw_parts_mut(ptr.add(BLOCK_SIZE), BLOCK_SIZE).into(), + ] + } +} + +/// Adapted from https://doc.rust-lang.org/std/primitive.slice.html#method.split_at_mut_unchecked +/// +/// SAFETY: Assumes that `four_blocks` is exactly four blocks. +#[inline] +#[allow(unsafe_code)] +pub(crate) unsafe fn split_into_four_blocks(four_blocks: &mut [u8]) -> [&mut Block; 4] { + let ptr = four_blocks.as_mut_ptr(); + + unsafe { + [ + from_raw_parts_mut(ptr, BLOCK_SIZE).into(), + from_raw_parts_mut(ptr.add(BLOCK_SIZE), BLOCK_SIZE).into(), + from_raw_parts_mut(ptr.add(2 * BLOCK_SIZE), BLOCK_SIZE).into(), + from_raw_parts_mut(ptr.add(3 * BLOCK_SIZE), BLOCK_SIZE).into(), + ] + } +} diff --git a/ocb3/tests/kats.rs b/ocb3/tests/kats.rs new file mode 100644 index 00000000..bf4d8198 --- /dev/null +++ b/ocb3/tests/kats.rs @@ -0,0 +1,250 @@ +#![allow(non_snake_case)] + +use aead::{ + consts::{U12, U8}, + AeadInPlace, KeyInit, +}; +use aes::{Aes128, Aes192, Aes256, Block}; +use hex_literal::hex; +use ocb3::{Aes128Ocb3, Aes256Ocb3, AesOcb3, GenericArray}; + +/// Test vectors from https://www.rfc-editor.org/rfc/rfc7253.html#appendix-A +#[test] +fn rfc7253_sample_results() { + let key = GenericArray::from(hex!("000102030405060708090A0B0C0D0E0F")); + struct Kat { + nonce: Vec, + associated_data: Vec, + plaintext: Vec, + ciphertext: Vec, + } + let kats = [ + Kat { + nonce: hex!("BBAA99887766554433221100").to_vec(), + associated_data: hex!("").to_vec(), + plaintext: hex!("").to_vec(), + ciphertext: hex!("785407BFFFC8AD9EDCC5520AC9111EE6").to_vec(), + }, + Kat { + nonce: hex!("BBAA99887766554433221101").to_vec(), + associated_data: hex!("0001020304050607").to_vec(), + plaintext: hex!("0001020304050607").to_vec(), + ciphertext: hex!("6820B3657B6F615A5725BDA0D3B4EB3A257C9AF1F8F03009").to_vec(), + }, + Kat { + nonce: hex!("BBAA99887766554433221102").to_vec(), + associated_data: hex!("0001020304050607").to_vec(), + plaintext: hex!("").to_vec(), + ciphertext: hex!("81017F8203F081277152FADE694A0A00").to_vec(), + }, + Kat { + nonce: hex!("BBAA99887766554433221103").to_vec(), + associated_data: hex!("").to_vec(), + plaintext: hex!("0001020304050607").to_vec(), + ciphertext: hex!("45DD69F8F5AAE72414054CD1F35D82760B2CD00D2F99BFA9").to_vec(), + }, + Kat { + nonce: hex!("BBAA99887766554433221104").to_vec(), + associated_data: hex!("000102030405060708090A0B0C0D0E0F").to_vec(), + plaintext: hex!("000102030405060708090A0B0C0D0E0F").to_vec(), + ciphertext: hex!( + "571D535B60B277188BE5147170A9A22C3AD7A4FF3835B8C5701C1CCEC8FC3358" + ) + .to_vec(), + }, + Kat { + nonce: hex!("BBAA99887766554433221105").to_vec(), + associated_data: hex!("000102030405060708090A0B0C0D0E0F").to_vec(), + plaintext: hex!("").to_vec(), + ciphertext: hex!("8CF761B6902EF764462AD86498CA6B97").to_vec(), + }, + Kat { + nonce: hex!("BBAA99887766554433221106").to_vec(), + associated_data: hex!("").to_vec(), + plaintext: hex!("000102030405060708090A0B0C0D0E0F").to_vec(), + ciphertext: hex!( + "5CE88EC2E0692706A915C00AEB8B2396F40E1C743F52436BDF06D8FA1ECA343D" + ) + .to_vec(), + }, + Kat { + nonce: hex!("BBAA99887766554433221107").to_vec(), + associated_data: hex!("000102030405060708090A0B0C0D0E0F1011121314151617").to_vec(), + plaintext: hex!("000102030405060708090A0B0C0D0E0F1011121314151617").to_vec(), + ciphertext: hex!("1CA2207308C87C010756104D8840CE1952F09673A448A122C92C62241051F57356D7F3C90BB0E07F").to_vec(), + }, + Kat { + nonce: hex!("BBAA99887766554433221108").to_vec(), + associated_data: hex!("000102030405060708090A0B0C0D0E0F1011121314151617").to_vec(), + plaintext: hex!("").to_vec(), + ciphertext: hex!("6DC225A071FC1B9F7C69F93B0F1E10DE").to_vec(), + }, + Kat { + nonce: hex!("BBAA99887766554433221109").to_vec(), + associated_data: hex!("").to_vec(), + plaintext: hex!("000102030405060708090A0B0C0D0E0F1011121314151617").to_vec(), + ciphertext: hex!("221BD0DE7FA6FE993ECCD769460A0AF2D6CDED0C395B1C3CE725F32494B9F914D85C0B1EB38357FF").to_vec(), + }, + Kat { + nonce: hex!("BBAA9988776655443322110A").to_vec(), + associated_data: hex!("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F").to_vec(), + plaintext: hex!("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F").to_vec(), + ciphertext: hex!("BD6F6C496201C69296C11EFD138A467ABD3C707924B964DEAFFC40319AF5A48540FBBA186C5553C68AD9F592A79A4240").to_vec(), + }, + Kat { + nonce: hex!("BBAA9988776655443322110B").to_vec(), + associated_data: hex!("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F").to_vec(), + plaintext: hex!("").to_vec(), + ciphertext: hex!("FE80690BEE8A485D11F32965BC9D2A32").to_vec(), + }, + Kat { + nonce: hex!("BBAA9988776655443322110C").to_vec(), + associated_data: hex!("").to_vec(), + plaintext: hex!("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F").to_vec(), + ciphertext: hex!("2942BFC773BDA23CABC6ACFD9BFD5835BD300F0973792EF46040C53F1432BCDFB5E1DDE3BC18A5F840B52E653444D5DF").to_vec(), + }, + Kat { + nonce: hex!("BBAA9988776655443322110D").to_vec(), + associated_data: hex!("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627").to_vec(), + plaintext: hex!("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627").to_vec(), + ciphertext: hex!("D5CA91748410C1751FF8A2F618255B68A0A12E093FF454606E59F9C1D0DDC54B65E8628E568BAD7AED07BA06A4A69483A7035490C5769E60").to_vec(), + }, + Kat { + nonce: hex!("BBAA9988776655443322110E").to_vec(), + associated_data: hex!("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627").to_vec(), + plaintext: hex!("").to_vec(), + ciphertext: hex!("C5CD9D1850C141E358649994EE701B68").to_vec(), + }, + Kat { + nonce: hex!("BBAA9988776655443322110F").to_vec(), + associated_data: hex!("").to_vec(), + plaintext: hex!("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627").to_vec(), + ciphertext: hex!("4412923493C57D5DE0D700F753CCE0D1D2D95060122E9F15A5DDBFC5787E50B5CC55EE507BCB084E479AD363AC366B95A98CA5F3000B1479").to_vec(), + }, + ]; + + for kat in kats { + let ocb3 = Aes128Ocb3::new(&key); + + let buffer = &mut kat.plaintext.clone(); + let tag = ocb3 + .encrypt_in_place_detached( + kat.nonce.as_slice().into(), + kat.associated_data.as_slice(), + buffer, + ) + .unwrap(); + + assert_eq!( + &tag, + Block::from_slice(&kat.ciphertext.as_slice()[kat.ciphertext.len() - 16..]) + ); + assert_eq!( + buffer.as_slice(), + &kat.ciphertext.as_slice()[..kat.ciphertext.len() - 16] + ); + + let res = ocb3.decrypt_in_place_detached( + kat.nonce.as_slice().into(), + kat.associated_data.as_slice(), + buffer, + &tag, + ); + assert!(res.is_ok()); + assert_eq!(buffer.as_slice(), kat.plaintext.as_slice()); + } +} + +fn num2str96(num: usize) -> [u8; 12] { + let num: u32 = num.try_into().unwrap(); + let mut out = [0u8; 12]; + out[8..12].copy_from_slice(&num.to_be_bytes()); + out +} + +/// Test vectors from Page 18 of https://www.rfc-editor.org/rfc/rfc7253.html#appendix-A +macro_rules! rfc7253_wider_variety { + ($ocb:tt, $keylen:tt, $taglen:expr, $expected:expr) => { + let mut key_bytes = vec![0u8; $keylen]; + key_bytes[$keylen - 1] = 8 * $taglen; // taglen in bytes + + let key = GenericArray::from_slice(key_bytes.as_slice()); + let ocb = $ocb::new(key); + + let mut ciphertext = Vec::new(); + + for i in 0..128 { + // S = zeros(8i) + let S = vec![0u8; i]; + + // N = num2str(3i+1,96) + // C = C || OCB-ENCRYPT(K,N,S,S) + let N = num2str96(3 * i + 1); + let mut buffer = S.clone(); + let tag = ocb + .encrypt_in_place_detached(N.as_slice().into(), &S, &mut buffer) + .unwrap(); + ciphertext.append(&mut buffer); + ciphertext.append(&mut tag.as_slice().to_vec()); + + // N = num2str(3i+2,96) + // C = C || OCB-ENCRYPT(K,N,,S) + let N = num2str96(3 * i + 2); + let mut buffer = S.clone(); + let tag = ocb + .encrypt_in_place_detached(N.as_slice().into(), &[], &mut buffer) + .unwrap(); + ciphertext.append(&mut buffer); + ciphertext.append(&mut tag.as_slice().to_vec()); + + // N = num2str(3i+3,96) + // C = C || OCB-ENCRYPT(K,N,S,) + let N = num2str96(3 * i + 3); + let tag = ocb + .encrypt_in_place_detached(N.as_slice().into(), &S, &mut []) + .unwrap(); + ciphertext.append(&mut tag.as_slice().to_vec()); + } + if $taglen == 16 { + assert_eq!(ciphertext.len(), 22_400); + } else if $taglen == 12 { + assert_eq!(ciphertext.len(), 20_864); + } else if $taglen == 8 { + assert_eq!(ciphertext.len(), 19_328); + } else { + unreachable!(); + } + + // N = num2str(385,96) + // Output : OCB-ENCRYPT(K,N,C,) + let N = num2str96(385); + let tag = ocb + .encrypt_in_place_detached(N.as_slice().into(), &ciphertext, &mut []) + .unwrap(); + + assert_eq!(tag.as_slice(), hex!($expected)) + }; +} + +// More types for testing +type Aes192Ocb3 = AesOcb3; +type Aes128Ocb3Tag96 = AesOcb3; +type Aes192Ocb3Tag96 = AesOcb3; +type Aes256Ocb3Tag96 = AesOcb3; +type Aes128Ocb3Tag64 = AesOcb3; +type Aes192Ocb3Tag64 = AesOcb3; +type Aes256Ocb3Tag64 = AesOcb3; + +/// Test vectors from Page 18 of https://www.rfc-editor.org/rfc/rfc7253.html#appendix-A +#[test] +fn rfc7253_more_sample_results() { + rfc7253_wider_variety!(Aes128Ocb3, 16, 16, "67E944D23256C5E0B6C61FA22FDF1EA2"); + rfc7253_wider_variety!(Aes192Ocb3, 24, 16, "F673F2C3E7174AAE7BAE986CA9F29E17"); + rfc7253_wider_variety!(Aes256Ocb3, 32, 16, "D90EB8E9C977C88B79DD793D7FFA161C"); + rfc7253_wider_variety!(Aes128Ocb3Tag96, 16, 12, "77A3D8E73589158D25D01209"); + rfc7253_wider_variety!(Aes192Ocb3Tag96, 24, 12, "05D56EAD2752C86BE6932C5E"); + rfc7253_wider_variety!(Aes256Ocb3Tag96, 32, 12, "5458359AC23B0CBA9E6330DD"); + rfc7253_wider_variety!(Aes128Ocb3Tag64, 16, 8, "192C9B7BD90BA06A"); + rfc7253_wider_variety!(Aes192Ocb3Tag64, 24, 8, "0066BC6E0EF34E24"); + rfc7253_wider_variety!(Aes256Ocb3Tag64, 32, 8, "7D4EA5D445501CBE"); +} From 0a4c0d308b86d94644e275b1271372f87f5cffcf Mon Sep 17 00:00:00 2001 From: sanketh Date: Sat, 16 Sep 2023 04:21:21 -0400 Subject: [PATCH 2/7] fix typos --- ocb3/src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ocb3/src/lib.rs b/ocb3/src/lib.rs index a34aa3b5..6aec629d 100644 --- a/ocb3/src/lib.rs +++ b/ocb3/src/lib.rs @@ -57,7 +57,7 @@ impl TagSize for T {} pub trait NonceSize: private::SealedNonceSize {} impl NonceSize for T {} -// Adapted from https://github.com/sgmenda/AEADs/blob/2209bcaa9edc65e9a60498e7ece5b50e66f32ebf/aes-gcm/src/lib.rs#L143-L157 +// Adapted from https://github.com/rustcrypto/AEADs/blob/2209bcaa9edc65e9a60498e7ece5b50e66f32ebf/aes-gcm/src/lib.rs#L143-L157 mod private { use aead::generic_array::ArrayLength; use cipher::{consts, Unsigned}; @@ -318,7 +318,6 @@ where /// Encrypts plaintext in groups of WIDTH. /// /// Adapted from https://www.cs.ucdavis.edu/~rogaway/ocb/news/code/ocb.c - #[inline(never)] fn wide_encrypt(&self, nonce: &Nonce, buffer: &mut [u8]) -> (usize, Block, Block) { #[cfg(not(target_feature = "avx512vaes"))] const WIDTH: usize = 2; @@ -372,7 +371,6 @@ where /// Decrypts plaintext in groups of WIDTH. /// /// Adapted from https://www.cs.ucdavis.edu/~rogaway/ocb/news/code/ocb.c - #[inline(never)] fn wide_decrypt(&self, nonce: &Nonce, buffer: &mut [u8]) -> (usize, Block, Block) { #[cfg(not(target_feature = "avx512vaes"))] const WIDTH: usize = 2; From 41bd413ff19e01a0fa61a5995119b0e396805cb6 Mon Sep 17 00:00:00 2001 From: sanketh Date: Sat, 16 Sep 2023 04:28:49 -0400 Subject: [PATCH 3/7] fix clippy warning --- ocb3/src/lib.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ocb3/src/lib.rs b/ocb3/src/lib.rs index 6aec629d..4c82d071 100644 --- a/ocb3/src/lib.rs +++ b/ocb3/src/lib.rs @@ -436,12 +436,12 @@ fn nonce_dependent_variables< let mut Nonce = u128::from_be_bytes(Nonce); // Nonce = num2str(TAGLEN mod 128,7) || zeros(120-bitlen(N)) || 1 || N Nonce |= 1 << 96; - if tag_len == 16 { - // do nothing because 128 mod 128 = 0 - } else if tag_len < 16 { - Nonce |= (u128::from(tag_len) * 8) << (128 - 7); - } else { - unreachable!(); + match tag_len { + 16 => {} + x if x < 16 => { + Nonce |= (u128::from(tag_len) * 8) << (128 - 7); + } + _ => unreachable!(), } // Separate the last 6 bits into `bottom`, and the rest into `top`. From 76d1e4e822592b8ee3c3aaa2082765b46d85be8d Mon Sep 17 00:00:00 2001 From: sanketh Date: Sat, 7 Oct 2023 19:47:20 -0400 Subject: [PATCH 4/7] add ci config --- .github/workflows/ocb3.yml | 68 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 .github/workflows/ocb3.yml diff --git a/.github/workflows/ocb3.yml b/.github/workflows/ocb3.yml new file mode 100644 index 00000000..8e2e9595 --- /dev/null +++ b/.github/workflows/ocb3.yml @@ -0,0 +1,68 @@ +name: ocb3 + +on: + pull_request: + paths: + - ".github/workflows/ocb3.yml" + - "ocb3/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: ocb3 + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.56.0 # MSRV + - stable + target: + - armv7a-none-eabi + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.target }} + - run: cargo build --no-default-features --release --target ${{ matrix.target }} + + test: + runs-on: ubuntu-latest + strategy: + matrix: + include: + # 32-bit Linux + - target: i686-unknown-linux-gnu + rust: 1.56.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.56.0 # MSRV + - target: x86_64-unknown-linux-gnu + rust: stable + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.target }} + - run: ${{ matrix.deps }} + - run: cargo test --target ${{ matrix.target }} --release + - run: cargo test --target ${{ matrix.target }} --release --features stream,std,zeroize + - run: cargo test --target ${{ matrix.target }} --release --all-features + - run: cargo build --target ${{ matrix.target }} --benches From 4bdda1140b5371ae00c9106e76510e4b3f848648 Mon Sep 17 00:00:00 2001 From: sanketh Date: Sat, 7 Oct 2023 19:58:04 -0400 Subject: [PATCH 5/7] fix typo in README --- ocb3/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ocb3/README.md b/ocb3/README.md index 730e35bb..1189152e 100644 --- a/ocb3/README.md +++ b/ocb3/README.md @@ -35,7 +35,7 @@ dual licensed as above, without any additional terms or conditions. [//]: # (badges) [crate-image]: https://buildstats.info/crate/ocb3 -[crate-link]: https://crates.io/crates/chacha20poly1305 +[crate-link]: https://crates.io/crates/ocb3 [docs-image]: https://docs.rs/ocb3/badge.svg [docs-link]: https://docs.rs/ocb3/ [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg From 757ba513ff021229835a39db75b1f5ce4ec9c2d6 Mon Sep 17 00:00:00 2001 From: sanketh Date: Sat, 7 Oct 2023 19:58:39 -0400 Subject: [PATCH 6/7] use only snake case --- ocb3/Cargo.toml | 4 +- ocb3/src/lib.rs | 351 ++++++++++++++++++++++++------------------------ 2 files changed, 177 insertions(+), 178 deletions(-) diff --git a/ocb3/Cargo.toml b/ocb3/Cargo.toml index b8502bd7..f9e1be8d 100644 --- a/ocb3/Cargo.toml +++ b/ocb3/Cargo.toml @@ -2,7 +2,7 @@ name = "ocb3" version = "0.1.0" description = """ -Pure Rust implementation of the AES-OCB3 +Pure Rust implementation of the OCB3 Authenticated Encryption with Associated Data (AEAD) Cipher """ authors = ["RustCrypto Developers"] @@ -11,7 +11,7 @@ license = "Apache-2.0 OR MIT" readme = "README.md" documentation = "https://docs.rs/ocb3" repository = "https://github.com/RustCrypto/AEADs" -keywords = ["aead", "aes", "encryption", "ocb", "ocb3"] +keywords = ["aead", "encryption", "ocb", "ocb3"] categories = ["cryptography", "no-std"] rust-version = "1.56" diff --git a/ocb3/src/lib.rs b/ocb3/src/lib.rs index 4c82d071..d4fa7f4b 100644 --- a/ocb3/src/lib.rs +++ b/ocb3/src/lib.rs @@ -7,7 +7,6 @@ )] #![deny(unsafe_code)] #![warn(missing_docs, rust_2018_idioms)] -#![allow(non_snake_case)] use core::marker::PhantomData; @@ -88,10 +87,10 @@ where nonce_size: PhantomData, tag_size: PhantomData, // precomputed key-dependent variables - Lstar: Block, - Ldollar: Block, + ll_star: Block, + ll_dollar: Block, // list of pre-computed L values - L: [Block; L_TABLE_SIZE], + ll: [Block; L_TABLE_SIZE], } /// Output of the HASH function defined in https://www.rfc-editor.org/rfc/rfc7253.html#section-4.1 @@ -135,15 +134,15 @@ where NonceSize: self::NonceSize, { fn from(cipher: Aes) -> Self { - let (Lstar, Ldollar, L) = key_dependent_variables(&cipher); + let (ll_star, ll_dollar, ll) = key_dependent_variables(&cipher); Self { cipher, nonce_size: PhantomData, tag_size: PhantomData, - Lstar, - Ldollar, - L, + ll_star, + ll_dollar, + ll, } } } @@ -154,18 +153,18 @@ fn key_dependent_variables + BlockEncrypt>( cipher: &Aes, ) -> (Block, Block, [Block; L_TABLE_SIZE]) { let mut zeros = [0u8; 16]; - let Lstar = Block::from_mut_slice(&mut zeros); - cipher.encrypt_block(Lstar); - let Ldollar = double(Lstar); + let ll_star = Block::from_mut_slice(&mut zeros); + cipher.encrypt_block(ll_star); + let ll_dollar = double(ll_star); - let mut L = [Block::default(); L_TABLE_SIZE]; - let mut Li = Ldollar; + let mut ll = [Block::default(); L_TABLE_SIZE]; + let mut ll_i = ll_dollar; #[allow(clippy::needless_range_loop)] for i in 0..L_TABLE_SIZE { - Li = double(&Li); - L[i] = Li + ll_i = double(&ll_i); + ll[i] = ll_i } - (*Lstar, Ldollar, L) + (*ll_star, ll_dollar, ll) } impl AeadInPlace for AesOcb3 @@ -185,22 +184,22 @@ where } // First, try to process many blocks at once. - let (processed_bytes, mut Offset_i, mut Checksum_i) = self.wide_encrypt(nonce, buffer); + let (processed_bytes, mut offset_i, mut checksum_i) = self.wide_encrypt(nonce, buffer); let mut i = (processed_bytes / 16) + 1; // Then, process the remaining blocks. - for P_i in buffer[processed_bytes..].chunks_exact_mut(16) { - let P_i = Block::from_mut_slice(P_i); - // Offset_i = Offset_{i-1} xor L_{ntz(i)} - inplace_xor(&mut Offset_i, &self.L[ntz(i)]); - // Checksum_i = Checksum_{i-1} xor P_i - inplace_xor(&mut Checksum_i, P_i); - // C_i = Offset_i xor ENCIPHER(K, P_i xor Offset_i) - let C_i = P_i; - inplace_xor(C_i, &Offset_i); - self.cipher.encrypt_block(C_i); - inplace_xor(C_i, &Offset_i); + for p_i in buffer[processed_bytes..].chunks_exact_mut(16) { + let p_i = Block::from_mut_slice(p_i); + // offset_i = offset_{i-1} xor L_{ntz(i)} + inplace_xor(&mut offset_i, &self.ll[ntz(i)]); + // checksum_i = checksum_{i-1} xor p_i + inplace_xor(&mut checksum_i, p_i); + // c_i = offset_i xor ENCIPHER(K, p_i xor offset_i) + let c_i = p_i; + inplace_xor(c_i, &offset_i); + self.cipher.encrypt_block(c_i); + inplace_xor(c_i, &offset_i); i += 1; } @@ -210,26 +209,26 @@ where let processed_bytes = (i - 1) * 16; let remaining_bytes = buffer.len() - processed_bytes; - // Offset_* = Offset_m xor L_* - inplace_xor(&mut Offset_i, &self.Lstar); - // Pad = ENCIPHER(K, Offset_*) - let mut Pad = Block::default(); - inplace_xor(&mut Pad, &Offset_i); - self.cipher.encrypt_block(&mut Pad); - // Checksum_* = Checksum_m xor (P_* || 1 || zeros(127-bitlen(P_*))) - let Checksum_rhs = &mut [0u8; 16]; - Checksum_rhs[..remaining_bytes].copy_from_slice(&buffer[processed_bytes..]); - Checksum_rhs[remaining_bytes] = 0b1000_0000; - inplace_xor(&mut Checksum_i, Block::from_slice(Checksum_rhs)); + // offset_* = offset_m xor L_* + inplace_xor(&mut offset_i, &self.ll_star); + // Pad = ENCIPHER(K, offset_*) + let mut pad = Block::default(); + inplace_xor(&mut pad, &offset_i); + self.cipher.encrypt_block(&mut pad); + // checksum_* = checksum_m xor (P_* || 1 || zeros(127-bitlen(P_*))) + let checksum_rhs = &mut [0u8; 16]; + checksum_rhs[..remaining_bytes].copy_from_slice(&buffer[processed_bytes..]); + checksum_rhs[remaining_bytes] = 0b1000_0000; + inplace_xor(&mut checksum_i, Block::from_slice(checksum_rhs)); // C_* = P_* xor Pad[1..bitlen(P_*)] - let P_star = &mut buffer[processed_bytes..]; - let Pad = &mut Pad[..P_star.len()]; - for (aa, bb) in P_star.iter_mut().zip(Pad) { + let p_star = &mut buffer[processed_bytes..]; + let pad = &mut pad[..p_star.len()]; + for (aa, bb) in p_star.iter_mut().zip(pad) { *aa ^= *bb; } } - let tag = self.compute_tag(associated_data, &mut Checksum_i, &Offset_i); + let tag = self.compute_tag(associated_data, &mut checksum_i, &offset_i); Ok(tag) } @@ -268,22 +267,22 @@ where } // First, try to process many blocks at once. - let (processed_bytes, mut Offset_i, mut Checksum_i) = self.wide_decrypt(nonce, buffer); + let (processed_bytes, mut offset_i, mut checksum_i) = self.wide_decrypt(nonce, buffer); let mut i = (processed_bytes / 16) + 1; // Then, process the remaining blocks. - for C_i in buffer[processed_bytes..].chunks_exact_mut(16) { - let C_i = Block::from_mut_slice(C_i); - // Offset_i = Offset_{i-1} xor L_{ntz(i)} - inplace_xor(&mut Offset_i, &self.L[ntz(i)]); - // P_i = Offset_i xor DECIPHER(K, C_i xor Offset_i) - let P_i = C_i; - inplace_xor(P_i, &Offset_i); - self.cipher.decrypt_block(P_i); - inplace_xor(P_i, &Offset_i); - // Checksum_i = Checksum_{i-1} xor P_i - inplace_xor(&mut Checksum_i, P_i); + for c_i in buffer[processed_bytes..].chunks_exact_mut(16) { + let c_i = Block::from_mut_slice(c_i); + // offset_i = offset_{i-1} xor L_{ntz(i)} + inplace_xor(&mut offset_i, &self.ll[ntz(i)]); + // p_i = offset_i xor DECIPHER(K, c_i xor offset_i) + let p_i = c_i; + inplace_xor(p_i, &offset_i); + self.cipher.decrypt_block(p_i); + inplace_xor(p_i, &offset_i); + // checksum_i = checksum_{i-1} xor p_i + inplace_xor(&mut checksum_i, p_i); i += 1; } @@ -293,26 +292,26 @@ where let processed_bytes = (i - 1) * 16; let remaining_bytes = buffer.len() - processed_bytes; - // Offset_* = Offset_m xor L_* - inplace_xor(&mut Offset_i, &self.Lstar); - // Pad = ENCIPHER(K, Offset_*) - let mut Pad = Block::default(); - inplace_xor(&mut Pad, &Offset_i); - self.cipher.encrypt_block(&mut Pad); + // offset_* = offset_m xor L_* + inplace_xor(&mut offset_i, &self.ll_star); + // Pad = ENCIPHER(K, offset_*) + let mut pad = Block::default(); + inplace_xor(&mut pad, &offset_i); + self.cipher.encrypt_block(&mut pad); // P_* = C_* xor Pad[1..bitlen(C_*)] - let C_star = &mut buffer[processed_bytes..]; - let Pad = &mut Pad[..C_star.len()]; - for (aa, bb) in C_star.iter_mut().zip(Pad) { + let c_star = &mut buffer[processed_bytes..]; + let pad = &mut pad[..c_star.len()]; + for (aa, bb) in c_star.iter_mut().zip(pad) { *aa ^= *bb; } - // Checksum_* = Checksum_m xor (P_* || 1 || zeros(127-bitlen(P_*))) - let Checksum_rhs = &mut [0u8; 16]; - Checksum_rhs[..remaining_bytes].copy_from_slice(&buffer[processed_bytes..]); - Checksum_rhs[remaining_bytes] = 0b1000_0000; - inplace_xor(&mut Checksum_i, Block::from_slice(Checksum_rhs)); + // checksum_* = checksum_m xor (P_* || 1 || zeros(127-bitlen(P_*))) + let checksum_rhs = &mut [0u8; 16]; + checksum_rhs[..remaining_bytes].copy_from_slice(&buffer[processed_bytes..]); + checksum_rhs[remaining_bytes] = 0b1000_0000; + inplace_xor(&mut checksum_i, Block::from_slice(checksum_rhs)); } - self.compute_tag(associated_data, &mut Checksum_i, &Offset_i) + self.compute_tag(associated_data, &mut checksum_i, &offset_i) } /// Encrypts plaintext in groups of WIDTH. @@ -331,32 +330,32 @@ where let mut i = 1; - let mut Offset_i = [Block::default(); WIDTH]; - Offset_i[Offset_i.len() - 1] = initial_offset(&self.cipher, nonce, TagSize::to_u32()); - let mut Checksum_i = Block::default(); + let mut offset_i = [Block::default(); WIDTH]; + offset_i[offset_i.len() - 1] = initial_offset(&self.cipher, nonce, TagSize::to_u32()); + let mut checksum_i = Block::default(); #[allow(unsafe_code)] unsafe { for wide_blocks in buffer.chunks_exact_mut(16 * WIDTH) { - let P_i = split_into_blocks(wide_blocks); + let p_i = split_into_blocks(wide_blocks); - // Checksum_i = Checksum_{i-1} xor P_i - for P_ij in &P_i { - inplace_xor(&mut Checksum_i, P_ij); + // checksum_i = checksum_{i-1} xor p_i + for p_ij in &p_i { + inplace_xor(&mut checksum_i, p_ij); } - // Offset_i = Offset_{i-1} xor L_{ntz(i)} - Offset_i[0] = Offset_i[Offset_i.len() - 1]; - inplace_xor(&mut Offset_i[0], &self.L[ntz(i)]); - for j in 1..P_i.len() { - Offset_i[j] = Offset_i[j - 1]; - inplace_xor(&mut Offset_i[j], &self.L[ntz(i + j)]); + // offset_i = offset_{i-1} xor L_{ntz(i)} + offset_i[0] = offset_i[offset_i.len() - 1]; + inplace_xor(&mut offset_i[0], &self.ll[ntz(i)]); + for j in 1..p_i.len() { + offset_i[j] = offset_i[j - 1]; + inplace_xor(&mut offset_i[j], &self.ll[ntz(i + j)]); } - // C_i = Offset_i xor ENCIPHER(K, P_i xor Offset_i) - for j in 0..P_i.len() { - inplace_xor(P_i[j], &Offset_i[j]); - self.cipher.encrypt_block(P_i[j]); - inplace_xor(P_i[j], &Offset_i[j]) + // c_i = offset_i xor ENCIPHER(K, p_i xor offset_i) + for j in 0..p_i.len() { + inplace_xor(p_i[j], &offset_i[j]); + self.cipher.encrypt_block(p_i[j]); + inplace_xor(p_i[j], &offset_i[j]) } i += WIDTH; @@ -365,7 +364,7 @@ where let processed_bytes = (buffer.len() / (WIDTH * 16)) * (WIDTH * 16); - (processed_bytes, Offset_i[Offset_i.len() - 1], Checksum_i) + (processed_bytes, offset_i[offset_i.len() - 1], checksum_i) } /// Decrypts plaintext in groups of WIDTH. @@ -384,29 +383,29 @@ where let mut i = 1; - let mut Offset_i = [Block::default(); WIDTH]; - Offset_i[Offset_i.len() - 1] = initial_offset(&self.cipher, nonce, TagSize::to_u32()); - let mut Checksum_i = Block::default(); + let mut offset_i = [Block::default(); WIDTH]; + offset_i[offset_i.len() - 1] = initial_offset(&self.cipher, nonce, TagSize::to_u32()); + let mut checksum_i = Block::default(); #[allow(unsafe_code)] unsafe { for wide_blocks in buffer.chunks_exact_mut(16 * WIDTH) { - let C_i = split_into_blocks(wide_blocks); - - // Offset_i = Offset_{i-1} xor L_{ntz(i)} - Offset_i[0] = Offset_i[Offset_i.len() - 1]; - inplace_xor(&mut Offset_i[0], &self.L[ntz(i)]); - for j in 1..C_i.len() { - Offset_i[j] = Offset_i[j - 1]; - inplace_xor(&mut Offset_i[j], &self.L[ntz(i + j)]); + let c_i = split_into_blocks(wide_blocks); + + // offset_i = offset_{i-1} xor L_{ntz(i)} + offset_i[0] = offset_i[offset_i.len() - 1]; + inplace_xor(&mut offset_i[0], &self.ll[ntz(i)]); + for j in 1..c_i.len() { + offset_i[j] = offset_i[j - 1]; + inplace_xor(&mut offset_i[j], &self.ll[ntz(i + j)]); } - // P_i = Offset_i xor DECIPHER(K, C_i xor Offset_i) - // Checksum_i = Checksum_{i-1} xor P_i - for j in 0..C_i.len() { - inplace_xor(C_i[j], &Offset_i[j]); - self.cipher.decrypt_block(C_i[j]); - inplace_xor(C_i[j], &Offset_i[j]); - inplace_xor(&mut Checksum_i, C_i[j]); + // p_i = offset_i xor DECIPHER(K, c_i xor offset_i) + // checksum_i = checksum_{i-1} xor p_i + for j in 0..c_i.len() { + inplace_xor(c_i[j], &offset_i[j]); + self.cipher.decrypt_block(c_i[j]); + inplace_xor(c_i[j], &offset_i[j]); + inplace_xor(&mut checksum_i, c_i[j]); } i += WIDTH; @@ -415,7 +414,7 @@ where let processed_bytes = (buffer.len() / (WIDTH * 16)) * (WIDTH * 16); - (processed_bytes, Offset_i[Offset_i.len() - 1], Checksum_i) + (processed_bytes, offset_i[offset_i.len() - 1], checksum_i) } } @@ -428,39 +427,39 @@ fn nonce_dependent_variables< NonceSize: self::NonceSize, >( cipher: &Aes, - N: &Nonce, + nn: &Nonce, tag_len: u32, ) -> (usize, [u8; 24]) { - let mut Nonce = [0u8; 16]; - Nonce[4..16].copy_from_slice(N.as_slice()); - let mut Nonce = u128::from_be_bytes(Nonce); + let mut nonce = [0u8; 16]; + nonce[4..16].copy_from_slice(nn.as_slice()); + let mut nonce = u128::from_be_bytes(nonce); // Nonce = num2str(TAGLEN mod 128,7) || zeros(120-bitlen(N)) || 1 || N - Nonce |= 1 << 96; + nonce |= 1 << 96; match tag_len { 16 => {} x if x < 16 => { - Nonce |= (u128::from(tag_len) * 8) << (128 - 7); + nonce |= (u128::from(tag_len) * 8) << (128 - 7); } _ => unreachable!(), } // Separate the last 6 bits into `bottom`, and the rest into `top`. - let bottom = usize::try_from(Nonce & 0b111111).unwrap(); - let top = Nonce & !0b111111; + let bottom = usize::try_from(nonce & 0b111111).unwrap(); + let top = nonce & !0b111111; - let mut Ktop = Block::from(top.to_be_bytes()); - cipher.encrypt_block(&mut Ktop); - let Ktop = Ktop.as_mut_slice(); + let mut ktop = Block::from(top.to_be_bytes()); + cipher.encrypt_block(&mut ktop); + let ktop = ktop.as_mut_slice(); - // Stretch = Ktop || (Ktop[1..64] xor Ktop[9..72]) - let mut Stretch = [0u8; 24]; - Stretch[..16].copy_from_slice(Ktop); + // stretch = Ktop || (Ktop[1..64] xor Ktop[9..72]) + let mut stretch = [0u8; 24]; + stretch[..16].copy_from_slice(ktop); for i in 0..8 { - Ktop[i] ^= Ktop[i + 1]; + ktop[i] ^= ktop[i + 1]; } - Stretch[16..].copy_from_slice(&Ktop[..8]); + stretch[16..].copy_from_slice(&ktop[..8]); - (bottom, Stretch) + (bottom, stretch) } /// Computes the initial offset as defined @@ -472,17 +471,17 @@ fn initial_offset< NonceSize: self::NonceSize, >( cipher: &Aes, - N: &Nonce, + nn: &Nonce, tag_size: u32, ) -> Block { - let (bottom, Stretch) = nonce_dependent_variables(cipher, N, tag_size); - let Stretch_low = u128::from_be_bytes((&Stretch[..16]).try_into().unwrap()); - let Stretch_hi = u64::from_be_bytes((&Stretch[16..24]).try_into().unwrap()); - let Stretch_hi = u128::from(Stretch_hi); - - // Offset_0 = Stretch[1+bottom..128+bottom] - let Offset = (Stretch_low << bottom) | (Stretch_hi >> (64 - bottom)); - Offset.to_be_bytes().into() + let (bottom, stretch) = nonce_dependent_variables(cipher, nn, tag_size); + let stretch_low = u128::from_be_bytes((&stretch[..16]).try_into().unwrap()); + let stretch_hi = u64::from_be_bytes((&stretch[16..24]).try_into().unwrap()); + let stretch_hi = u128::from(stretch_hi); + + // offset_0 = stretch[1+bottom..128+bottom] + let offset = (stretch_low << bottom) | (stretch_hi >> (64 - bottom)); + offset.to_be_bytes().into() } impl AesOcb3 @@ -493,18 +492,18 @@ where { /// Computes HASH function defined in https://www.rfc-editor.org/rfc/rfc7253.html#section-4.1 fn hash(&self, associated_data: &[u8]) -> Sum { - let mut Offset_i = Block::default(); - let mut Sum_i = Block::default(); + let mut offset_i = Block::default(); + let mut sum_i = Block::default(); let mut i = 1; - for A_i in associated_data.chunks_exact(16) { - // Offset_i = Offset_{i-1} xor L_{ntz(i)} - inplace_xor(&mut Offset_i, &self.L[ntz(i)]); - // Sum_i = Sum_{i-1} xor ENCIPHER(K, A_i xor Offset_i) - let mut A_i = *Block::from_slice(A_i); - inplace_xor(&mut A_i, &Offset_i); - self.cipher.encrypt_block(&mut A_i); - inplace_xor(&mut Sum_i, &A_i); + for a_i in associated_data.chunks_exact(16) { + // offset_i = offset_{i-1} xor L_{ntz(i)} + inplace_xor(&mut offset_i, &self.ll[ntz(i)]); + // Sum_i = Sum_{i-1} xor ENCIPHER(K, A_i xor offset_i) + let mut a_i = *Block::from_slice(a_i); + inplace_xor(&mut a_i, &offset_i); + self.cipher.encrypt_block(&mut a_i); + inplace_xor(&mut sum_i, &a_i); i += 1; } @@ -514,32 +513,32 @@ where let processed_bytes = (i - 1) * 16; let remaining_bytes = associated_data.len() - processed_bytes; - // Offset_* = Offset_m xor L_* - inplace_xor(&mut Offset_i, &self.Lstar); - // CipherInput = (A_* || 1 || zeros(127-bitlen(A_*))) xor Offset_* - let CipherInput = &mut [0u8; 16]; - CipherInput[..remaining_bytes].copy_from_slice(&associated_data[processed_bytes..]); - CipherInput[remaining_bytes] = 0b1000_0000; - let CipherInput = Block::from_mut_slice(CipherInput); - inplace_xor(CipherInput, &Offset_i); + // offset_* = offset_m xor L_* + inplace_xor(&mut offset_i, &self.ll_star); + // CipherInput = (A_* || 1 || zeros(127-bitlen(A_*))) xor offset_* + let cipher_input = &mut [0u8; 16]; + cipher_input[..remaining_bytes].copy_from_slice(&associated_data[processed_bytes..]); + cipher_input[remaining_bytes] = 0b1000_0000; + let cipher_input = Block::from_mut_slice(cipher_input); + inplace_xor(cipher_input, &offset_i); // Sum = Sum_m xor ENCIPHER(K, CipherInput) - self.cipher.encrypt_block(CipherInput); - inplace_xor(&mut Sum_i, CipherInput); + self.cipher.encrypt_block(cipher_input); + inplace_xor(&mut sum_i, cipher_input); } - Sum_i + sum_i } fn compute_tag( &self, associated_data: &[u8], - Checksum_m: &mut Block, - Offset_m: &Block, + checksum_m: &mut Block, + offset_m: &Block, ) -> Tag { - // Tag = ENCIPHER(K, Checksum_m xor Offset_m xor L_$) xor HASH(K,A) - let full_tag = Checksum_m; - inplace_xor(full_tag, Offset_m); - inplace_xor(full_tag, &self.Ldollar); + // Tag = ENCIPHER(K, checksum_m xor offset_m xor L_$) xor HASH(K,A) + let full_tag = checksum_m; + inplace_xor(full_tag, offset_m); + inplace_xor(full_tag, &self.ll_dollar); self.cipher.encrypt_block(full_tag); inplace_xor(full_tag, &self.hash(associated_data)); @@ -566,18 +565,18 @@ mod tests { fn rfc7253_key_dependent_constants() { // Test vector from page 17 of https://www.rfc-editor.org/rfc/rfc7253.html let key = hex!("000102030405060708090A0B0C0D0E0F"); - let expected_Lstar = Block::from(hex!("C6A13B37878F5B826F4F8162A1C8D879")); - let expected_Ldollar = Block::from(hex!("8D42766F0F1EB704DE9F02C54391B075")); - let expected_L0 = Block::from(hex!("1A84ECDE1E3D6E09BD3E058A8723606D")); - let expected_L1 = Block::from(hex!("3509D9BC3C7ADC137A7C0B150E46C0DA")); + let expected_ll_star = Block::from(hex!("C6A13B37878F5B826F4F8162A1C8D879")); + let expected_ll_dollar = Block::from(hex!("8D42766F0F1EB704DE9F02C54391B075")); + let expected_ll0 = Block::from(hex!("1A84ECDE1E3D6E09BD3E058A8723606D")); + let expected_ll1 = Block::from(hex!("3509D9BC3C7ADC137A7C0B150E46C0DA")); let cipher = Aes128::new(GenericArray::from_slice(&key)); - let (Lstar, Ldollar, L) = key_dependent_variables(&cipher); + let (ll_star, ll_dollar, ll) = key_dependent_variables(&cipher); - assert_eq!(Lstar, expected_Lstar); - assert_eq!(Ldollar, expected_Ldollar); - assert_eq!(L[0], expected_L0); - assert_eq!(L[1], expected_L1); + assert_eq!(ll_star, expected_ll_star); + assert_eq!(ll_dollar, expected_ll_dollar); + assert_eq!(ll[0], expected_ll0); + assert_eq!(ll[1], expected_ll1); } #[test] @@ -586,17 +585,17 @@ mod tests { let key = hex!("000102030405060708090A0B0C0D0E0F"); let nonce = hex!("BBAA9988776655443322110F"); let expected_bottom = usize::try_from(15).unwrap(); - let expected_Stretch = hex!("9862B0FDEE4E2DD56DBA6433F0125AA2FAD24D13A063F8B8"); - let expected_Offset_0 = Block::from(hex!("587EF72716EAB6DD3219F8092D517D69")); + let expected_stretch = hex!("9862B0FDEE4E2DD56DBA6433F0125AA2FAD24D13A063F8B8"); + let expected_offset_0 = Block::from(hex!("587EF72716EAB6DD3219F8092D517D69")); const TAGLEN: u32 = 16; let cipher = Aes128::new(GenericArray::from_slice(&key)); - let (bottom, Stretch) = nonce_dependent_variables(&cipher, &Nonce::from(nonce), TAGLEN); - let Offset_0 = initial_offset(&cipher, &Nonce::from(nonce), TAGLEN); + let (bottom, stretch) = nonce_dependent_variables(&cipher, &Nonce::from(nonce), TAGLEN); + let offset_0 = initial_offset(&cipher, &Nonce::from(nonce), TAGLEN); assert_eq!(bottom, expected_bottom); - assert_eq!(Stretch, expected_Stretch); - assert_eq!(Offset_0, expected_Offset_0); + assert_eq!(stretch, expected_stretch); + assert_eq!(offset_0, expected_offset_0); } } From afa10b4450d87de84e3abeb3facc08d0b92a7398 Mon Sep 17 00:00:00 2001 From: sanketh Date: Sat, 7 Oct 2023 20:12:33 -0400 Subject: [PATCH 7/7] remove unsafe code --- ocb3/src/lib.rs | 110 +++++++++++++++++++---------------------------- ocb3/src/util.rs | 38 ++-------------- 2 files changed, 48 insertions(+), 100 deletions(-) diff --git a/ocb3/src/lib.rs b/ocb3/src/lib.rs index d4fa7f4b..64266a86 100644 --- a/ocb3/src/lib.rs +++ b/ocb3/src/lib.rs @@ -314,52 +314,42 @@ where self.compute_tag(associated_data, &mut checksum_i, &offset_i) } - /// Encrypts plaintext in groups of WIDTH. + /// Encrypts plaintext in groups of two. /// /// Adapted from https://www.cs.ucdavis.edu/~rogaway/ocb/news/code/ocb.c fn wide_encrypt(&self, nonce: &Nonce, buffer: &mut [u8]) -> (usize, Block, Block) { - #[cfg(not(target_feature = "avx512vaes"))] const WIDTH: usize = 2; - #[cfg(not(target_feature = "avx512vaes"))] let split_into_blocks = crate::util::split_into_two_blocks; - #[cfg(target_feature = "avx512vaes")] - const WIDTH: usize = 4; - #[cfg(target_feature = "avx512vaes")] - let split_into_blocks = crate::util::split_into_four_blocks; - let mut i = 1; let mut offset_i = [Block::default(); WIDTH]; offset_i[offset_i.len() - 1] = initial_offset(&self.cipher, nonce, TagSize::to_u32()); let mut checksum_i = Block::default(); - #[allow(unsafe_code)] - unsafe { - for wide_blocks in buffer.chunks_exact_mut(16 * WIDTH) { - let p_i = split_into_blocks(wide_blocks); - - // checksum_i = checksum_{i-1} xor p_i - for p_ij in &p_i { - inplace_xor(&mut checksum_i, p_ij); - } - - // offset_i = offset_{i-1} xor L_{ntz(i)} - offset_i[0] = offset_i[offset_i.len() - 1]; - inplace_xor(&mut offset_i[0], &self.ll[ntz(i)]); - for j in 1..p_i.len() { - offset_i[j] = offset_i[j - 1]; - inplace_xor(&mut offset_i[j], &self.ll[ntz(i + j)]); - } - - // c_i = offset_i xor ENCIPHER(K, p_i xor offset_i) - for j in 0..p_i.len() { - inplace_xor(p_i[j], &offset_i[j]); - self.cipher.encrypt_block(p_i[j]); - inplace_xor(p_i[j], &offset_i[j]) - } - - i += WIDTH; + for wide_blocks in buffer.chunks_exact_mut(16 * WIDTH) { + let p_i = split_into_blocks(wide_blocks); + + // checksum_i = checksum_{i-1} xor p_i + for p_ij in &p_i { + inplace_xor(&mut checksum_i, p_ij); + } + + // offset_i = offset_{i-1} xor L_{ntz(i)} + offset_i[0] = offset_i[offset_i.len() - 1]; + inplace_xor(&mut offset_i[0], &self.ll[ntz(i)]); + for j in 1..p_i.len() { + offset_i[j] = offset_i[j - 1]; + inplace_xor(&mut offset_i[j], &self.ll[ntz(i + j)]); + } + + // c_i = offset_i xor ENCIPHER(K, p_i xor offset_i) + for j in 0..p_i.len() { + inplace_xor(p_i[j], &offset_i[j]); + self.cipher.encrypt_block(p_i[j]); + inplace_xor(p_i[j], &offset_i[j]) } + + i += WIDTH; } let processed_bytes = (buffer.len() / (WIDTH * 16)) * (WIDTH * 16); @@ -367,49 +357,39 @@ where (processed_bytes, offset_i[offset_i.len() - 1], checksum_i) } - /// Decrypts plaintext in groups of WIDTH. + /// Decrypts plaintext in groups of two. /// /// Adapted from https://www.cs.ucdavis.edu/~rogaway/ocb/news/code/ocb.c fn wide_decrypt(&self, nonce: &Nonce, buffer: &mut [u8]) -> (usize, Block, Block) { - #[cfg(not(target_feature = "avx512vaes"))] const WIDTH: usize = 2; - #[cfg(not(target_feature = "avx512vaes"))] let split_into_blocks = crate::util::split_into_two_blocks; - #[cfg(target_feature = "avx512vaes")] - const WIDTH: usize = 4; - #[cfg(target_feature = "avx512vaes")] - let split_into_blocks = crate::util::split_into_four_blocks; - let mut i = 1; let mut offset_i = [Block::default(); WIDTH]; offset_i[offset_i.len() - 1] = initial_offset(&self.cipher, nonce, TagSize::to_u32()); let mut checksum_i = Block::default(); - #[allow(unsafe_code)] - unsafe { - for wide_blocks in buffer.chunks_exact_mut(16 * WIDTH) { - let c_i = split_into_blocks(wide_blocks); - - // offset_i = offset_{i-1} xor L_{ntz(i)} - offset_i[0] = offset_i[offset_i.len() - 1]; - inplace_xor(&mut offset_i[0], &self.ll[ntz(i)]); - for j in 1..c_i.len() { - offset_i[j] = offset_i[j - 1]; - inplace_xor(&mut offset_i[j], &self.ll[ntz(i + j)]); - } - - // p_i = offset_i xor DECIPHER(K, c_i xor offset_i) - // checksum_i = checksum_{i-1} xor p_i - for j in 0..c_i.len() { - inplace_xor(c_i[j], &offset_i[j]); - self.cipher.decrypt_block(c_i[j]); - inplace_xor(c_i[j], &offset_i[j]); - inplace_xor(&mut checksum_i, c_i[j]); - } - - i += WIDTH; + for wide_blocks in buffer.chunks_exact_mut(16 * WIDTH) { + let c_i = split_into_blocks(wide_blocks); + + // offset_i = offset_{i-1} xor L_{ntz(i)} + offset_i[0] = offset_i[offset_i.len() - 1]; + inplace_xor(&mut offset_i[0], &self.ll[ntz(i)]); + for j in 1..c_i.len() { + offset_i[j] = offset_i[j - 1]; + inplace_xor(&mut offset_i[j], &self.ll[ntz(i + j)]); } + + // p_i = offset_i xor DECIPHER(K, c_i xor offset_i) + // checksum_i = checksum_{i-1} xor p_i + for j in 0..c_i.len() { + inplace_xor(c_i[j], &offset_i[j]); + self.cipher.decrypt_block(c_i[j]); + inplace_xor(c_i[j], &offset_i[j]); + inplace_xor(&mut checksum_i, c_i[j]); + } + + i += WIDTH; } let processed_bytes = (buffer.len() / (WIDTH * 16)) * (WIDTH * 16); diff --git a/ocb3/src/util.rs b/ocb3/src/util.rs index a24c237b..fca7fc7a 100644 --- a/ocb3/src/util.rs +++ b/ocb3/src/util.rs @@ -1,7 +1,3 @@ -#![allow(dead_code)] - -use core::slice::from_raw_parts_mut; - use aead::generic_array::{ArrayLength, GenericArray}; use aes::Block; @@ -41,36 +37,8 @@ pub(crate) fn ntz(n: usize) -> usize { const BLOCK_SIZE: usize = 16; -/// Adapted from https://doc.rust-lang.org/std/primitive.slice.html#method.split_at_mut_unchecked -/// -/// SAFETY: Assumes that `two_blocks` is exactly two blocks. #[inline] -#[allow(unsafe_code)] -pub(crate) unsafe fn split_into_two_blocks(two_blocks: &mut [u8]) -> [&mut Block; 2] { - let ptr = two_blocks.as_mut_ptr(); - - unsafe { - [ - from_raw_parts_mut(ptr, BLOCK_SIZE).into(), - from_raw_parts_mut(ptr.add(BLOCK_SIZE), BLOCK_SIZE).into(), - ] - } -} - -/// Adapted from https://doc.rust-lang.org/std/primitive.slice.html#method.split_at_mut_unchecked -/// -/// SAFETY: Assumes that `four_blocks` is exactly four blocks. -#[inline] -#[allow(unsafe_code)] -pub(crate) unsafe fn split_into_four_blocks(four_blocks: &mut [u8]) -> [&mut Block; 4] { - let ptr = four_blocks.as_mut_ptr(); - - unsafe { - [ - from_raw_parts_mut(ptr, BLOCK_SIZE).into(), - from_raw_parts_mut(ptr.add(BLOCK_SIZE), BLOCK_SIZE).into(), - from_raw_parts_mut(ptr.add(2 * BLOCK_SIZE), BLOCK_SIZE).into(), - from_raw_parts_mut(ptr.add(3 * BLOCK_SIZE), BLOCK_SIZE).into(), - ] - } +pub(crate) fn split_into_two_blocks(two_blocks: &mut [u8]) -> [&mut Block; 2] { + let (b0, b1) = two_blocks.split_at_mut(BLOCK_SIZE); + [b0.into(), b1.into()] }