From 2676e58463a7bce248c8adc152bb925d7a21e80b Mon Sep 17 00:00:00 2001 From: DongHun Kwak Date: Wed, 22 Mar 2023 10:29:49 +0900 Subject: [PATCH] Import aes 0.8.2 --- .cargo_vcs_info.json | 6 + CHANGELOG.md | 133 ++++ Cargo.toml | 70 +++ Cargo.toml.orig | 38 ++ LICENSE-APACHE | 201 ++++++ LICENSE-MIT | 25 + README.md | 95 +++ benches/mod.rs | 62 ++ src/armv8.rs | 342 +++++++++++ src/armv8/encdec.rs | 158 +++++ src/armv8/expand.rs | 77 +++ src/armv8/hazmat.rs | 104 ++++ src/armv8/test_expand.rs | 130 ++++ src/autodetect.rs | 430 +++++++++++++ src/hazmat.rs | 159 +++++ src/lib.rs | 232 +++++++ src/ni.rs | 361 +++++++++++ src/ni/aes128.rs | 145 +++++ src/ni/aes192.rs | 197 ++++++ src/ni/aes256.rs | 196 ++++++ src/ni/hazmat.rs | 80 +++ src/ni/test_expand.rs | 275 +++++++++ src/ni/utils.rs | 92 +++ src/soft.rs | 342 +++++++++++ src/soft/fixslice32.rs | 1479 ++++++++++++++++++++++++++++++++++++++++++++ src/soft/fixslice64.rs | 1534 ++++++++++++++++++++++++++++++++++++++++++++++ tests/data/aes128.blb | Bin 0 -> 27418 bytes tests/data/aes192.blb | Bin 0 -> 41322 bytes tests/data/aes256.blb | Bin 0 -> 49594 bytes tests/hazmat.rs | 155 +++++ tests/mod.rs | 6 + 31 files changed, 7124 insertions(+) create mode 100644 .cargo_vcs_info.json create mode 100644 CHANGELOG.md create mode 100644 Cargo.toml create mode 100644 Cargo.toml.orig create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT create mode 100644 README.md create mode 100644 benches/mod.rs create mode 100644 src/armv8.rs create mode 100644 src/armv8/encdec.rs create mode 100644 src/armv8/expand.rs create mode 100644 src/armv8/hazmat.rs create mode 100644 src/armv8/test_expand.rs create mode 100644 src/autodetect.rs create mode 100644 src/hazmat.rs create mode 100644 src/lib.rs create mode 100644 src/ni.rs create mode 100644 src/ni/aes128.rs create mode 100644 src/ni/aes192.rs create mode 100644 src/ni/aes256.rs create mode 100644 src/ni/hazmat.rs create mode 100644 src/ni/test_expand.rs create mode 100644 src/ni/utils.rs create mode 100644 src/soft.rs create mode 100644 src/soft/fixslice32.rs create mode 100644 src/soft/fixslice64.rs create mode 100644 tests/data/aes128.blb create mode 100644 tests/data/aes192.blb create mode 100644 tests/data/aes256.blb create mode 100644 tests/hazmat.rs create mode 100644 tests/mod.rs diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..4210283 --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "48242cc25cf9fa8af87acef6deb5b5edde1265f9" + }, + "path_in_vcs": "aes" +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..cdf781d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,133 @@ +# 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.8.2 (2022-10-27) +### Fixed +- Crate documentation around configuration flags ([#343]) + +[#343]: https://github.com/RustCrypto/block-ciphers/pull/343 + +## 0.8.1 (2022-02-17) +### Fixed +- Minimal versions build ([#303]) + +[#303]: https://github.com/RustCrypto/block-ciphers/pull/303 + +## 0.8.0 (2022-02-10) +### Changed +- Bump `cipher` dependency to v0.4 ([#284]) + +### Added +- Encrypt-only and decrypt-only cipher types ([#284]) + +[#284]: https://github.com/RustCrypto/block-ciphers/pull/284 + +## 0.7.5 (2021-08-26) +### Changed +- Bump `ctr` dependency to v0.8 ([#275]) +- Use the `aes` target feature instead of `crypto` on ARMv8 ([#279]) +- Use `core::arch::aarch64::vst1q_u8` intrinsic on `armv8` ([#280]) +- Bump `cpufeatures` dependency to v0.2 ([#281]) + +[#275]: https://github.com/RustCrypto/block-ciphers/pull/275 +[#279]: https://github.com/RustCrypto/block-ciphers/pull/279 +[#280]: https://github.com/RustCrypto/block-ciphers/pull/280 +[#281]: https://github.com/RustCrypto/block-ciphers/pull/281 + +## 0.7.4 (2021-06-01) +### Added +- Soft `hazmat` backend ([#267], [#268]) +- Parallel `hazmat` APIs ([#269]) + +[#267]: https://github.com/RustCrypto/block-ciphers/pull/267 +[#268]: https://github.com/RustCrypto/block-ciphers/pull/268 +[#269]: https://github.com/RustCrypto/block-ciphers/pull/269 + +## 0.7.3 (2021-05-26) +### Added +- `hazmat` feature/module providing round function access ([#257], [#259], [#260]) +- `BLOCK_SIZE` constant ([#263]) + +[#257]: https://github.com/RustCrypto/block-ciphers/pull/257 +[#259]: https://github.com/RustCrypto/block-ciphers/pull/259 +[#260]: https://github.com/RustCrypto/block-ciphers/pull/260 +[#263]: https://github.com/RustCrypto/block-ciphers/pull/263 + +## 0.7.2 (2021-05-17) +### Added +- Nightly-only ARMv8 intrinsics support gated under the `armv8` feature ([#250]) + +[#250]: https://github.com/RustCrypto/block-ciphers/pull/250 + +## 0.7.1 (2021-05-09) +### Fixed +- Restore `fixslice64.rs` ([#247]) + +[#247]: https://github.com/RustCrypto/block-ciphers/pull/247 + +## 0.7.0 (2021-04-29) +### Added +- Auto-detection support for AES-NI; MSRV 1.49+ ([#208], [#214], [#215], [#216]) +- `ctr` feature providing SIMD accelerated AES-CTR ([#200]) + +### Changed +- Unify the `aes`, `aesni`, `aes-ctr`, and `aes-soft` crates ([#200]) +- Use `cfg-if` crate ([#203]) +- Rename `semi_fixslice` feature to `compact` ([#204]) +- Refactor NI backend ([#224], [#225]) +- Bump `cipher` crate dependency to v0.3 ([#235]) +- Bump `ctr` crate dependency to v0.7 ([#237]) + +[#200]: https://github.com/RustCrypto/block-ciphers/pull/200 +[#203]: https://github.com/RustCrypto/block-ciphers/pull/203 +[#204]: https://github.com/RustCrypto/block-ciphers/pull/204 +[#208]: https://github.com/RustCrypto/block-ciphers/pull/208 +[#214]: https://github.com/RustCrypto/block-ciphers/pull/214 +[#215]: https://github.com/RustCrypto/block-ciphers/pull/215 +[#216]: https://github.com/RustCrypto/block-ciphers/pull/216 +[#224]: https://github.com/RustCrypto/block-ciphers/pull/224 +[#225]: https://github.com/RustCrypto/block-ciphers/pull/225 +[#235]: https://github.com/RustCrypto/block-ciphers/pull/235 +[#237]: https://github.com/RustCrypto/block-ciphers/pull/237 + +## 0.6.0 (2020-10-16) +### Changed +- Replace `block-cipher`/`stream-cipher` with `cipher` crate ([#167]) + +[#167]: https://github.com/RustCrypto/block-ciphers/pull/167 + +## 0.5.1 (2020-08-25) +### Changed +- Bump `aesni` dependency to v0.9 ([#158]) + +[#158]: https://github.com/RustCrypto/block-ciphers/pull/158 + +## 0.5.0 (2020-08-07) +### Changed +- Bump `block-cipher` dependency to v0.8 ([#138]) +- Bump `opaque-debug` dependency to v0.3 ([#140]) + +[#138]: https://github.com/RustCrypto/block-ciphers/pull/138 +[#140]: https://github.com/RustCrypto/block-ciphers/pull/140 + +## 0.4.0 (2020-06-05) +### Changed +- Bump `block-cipher` dependency to v0.7 ([#86], [#122]) +- Update to Rust 2018 edition ([#86]) + +[#121]: https://github.com/RustCrypto/block-ciphers/pull/122 +[#86]: https://github.com/RustCrypto/block-ciphers/pull/86 + +## 0.3.2 (2018-11-01) + +## 0.3.1 (2018-10-04) + +## 0.3.0 (2018-10-03) + +## 0.2.0 (2018-07-27) + +## 0.1.0 (2018-06-22) diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e03d07a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,70 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +rust-version = "1.56" +name = "aes" +version = "0.8.2" +authors = ["RustCrypto Developers"] +description = "Pure Rust implementation of the Advanced Encryption Standard (a.k.a. Rijndael)" +documentation = "https://docs.rs/aes" +readme = "README.md" +keywords = [ + "crypto", + "aes", + "rijndael", + "block-cipher", +] +categories = [ + "cryptography", + "no-std", +] +license = "MIT OR Apache-2.0" +repository = "https://github.com/RustCrypto/block-ciphers" +resolver = "1" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = [ + "--cfg", + "docsrs", +] + +[dependencies.cfg-if] +version = "1" + +[dependencies.cipher] +version = "0.4.2" + +[dev-dependencies.cipher] +version = "0.4.2" +features = ["dev"] + +[dev-dependencies.hex-literal] +version = "0.3" + +[features] +hazmat = [] + +[target."cfg(all(aes_armv8, target_arch = \"aarch64\"))".dependencies.zeroize] +version = "1.5.6" +features = ["aarch64"] +optional = true +default_features = false + +[target."cfg(any(target_arch = \"aarch64\", target_arch = \"x86_64\", target_arch = \"x86\"))".dependencies.cpufeatures] +version = "0.2" + +[target."cfg(not(all(aes_armv8, target_arch = \"aarch64\")))".dependencies.zeroize] +version = "1.5.6" +optional = true +default_features = false diff --git a/Cargo.toml.orig b/Cargo.toml.orig new file mode 100644 index 0000000..ebf0415 --- /dev/null +++ b/Cargo.toml.orig @@ -0,0 +1,38 @@ +[package] +name = "aes" +version = "0.8.2" +description = "Pure Rust implementation of the Advanced Encryption Standard (a.k.a. Rijndael)" +authors = ["RustCrypto Developers"] +license = "MIT OR Apache-2.0" +edition = "2021" +rust-version = "1.56" +readme = "README.md" +documentation = "https://docs.rs/aes" +repository = "https://github.com/RustCrypto/block-ciphers" +keywords = ["crypto", "aes", "rijndael", "block-cipher"] +categories = ["cryptography", "no-std"] + +[dependencies] +cfg-if = "1" +cipher = "0.4.2" + +[target.'cfg(any(target_arch = "aarch64", target_arch = "x86_64", target_arch = "x86"))'.dependencies] +cpufeatures = "0.2" + +[target.'cfg(not(all(aes_armv8, target_arch = "aarch64")))'.dependencies] +zeroize = { version = "1.5.6", optional = true, default_features = false } + +# TODO(tarcieri): unconditionally enable `aarch64` feature when MSRV is 1.59 +[target.'cfg(all(aes_armv8, target_arch = "aarch64"))'.dependencies] +zeroize = { version = "1.5.6", optional = true, default_features = false, features = ["aarch64"] } + +[dev-dependencies] +cipher = { version = "0.4.2", features = ["dev"] } +hex-literal = "0.3" + +[features] +hazmat = [] # Expose cryptographically hazardous APIs + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..78173fa --- /dev/null +++ b/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/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..f5b157a --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2018 Artyom Pavlov + +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/README.md b/README.md new file mode 100644 index 0000000..ce95a3a --- /dev/null +++ b/README.md @@ -0,0 +1,95 @@ +# RustCrypto: Advanced Encryption Standard (AES) + +[![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] +[![Downloads][downloads-image]][crate-link] +[![HAZMAT][hazmat-image]][hazmat-link] + +Pure Rust implementation of the [Advanced Encryption Standard (AES)][1]. + +This crate implements the low-level AES block function, and is intended +for use for implementing higher-level constructions *only*. It is NOT +intended for direct use in applications. + +[Documentation][docs-link] + + + +## Security + +### ⚠️ Warning: [Hazmat!][hazmat-link] + +This crate does not ensure ciphertexts are authentic (i.e. by using a MAC to +verify ciphertext integrity), which can lead to serious vulnerabilities +if used incorrectly! + +To avoid this, use an [AEAD][2] mode based on AES, such as [AES-GCM][3] or [AES-GCM-SIV][4]. +See the [RustCrypto/AEADs][5] repository for more information. + +USE AT YOUR OWN RISK! + +### Notes + +This crate has received one [security audit by NCC Group][6], with no significant +findings. We would like to thank [MobileCoin][7] for funding the audit. + +All implementations contained in the crate are designed to execute in constant +time, either by relying on hardware intrinsics (i.e. AES-NI on x86/x86_64), or +using a portable implementation based on bitslicing. + +## Minimum Supported Rust Version + +Rust **1.56** or higher. + +Minimum supported Rust version can be changed in future releases, but it will +be done with a minor version bump. + +## SemVer Policy + +- All on-by-default features of this library are covered by SemVer +- MSRV is considered exempt from SemVer as noted above + +## License + +Licensed under either of: + + * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) + * [MIT license](http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[//]: # (badges) + +[crate-image]: https://img.shields.io/crates/v/aes.svg +[crate-link]: https://crates.io/crates/aes +[docs-image]: https://docs.rs/aes/badge.svg +[docs-link]: https://docs.rs/aes/ +[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/260039-block-ciphers +[build-image]: https://github.com/RustCrypto/block-ciphers/workflows/aes/badge.svg?branch=master&event=push +[build-link]: https://github.com/RustCrypto/block-ciphers/actions?query=workflow%3Aaes +[downloads-image]: https://img.shields.io/crates/d/aes.svg +[hazmat-image]: https://img.shields.io/badge/crypto-hazmat%E2%9A%A0-red.svg +[hazmat-link]: https://github.com/RustCrypto/meta/blob/master/HAZMAT.md + +[//]: # (general links) + +[1]: https://en.wikipedia.org/wiki/Advanced_Encryption_Standard +[2]: https://en.wikipedia.org/wiki/Authenticated_encryption +[3]: https://github.com/RustCrypto/AEADs/tree/master/aes-gcm +[4]: https://github.com/RustCrypto/AEADs/tree/master/aes-gcm-siv +[5]: https://github.com/RustCrypto/AEADs +[6]: https://research.nccgroup.com/2020/02/26/public-report-rustcrypto-aes-gcm-and-chacha20poly1305-implementation-review/ +[7]: https://www.mobilecoin.com/ diff --git a/benches/mod.rs b/benches/mod.rs new file mode 100644 index 0000000..579b073 --- /dev/null +++ b/benches/mod.rs @@ -0,0 +1,62 @@ +#![feature(test)] +extern crate test; + +use cipher::{block_decryptor_bench, block_encryptor_bench, KeyInit}; + +block_encryptor_bench!( + Key: aes::Aes128, + aes128_encrypt_block, + aes128_encrypt_blocks, +); +block_decryptor_bench!( + Key: aes::Aes128, + aes128_decrypt_block, + aes128_decrypt_blocks, +); +block_encryptor_bench!( + Key: aes::Aes192, + aes192_encrypt_block, + aes192_encrypt_blocks, +); +block_decryptor_bench!( + Key: aes::Aes192, + aes192_decrypt_block, + aes192_decrypt_blocks, +); +block_encryptor_bench!( + Key: aes::Aes256, + aes256_encrypt_block, + aes256_encrypt_blocks, +); +block_decryptor_bench!( + Key: aes::Aes256, + aes256_decrypt_block, + aes256_decrypt_blocks, +); + +#[bench] +fn aes128_new(bh: &mut test::Bencher) { + bh.iter(|| { + let key = test::black_box(Default::default()); + let cipher = aes::Aes128::new(&key); + test::black_box(&cipher); + }); +} + +#[bench] +fn aes192_new(bh: &mut test::Bencher) { + bh.iter(|| { + let key = test::black_box(Default::default()); + let cipher = aes::Aes192::new(&key); + test::black_box(&cipher); + }); +} + +#[bench] +fn aes256_new(bh: &mut test::Bencher) { + bh.iter(|| { + let key = test::black_box(Default::default()); + let cipher = aes::Aes256::new(&key); + test::black_box(&cipher); + }); +} diff --git a/src/armv8.rs b/src/armv8.rs new file mode 100644 index 0000000..4ecc471 --- /dev/null +++ b/src/armv8.rs @@ -0,0 +1,342 @@ +//! AES block cipher implementation using the ARMv8 Cryptography Extensions. +//! +//! Based on this C intrinsics implementation: +//! +//! +//! Original C written and placed in public domain by Jeffrey Walton. +//! Based on code from ARM, and by Johannes Schneiders, Skip Hovsmith and +//! Barry O'Rourke for the mbedTLS project. + +#![allow(clippy::needless_range_loop)] + +#[cfg(feature = "hazmat")] +pub(crate) mod hazmat; + +mod encdec; +mod expand; +#[cfg(test)] +mod test_expand; + +use self::{ + encdec::{decrypt1, decrypt8, encrypt1, encrypt8}, + expand::{expand_key, inv_expanded_keys}, +}; +use crate::{Block, Block8}; +use cipher::{ + consts::{U16, U24, U32, U8}, + inout::InOut, + AlgorithmName, BlockBackend, BlockCipher, BlockClosure, BlockDecrypt, BlockEncrypt, + BlockSizeUser, Key, KeyInit, KeySizeUser, ParBlocksSizeUser, +}; +use core::arch::aarch64::*; +use core::fmt; + +macro_rules! define_aes_impl { + ( + $name:ident, + $name_enc:ident, + $name_dec:ident, + $name_back_enc:ident, + $name_back_dec:ident, + $key_size:ty, + $rounds:tt, + $doc:expr $(,)? + ) => { + #[doc=$doc] + #[doc = "block cipher"] + #[derive(Clone)] + pub struct $name { + encrypt: $name_enc, + decrypt: $name_dec, + } + + impl $name { + #[inline(always)] + pub(crate) fn get_enc_backend(&self) -> $name_back_enc<'_> { + self.encrypt.get_enc_backend() + } + + #[inline(always)] + pub(crate) fn get_dec_backend(&self) -> $name_back_dec<'_> { + self.decrypt.get_dec_backend() + } + } + + impl BlockCipher for $name {} + + impl KeySizeUser for $name { + type KeySize = $key_size; + } + + impl KeyInit for $name { + #[inline] + fn new(key: &Key) -> Self { + let encrypt = $name_enc::new(key); + let decrypt = $name_dec::from(&encrypt); + Self { encrypt, decrypt } + } + } + + impl From<$name_enc> for $name { + #[inline] + fn from(encrypt: $name_enc) -> $name { + let decrypt = (&encrypt).into(); + Self { encrypt, decrypt } + } + } + + impl From<&$name_enc> for $name { + #[inline] + fn from(encrypt: &$name_enc) -> $name { + let decrypt = encrypt.into(); + let encrypt = encrypt.clone(); + Self { encrypt, decrypt } + } + } + + impl BlockSizeUser for $name { + type BlockSize = U16; + } + + impl BlockEncrypt for $name { + fn encrypt_with_backend(&self, f: impl BlockClosure) { + self.encrypt.encrypt_with_backend(f) + } + } + + impl BlockDecrypt for $name { + fn decrypt_with_backend(&self, f: impl BlockClosure) { + self.decrypt.decrypt_with_backend(f) + } + } + + impl fmt::Debug for $name { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str(concat!(stringify!($name), " { .. }")) + } + } + + impl AlgorithmName for $name { + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(stringify!($name)) + } + } + + #[cfg(feature = "zeroize")] + impl zeroize::ZeroizeOnDrop for $name {} + + #[doc=$doc] + #[doc = "block cipher (encrypt-only)"] + #[derive(Clone)] + pub struct $name_enc { + round_keys: [uint8x16_t; $rounds], + } + + impl $name_enc { + #[inline(always)] + pub(crate) fn get_enc_backend(&self) -> $name_back_enc<'_> { + $name_back_enc(self) + } + } + + impl BlockCipher for $name_enc {} + + impl KeySizeUser for $name_enc { + type KeySize = $key_size; + } + + impl KeyInit for $name_enc { + fn new(key: &Key) -> Self { + Self { + round_keys: expand_key(key.as_ref()), + } + } + } + + impl BlockSizeUser for $name_enc { + type BlockSize = U16; + } + + impl BlockEncrypt for $name_enc { + fn encrypt_with_backend(&self, f: impl BlockClosure) { + f.call(&mut self.get_enc_backend()) + } + } + + impl fmt::Debug for $name_enc { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str(concat!(stringify!($name_enc), " { .. }")) + } + } + + impl AlgorithmName for $name_enc { + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(stringify!($name_enc)) + } + } + + impl Drop for $name_enc { + #[inline] + fn drop(&mut self) { + #[cfg(feature = "zeroize")] + zeroize::Zeroize::zeroize(&mut self.round_keys); + } + } + + #[cfg(feature = "zeroize")] + impl zeroize::ZeroizeOnDrop for $name_enc {} + + #[doc=$doc] + #[doc = "block cipher (decrypt-only)"] + #[derive(Clone)] + pub struct $name_dec { + round_keys: [uint8x16_t; $rounds], + } + + impl $name_dec { + #[inline(always)] + pub(crate) fn get_dec_backend(&self) -> $name_back_dec<'_> { + $name_back_dec(self) + } + } + + impl BlockCipher for $name_dec {} + + impl KeySizeUser for $name_dec { + type KeySize = $key_size; + } + + impl KeyInit for $name_dec { + fn new(key: &Key) -> Self { + $name_enc::new(key).into() + } + } + + impl From<$name_enc> for $name_dec { + #[inline] + fn from(enc: $name_enc) -> $name_dec { + Self::from(&enc) + } + } + + impl From<&$name_enc> for $name_dec { + fn from(enc: &$name_enc) -> $name_dec { + let mut round_keys = enc.round_keys; + inv_expanded_keys(&mut round_keys); + Self { round_keys } + } + } + + impl BlockSizeUser for $name_dec { + type BlockSize = U16; + } + + impl BlockDecrypt for $name_dec { + fn decrypt_with_backend(&self, f: impl BlockClosure) { + f.call(&mut self.get_dec_backend()); + } + } + + impl fmt::Debug for $name_dec { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str(concat!(stringify!($name_dec), " { .. }")) + } + } + + impl AlgorithmName for $name_dec { + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(stringify!($name_dec)) + } + } + + impl Drop for $name_dec { + #[inline] + fn drop(&mut self) { + #[cfg(feature = "zeroize")] + zeroize::Zeroize::zeroize(&mut self.round_keys); + } + } + + #[cfg(feature = "zeroize")] + impl zeroize::ZeroizeOnDrop for $name_dec {} + + pub(crate) struct $name_back_enc<'a>(&'a $name_enc); + + impl<'a> BlockSizeUser for $name_back_enc<'a> { + type BlockSize = U16; + } + + impl<'a> ParBlocksSizeUser for $name_back_enc<'a> { + type ParBlocksSize = U8; + } + + impl<'a> BlockBackend for $name_back_enc<'a> { + #[inline(always)] + fn proc_block(&mut self, block: InOut<'_, '_, Block>) { + unsafe { + encrypt1(&self.0.round_keys, block); + } + } + + #[inline(always)] + fn proc_par_blocks(&mut self, blocks: InOut<'_, '_, Block8>) { + unsafe { encrypt8(&self.0.round_keys, blocks) } + } + } + + pub(crate) struct $name_back_dec<'a>(&'a $name_dec); + + impl<'a> BlockSizeUser for $name_back_dec<'a> { + type BlockSize = U16; + } + + impl<'a> ParBlocksSizeUser for $name_back_dec<'a> { + type ParBlocksSize = U8; + } + + impl<'a> BlockBackend for $name_back_dec<'a> { + #[inline(always)] + fn proc_block(&mut self, block: InOut<'_, '_, Block>) { + unsafe { + decrypt1(&self.0.round_keys, block); + } + } + + #[inline(always)] + fn proc_par_blocks(&mut self, blocks: InOut<'_, '_, Block8>) { + unsafe { decrypt8(&self.0.round_keys, blocks) } + } + } + }; +} + +define_aes_impl!( + Aes128, + Aes128Enc, + Aes128Dec, + Aes128BackEnc, + Aes128BackDec, + U16, + 11, + "AES-128", +); +define_aes_impl!( + Aes192, + Aes192Enc, + Aes192Dec, + Aes192BackEnc, + Aes192BackDec, + U24, + 13, + "AES-192", +); +define_aes_impl!( + Aes256, + Aes256Enc, + Aes256Dec, + Aes256BackEnc, + Aes256BackDec, + U32, + 15, + "AES-256", +); diff --git a/src/armv8/encdec.rs b/src/armv8/encdec.rs new file mode 100644 index 0000000..ecf7d5c --- /dev/null +++ b/src/armv8/encdec.rs @@ -0,0 +1,158 @@ +//! AES encryption support + +use crate::{Block, Block8}; +use cipher::inout::InOut; +use core::arch::aarch64::*; + +/// Perform AES encryption using the given expanded keys. +#[target_feature(enable = "aes")] +#[target_feature(enable = "neon")] +pub(super) unsafe fn encrypt1( + expanded_keys: &[uint8x16_t; N], + block: InOut<'_, '_, Block>, +) { + let rounds = N - 1; + assert!(rounds == 10 || rounds == 12 || rounds == 14); + + let (in_ptr, out_ptr) = block.into_raw(); + + let mut state = vld1q_u8(in_ptr as *const u8); + + for k in expanded_keys.iter().take(rounds - 1) { + // AES single round encryption + state = vaeseq_u8(state, *k); + + // AES mix columns + state = vaesmcq_u8(state); + } + + // AES single round encryption + state = vaeseq_u8(state, expanded_keys[rounds - 1]); + + // Final add (bitwise XOR) + state = veorq_u8(state, expanded_keys[rounds]); + + vst1q_u8(out_ptr as *mut u8, state); +} + +/// Perform parallel AES encryption 8-blocks-at-a-time using the given expanded keys. +#[target_feature(enable = "aes")] +#[target_feature(enable = "neon")] +pub(super) unsafe fn encrypt8( + expanded_keys: &[uint8x16_t; N], + blocks: InOut<'_, '_, Block8>, +) { + let rounds = N - 1; + assert!(rounds == 10 || rounds == 12 || rounds == 14); + + let (in_ptr, out_ptr) = blocks.into_raw(); + let in_ptr = in_ptr as *const Block; + let out_ptr = out_ptr as *const Block; + + let mut state = [ + vld1q_u8(in_ptr.add(0) as *const u8), + vld1q_u8(in_ptr.add(1) as *const u8), + vld1q_u8(in_ptr.add(2) as *const u8), + vld1q_u8(in_ptr.add(3) as *const u8), + vld1q_u8(in_ptr.add(4) as *const u8), + vld1q_u8(in_ptr.add(5) as *const u8), + vld1q_u8(in_ptr.add(6) as *const u8), + vld1q_u8(in_ptr.add(7) as *const u8), + ]; + + for k in expanded_keys.iter().take(rounds - 1) { + for i in 0..8 { + // AES single round encryption + state[i] = vaeseq_u8(state[i], *k); + + // AES mix columns + state[i] = vaesmcq_u8(state[i]); + } + } + + for i in 0..8 { + // AES single round encryption + state[i] = vaeseq_u8(state[i], expanded_keys[rounds - 1]); + + // Final add (bitwise XOR) + state[i] = veorq_u8(state[i], expanded_keys[rounds]); + + vst1q_u8(out_ptr.add(i) as *mut u8, state[i]); + } +} + +/// Perform AES decryption using the given expanded keys. +#[target_feature(enable = "aes")] +#[target_feature(enable = "neon")] +pub(super) unsafe fn decrypt1( + expanded_keys: &[uint8x16_t; N], + block: InOut<'_, '_, Block>, +) { + let rounds = N - 1; + assert!(rounds == 10 || rounds == 12 || rounds == 14); + + let (in_ptr, out_ptr) = block.into_raw(); + let mut state = vld1q_u8(in_ptr as *const u8); + + for k in expanded_keys.iter().take(rounds - 1) { + // AES single round decryption + state = vaesdq_u8(state, *k); + + // AES inverse mix columns + state = vaesimcq_u8(state); + } + + // AES single round decryption + state = vaesdq_u8(state, expanded_keys[rounds - 1]); + + // Final add (bitwise XOR) + state = veorq_u8(state, expanded_keys[rounds]); + + vst1q_u8(out_ptr as *mut u8, state); +} + +/// Perform parallel AES decryption 8-blocks-at-a-time using the given expanded keys. +#[target_feature(enable = "aes")] +#[target_feature(enable = "neon")] +pub(super) unsafe fn decrypt8( + expanded_keys: &[uint8x16_t; N], + blocks: InOut<'_, '_, Block8>, +) { + let rounds = N - 1; + assert!(rounds == 10 || rounds == 12 || rounds == 14); + + let (in_ptr, out_ptr) = blocks.into_raw(); + let in_ptr = in_ptr as *const Block; + let out_ptr = out_ptr as *const Block; + + let mut state = [ + vld1q_u8(in_ptr.add(0) as *const u8), + vld1q_u8(in_ptr.add(1) as *const u8), + vld1q_u8(in_ptr.add(2) as *const u8), + vld1q_u8(in_ptr.add(3) as *const u8), + vld1q_u8(in_ptr.add(4) as *const u8), + vld1q_u8(in_ptr.add(5) as *const u8), + vld1q_u8(in_ptr.add(6) as *const u8), + vld1q_u8(in_ptr.add(7) as *const u8), + ]; + + for k in expanded_keys.iter().take(rounds - 1) { + for i in 0..8 { + // AES single round decryption + state[i] = vaesdq_u8(state[i], *k); + + // AES inverse mix columns + state[i] = vaesimcq_u8(state[i]); + } + } + + for i in 0..8 { + // AES single round decryption + state[i] = vaesdq_u8(state[i], expanded_keys[rounds - 1]); + + // Final add (bitwise XOR) + state[i] = veorq_u8(state[i], expanded_keys[rounds]); + + vst1q_u8(out_ptr.add(i) as *mut u8, state[i]); + } +} diff --git a/src/armv8/expand.rs b/src/armv8/expand.rs new file mode 100644 index 0000000..8e5cf88 --- /dev/null +++ b/src/armv8/expand.rs @@ -0,0 +1,77 @@ +//! AES key expansion support. + +use core::{arch::aarch64::*, mem, slice}; + +/// There are 4 AES words in a block. +const BLOCK_WORDS: usize = 4; + +/// The AES (nee Rijndael) notion of a word is always 32-bits, or 4-bytes. +const WORD_SIZE: usize = 4; + +/// AES round constants. +const ROUND_CONSTS: [u32; 10] = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36]; + +/// AES key expansion +// TODO(tarcieri): big endian support? +#[inline] +pub(super) fn expand_key(key: &[u8; L]) -> [uint8x16_t; N] { + assert!((L == 16 && N == 11) || (L == 24 && N == 13) || (L == 32 && N == 15)); + + let mut expanded_keys: [uint8x16_t; N] = unsafe { mem::zeroed() }; + + // TODO(tarcieri): construct expanded keys using `vreinterpretq_u8_u32` + let ek_words = unsafe { + slice::from_raw_parts_mut(expanded_keys.as_mut_ptr() as *mut u32, N * BLOCK_WORDS) + }; + + for (i, chunk) in key.chunks_exact(WORD_SIZE).enumerate() { + ek_words[i] = u32::from_ne_bytes(chunk.try_into().unwrap()); + } + + // From "The Rijndael Block Cipher" Section 4.1: + // > The number of columns of the Cipher Key is denoted by `Nk` and is + // > equal to the key length divided by 32 [bits]. + let nk = L / WORD_SIZE; + + for i in nk..(N * BLOCK_WORDS) { + let mut word = ek_words[i - 1]; + + if i % nk == 0 { + word = sub_word(word).rotate_right(8) ^ ROUND_CONSTS[i / nk - 1]; + } else if nk > 6 && i % nk == 4 { + word = sub_word(word) + } + + ek_words[i] = ek_words[i - nk] ^ word; + } + + expanded_keys +} + +/// Compute inverse expanded keys (for decryption). +/// +/// This is the reverse of the encryption keys, with the Inverse Mix Columns +/// operation applied to all but the first and last expanded key. +#[inline] +pub(super) fn inv_expanded_keys(expanded_keys: &mut [uint8x16_t; N]) { + assert!(N == 11 || N == 13 || N == 15); + + for ek in expanded_keys.iter_mut().take(N - 1).skip(1) { + unsafe { *ek = vaesimcq_u8(*ek) } + } + + expanded_keys.reverse(); +} + +/// Sub bytes for a single AES word: used for key expansion. +#[inline(always)] +fn sub_word(input: u32) -> u32 { + unsafe { + let input = vreinterpretq_u8_u32(vdupq_n_u32(input)); + + // AES single round encryption (with a "round" key of all zeros) + let sub_input = vaeseq_u8(input, vdupq_n_u8(0)); + + vgetq_lane_u32(vreinterpretq_u32_u8(sub_input), 0) + } +} diff --git a/src/armv8/hazmat.rs b/src/armv8/hazmat.rs new file mode 100644 index 0000000..f094243 --- /dev/null +++ b/src/armv8/hazmat.rs @@ -0,0 +1,104 @@ +//! Low-level "hazmat" AES functions: ARMv8 Cryptography Extensions support. +//! +//! Note: this isn't actually used in the `Aes128`/`Aes192`/`Aes256` +//! implementations in this crate, but instead provides raw AES-NI accelerated +//! access to the AES round function gated under the `hazmat` crate feature. + +use crate::{Block, Block8}; +use core::arch::aarch64::*; + +/// AES cipher (encrypt) round function. +#[allow(clippy::cast_ptr_alignment)] +#[target_feature(enable = "aes")] +pub(crate) unsafe fn cipher_round(block: &mut Block, round_key: &Block) { + let b = vld1q_u8(block.as_ptr()); + let k = vld1q_u8(round_key.as_ptr()); + + // AES single round encryption (all-zero round key, deferred until the end) + let mut state = vaeseq_u8(b, vdupq_n_u8(0)); + + // AES mix columns (the `vaeseq_u8` instruction otherwise omits this step) + state = vaesmcq_u8(state); + + // AES add round key (bitwise XOR) + state = veorq_u8(state, k); + + vst1q_u8(block.as_mut_ptr(), state); +} + +/// AES cipher (encrypt) round function: parallel version. +#[allow(clippy::cast_ptr_alignment)] +#[target_feature(enable = "aes")] +pub(crate) unsafe fn cipher_round_par(blocks: &mut Block8, round_keys: &Block8) { + for i in 0..8 { + let mut state = vld1q_u8(blocks[i].as_ptr()); + + // AES single round encryption + state = vaeseq_u8(state, vdupq_n_u8(0)); + + // AES mix columns + state = vaesmcq_u8(state); + + // AES add round key (bitwise XOR) + state = veorq_u8(state, vld1q_u8(round_keys[i].as_ptr())); + + vst1q_u8(blocks[i].as_mut_ptr(), state); + } +} + +/// AES equivalent inverse cipher (decrypt) round function. +#[allow(clippy::cast_ptr_alignment)] +#[target_feature(enable = "aes")] +pub(crate) unsafe fn equiv_inv_cipher_round(block: &mut Block, round_key: &Block) { + let b = vld1q_u8(block.as_ptr()); + let k = vld1q_u8(round_key.as_ptr()); + + // AES single round decryption (all-zero round key, deferred until the end) + let mut state = vaesdq_u8(b, vdupq_n_u8(0)); + + // AES inverse mix columns (the `vaesdq_u8` instruction otherwise omits this step) + state = vaesimcq_u8(state); + + // AES add round key (bitwise XOR) + state = veorq_u8(state, k); + + vst1q_u8(block.as_mut_ptr(), state); +} + +/// AES equivalent inverse cipher (decrypt) round function: parallel version. +#[allow(clippy::cast_ptr_alignment)] +#[target_feature(enable = "aes")] +pub(crate) unsafe fn equiv_inv_cipher_round_par(blocks: &mut Block8, round_keys: &Block8) { + for i in 0..8 { + let mut state = vld1q_u8(blocks[i].as_ptr()); + + // AES single round decryption (all-zero round key, deferred until the end) + state = vaesdq_u8(state, vdupq_n_u8(0)); + + // AES inverse mix columns (the `vaesdq_u8` instruction otherwise omits this step) + state = vaesimcq_u8(state); + + // AES add round key (bitwise XOR) + state = veorq_u8(state, vld1q_u8(round_keys[i].as_ptr())); + + vst1q_u8(blocks[i].as_mut_ptr(), state); + } +} + +/// AES mix columns function. +#[allow(clippy::cast_ptr_alignment)] +#[target_feature(enable = "aes")] +pub(crate) unsafe fn mix_columns(block: &mut Block) { + let b = vld1q_u8(block.as_ptr()); + let out = vaesmcq_u8(b); + vst1q_u8(block.as_mut_ptr(), out); +} + +/// AES inverse mix columns function. +#[allow(clippy::cast_ptr_alignment)] +#[target_feature(enable = "aes")] +pub(crate) unsafe fn inv_mix_columns(block: &mut Block) { + let b = vld1q_u8(block.as_ptr()); + let out = vaesimcq_u8(b); + vst1q_u8(block.as_mut_ptr(), out); +} diff --git a/src/armv8/test_expand.rs b/src/armv8/test_expand.rs new file mode 100644 index 0000000..c52bda7 --- /dev/null +++ b/src/armv8/test_expand.rs @@ -0,0 +1,130 @@ +use super::{expand_key, inv_expanded_keys}; +use core::arch::aarch64::*; +use hex_literal::hex; + +/// FIPS 197, Appendix A.1: AES-128 Cipher Key +/// user input, unaligned buffer +const AES128_KEY: [u8; 16] = hex!("2b7e151628aed2a6abf7158809cf4f3c"); + +/// FIPS 197 Appendix A.1: Expansion of a 128-bit Cipher Key +/// library controlled, aligned buffer +const AES128_EXP_KEYS: [[u8; 16]; 11] = [ + AES128_KEY, + hex!("a0fafe1788542cb123a339392a6c7605"), + hex!("f2c295f27a96b9435935807a7359f67f"), + hex!("3d80477d4716fe3e1e237e446d7a883b"), + hex!("ef44a541a8525b7fb671253bdb0bad00"), + hex!("d4d1c6f87c839d87caf2b8bc11f915bc"), + hex!("6d88a37a110b3efddbf98641ca0093fd"), + hex!("4e54f70e5f5fc9f384a64fb24ea6dc4f"), + hex!("ead27321b58dbad2312bf5607f8d292f"), + hex!("ac7766f319fadc2128d12941575c006e"), + hex!("d014f9a8c9ee2589e13f0cc8b6630ca6"), +]; + +/// Inverse expanded keys for [`AES128_EXPANDED_KEYS`] +const AES128_EXP_INVKEYS: [[u8; 16]; 11] = [ + hex!("d014f9a8c9ee2589e13f0cc8b6630ca6"), + hex!("0c7b5a631319eafeb0398890664cfbb4"), + hex!("df7d925a1f62b09da320626ed6757324"), + hex!("12c07647c01f22c7bc42d2f37555114a"), + hex!("6efcd876d2df54807c5df034c917c3b9"), + hex!("6ea30afcbc238cf6ae82a4b4b54a338d"), + hex!("90884413d280860a12a128421bc89739"), + hex!("7c1f13f74208c219c021ae480969bf7b"), + hex!("cc7505eb3e17d1ee82296c51c9481133"), + hex!("2b3708a7f262d405bc3ebdbf4b617d62"), + AES128_KEY, +]; + +/// FIPS 197, Appendix A.2: AES-192 Cipher Key +/// user input, unaligned buffer +const AES192_KEY: [u8; 24] = hex!("8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b"); + +/// FIPS 197 Appendix A.2: Expansion of a 192-bit Cipher Key +/// library controlled, aligned buffer +const AES192_EXP_KEYS: [[u8; 16]; 13] = [ + hex!("8e73b0f7da0e6452c810f32b809079e5"), + hex!("62f8ead2522c6b7bfe0c91f72402f5a5"), + hex!("ec12068e6c827f6b0e7a95b95c56fec2"), + hex!("4db7b4bd69b5411885a74796e92538fd"), + hex!("e75fad44bb095386485af05721efb14f"), + hex!("a448f6d94d6dce24aa326360113b30e6"), + hex!("a25e7ed583b1cf9a27f939436a94f767"), + hex!("c0a69407d19da4e1ec1786eb6fa64971"), + hex!("485f703222cb8755e26d135233f0b7b3"), + hex!("40beeb282f18a2596747d26b458c553e"), + hex!("a7e1466c9411f1df821f750aad07d753"), + hex!("ca4005388fcc5006282d166abc3ce7b5"), + hex!("e98ba06f448c773c8ecc720401002202"), +]; + +/// FIPS 197, Appendix A.3: AES-256 Cipher Key +/// user input, unaligned buffer +const AES256_KEY: [u8; 32] = + hex!("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); + +/// FIPS 197 Appendix A.3: Expansion of a 256-bit Cipher Key +/// library controlled, aligned buffer +const AES256_EXP_KEYS: [[u8; 16]; 15] = [ + hex!("603deb1015ca71be2b73aef0857d7781"), + hex!("1f352c073b6108d72d9810a30914dff4"), + hex!("9ba354118e6925afa51a8b5f2067fcde"), + hex!("a8b09c1a93d194cdbe49846eb75d5b9a"), + hex!("d59aecb85bf3c917fee94248de8ebe96"), + hex!("b5a9328a2678a647983122292f6c79b3"), + hex!("812c81addadf48ba24360af2fab8b464"), + hex!("98c5bfc9bebd198e268c3ba709e04214"), + hex!("68007bacb2df331696e939e46c518d80"), + hex!("c814e20476a9fb8a5025c02d59c58239"), + hex!("de1369676ccc5a71fa2563959674ee15"), + hex!("5886ca5d2e2f31d77e0af1fa27cf73c3"), + hex!("749c47ab18501ddae2757e4f7401905a"), + hex!("cafaaae3e4d59b349adf6acebd10190d"), + hex!("fe4890d1e6188d0b046df344706c631e"), +]; + +fn load_expanded_keys(input: [[u8; 16]; N]) -> [uint8x16_t; N] { + let mut output = [unsafe { vdupq_n_u8(0) }; N]; + + for (src, dst) in input.iter().zip(output.iter_mut()) { + *dst = unsafe { vld1q_u8(src.as_ptr()) } + } + + output +} + +fn store_expanded_keys(input: [uint8x16_t; N]) -> [[u8; 16]; N] { + let mut output = [[0u8; 16]; N]; + + for (src, dst) in input.iter().zip(output.iter_mut()) { + unsafe { vst1q_u8(dst.as_mut_ptr(), *src) } + } + + output +} + +#[test] +fn aes128_key_expansion() { + let ek = expand_key(&AES128_KEY); + assert_eq!(store_expanded_keys(ek), AES128_EXP_KEYS); +} + +#[test] +fn aes128_key_expansion_inv() { + let mut ek = load_expanded_keys(AES128_EXP_KEYS); + inv_expanded_keys(&mut ek); + assert_eq!(store_expanded_keys(ek), AES128_EXP_INVKEYS); +} + +#[test] +fn aes192_key_expansion() { + let ek = expand_key(&AES192_KEY); + assert_eq!(store_expanded_keys(ek), AES192_EXP_KEYS); +} + +#[test] +fn aes256_key_expansion() { + let ek = expand_key(&AES256_KEY); + assert_eq!(store_expanded_keys(ek), AES256_EXP_KEYS); +} diff --git a/src/autodetect.rs b/src/autodetect.rs new file mode 100644 index 0000000..ac471fa --- /dev/null +++ b/src/autodetect.rs @@ -0,0 +1,430 @@ +//! Autodetection support for hardware accelerated AES backends with fallback +//! to the fixsliced "soft" implementation. + +use crate::soft; +use cipher::{ + consts::{U16, U24, U32}, + AlgorithmName, BlockCipher, BlockClosure, BlockDecrypt, BlockEncrypt, BlockSizeUser, Key, + KeyInit, KeySizeUser, +}; +use core::fmt; +use core::mem::ManuallyDrop; + +#[cfg(all(target_arch = "aarch64", aes_armv8))] +use crate::armv8 as intrinsics; + +#[cfg(any(target_arch = "x86_64", target_arch = "x86"))] +use crate::ni as intrinsics; + +cpufeatures::new!(aes_intrinsics, "aes"); + +macro_rules! define_aes_impl { + ( + $name:ident, + $name_enc:ident, + $name_dec:ident, + $module:tt, + $key_size:ty, + $doc:expr $(,)? + ) => { + mod $module { + use super::{intrinsics, soft}; + use core::mem::ManuallyDrop; + + pub(super) union Inner { + pub(super) intrinsics: ManuallyDrop, + pub(super) soft: ManuallyDrop, + } + + pub(super) union InnerEnc { + pub(super) intrinsics: ManuallyDrop, + pub(super) soft: ManuallyDrop, + } + + pub(super) union InnerDec { + pub(super) intrinsics: ManuallyDrop, + pub(super) soft: ManuallyDrop, + } + } + + #[doc=$doc] + #[doc = "block cipher"] + pub struct $name { + inner: $module::Inner, + token: aes_intrinsics::InitToken, + } + + impl KeySizeUser for $name { + type KeySize = $key_size; + } + impl From<$name_enc> for $name { + #[inline] + fn from(enc: $name_enc) -> $name { + Self::from(&enc) + } + } + + impl From<&$name_enc> for $name { + fn from(enc: &$name_enc) -> $name { + use core::ops::Deref; + let inner = if enc.token.get() { + $module::Inner { + intrinsics: ManuallyDrop::new(unsafe { + enc.inner.intrinsics.deref().into() + }), + } + } else { + $module::Inner { + soft: ManuallyDrop::new(unsafe { enc.inner.soft.deref().into() }), + } + }; + + Self { + inner, + token: enc.token, + } + } + } + + impl KeyInit for $name { + #[inline] + fn new(key: &Key) -> Self { + let (token, aesni_present) = aes_intrinsics::init_get(); + + let inner = if aesni_present { + $module::Inner { + intrinsics: ManuallyDrop::new(intrinsics::$name::new(key)), + } + } else { + $module::Inner { + soft: ManuallyDrop::new(soft::$name::new(key)), + } + }; + + Self { inner, token } + } + } + + impl Clone for $name { + fn clone(&self) -> Self { + let inner = if self.token.get() { + $module::Inner { + intrinsics: unsafe { self.inner.intrinsics.clone() }, + } + } else { + $module::Inner { + soft: unsafe { self.inner.soft.clone() }, + } + }; + + Self { + inner, + token: self.token, + } + } + } + + impl BlockSizeUser for $name { + type BlockSize = U16; + } + + impl BlockCipher for $name {} + + impl BlockEncrypt for $name { + fn encrypt_with_backend(&self, f: impl BlockClosure) { + unsafe { + if self.token.get() { + #[target_feature(enable = "aes")] + unsafe fn inner( + state: &intrinsics::$name, + f: impl BlockClosure, + ) { + f.call(&mut state.get_enc_backend()); + } + inner(&self.inner.intrinsics, f); + } else { + f.call(&mut self.inner.soft.get_enc_backend()); + } + } + } + } + + impl BlockDecrypt for $name { + fn decrypt_with_backend(&self, f: impl BlockClosure) { + unsafe { + if self.token.get() { + #[target_feature(enable = "aes")] + unsafe fn inner( + state: &intrinsics::$name, + f: impl BlockClosure, + ) { + f.call(&mut state.get_dec_backend()); + } + inner(&self.inner.intrinsics, f); + } else { + f.call(&mut self.inner.soft.get_dec_backend()); + } + } + } + } + + impl fmt::Debug for $name { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str(concat!(stringify!($name), " { .. }")) + } + } + + impl AlgorithmName for $name { + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(stringify!($name)) + } + } + + impl Drop for $name { + #[inline] + fn drop(&mut self) { + if self.token.get() { + unsafe { ManuallyDrop::drop(&mut self.inner.intrinsics) }; + } else { + unsafe { ManuallyDrop::drop(&mut self.inner.soft) }; + }; + } + } + + #[cfg(feature = "zeroize")] + impl zeroize::ZeroizeOnDrop for $name {} + + #[doc=$doc] + #[doc = "block cipher (encrypt-only)"] + pub struct $name_enc { + inner: $module::InnerEnc, + token: aes_intrinsics::InitToken, + } + + impl KeySizeUser for $name_enc { + type KeySize = $key_size; + } + + impl KeyInit for $name_enc { + #[inline] + fn new(key: &Key) -> Self { + let (token, aesni_present) = aes_intrinsics::init_get(); + + let inner = if aesni_present { + $module::InnerEnc { + intrinsics: ManuallyDrop::new(intrinsics::$name_enc::new(key)), + } + } else { + $module::InnerEnc { + soft: ManuallyDrop::new(soft::$name_enc::new(key)), + } + }; + + Self { inner, token } + } + } + + impl Clone for $name_enc { + fn clone(&self) -> Self { + let inner = if self.token.get() { + $module::InnerEnc { + intrinsics: unsafe { self.inner.intrinsics.clone() }, + } + } else { + $module::InnerEnc { + soft: unsafe { self.inner.soft.clone() }, + } + }; + + Self { + inner, + token: self.token, + } + } + } + + impl BlockSizeUser for $name_enc { + type BlockSize = U16; + } + + impl BlockCipher for $name_enc {} + + impl BlockEncrypt for $name_enc { + fn encrypt_with_backend(&self, f: impl BlockClosure) { + unsafe { + if self.token.get() { + #[target_feature(enable = "aes")] + unsafe fn inner( + state: &intrinsics::$name_enc, + f: impl BlockClosure, + ) { + f.call(&mut state.get_enc_backend()); + } + inner(&self.inner.intrinsics, f); + } else { + f.call(&mut self.inner.soft.get_enc_backend()); + } + } + } + } + + impl fmt::Debug for $name_enc { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str(concat!(stringify!($name_enc), " { .. }")) + } + } + + impl AlgorithmName for $name_enc { + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(stringify!($name_enc)) + } + } + + impl Drop for $name_enc { + #[inline] + fn drop(&mut self) { + if self.token.get() { + unsafe { ManuallyDrop::drop(&mut self.inner.intrinsics) }; + } else { + unsafe { ManuallyDrop::drop(&mut self.inner.soft) }; + }; + } + } + + #[cfg(feature = "zeroize")] + impl zeroize::ZeroizeOnDrop for $name_enc {} + + #[doc=$doc] + #[doc = "block cipher (decrypt-only)"] + pub struct $name_dec { + inner: $module::InnerDec, + token: aes_intrinsics::InitToken, + } + + impl KeySizeUser for $name_dec { + type KeySize = $key_size; + } + + impl From<$name_enc> for $name_dec { + #[inline] + fn from(enc: $name_enc) -> $name_dec { + Self::from(&enc) + } + } + + impl From<&$name_enc> for $name_dec { + fn from(enc: &$name_enc) -> $name_dec { + use core::ops::Deref; + let inner = if enc.token.get() { + $module::InnerDec { + intrinsics: ManuallyDrop::new(unsafe { + enc.inner.intrinsics.deref().into() + }), + } + } else { + $module::InnerDec { + soft: ManuallyDrop::new(unsafe { enc.inner.soft.deref().into() }), + } + }; + + Self { + inner, + token: enc.token, + } + } + } + + impl KeyInit for $name_dec { + #[inline] + fn new(key: &Key) -> Self { + let (token, aesni_present) = aes_intrinsics::init_get(); + + let inner = if aesni_present { + $module::InnerDec { + intrinsics: ManuallyDrop::new(intrinsics::$name_dec::new(key)), + } + } else { + $module::InnerDec { + soft: ManuallyDrop::new(soft::$name_dec::new(key)), + } + }; + + Self { inner, token } + } + } + + impl Clone for $name_dec { + fn clone(&self) -> Self { + let inner = if self.token.get() { + $module::InnerDec { + intrinsics: unsafe { self.inner.intrinsics.clone() }, + } + } else { + $module::InnerDec { + soft: unsafe { self.inner.soft.clone() }, + } + }; + + Self { + inner, + token: self.token, + } + } + } + + impl BlockSizeUser for $name_dec { + type BlockSize = U16; + } + + impl BlockCipher for $name_dec {} + + impl BlockDecrypt for $name_dec { + fn decrypt_with_backend(&self, f: impl BlockClosure) { + unsafe { + if self.token.get() { + #[target_feature(enable = "aes")] + unsafe fn inner( + state: &intrinsics::$name_dec, + f: impl BlockClosure, + ) { + f.call(&mut state.get_dec_backend()); + } + inner(&self.inner.intrinsics, f); + } else { + f.call(&mut self.inner.soft.get_dec_backend()); + } + } + } + } + + impl fmt::Debug for $name_dec { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str(concat!(stringify!($name_dec), " { .. }")) + } + } + + impl AlgorithmName for $name_dec { + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(stringify!($name_dec)) + } + } + + impl Drop for $name_dec { + #[inline] + fn drop(&mut self) { + if self.token.get() { + unsafe { ManuallyDrop::drop(&mut self.inner.intrinsics) }; + } else { + unsafe { ManuallyDrop::drop(&mut self.inner.soft) }; + }; + } + } + + #[cfg(feature = "zeroize")] + impl zeroize::ZeroizeOnDrop for $name_dec {} + }; +} + +define_aes_impl!(Aes128, Aes128Enc, Aes128Dec, aes128, U16, "AES-128"); +define_aes_impl!(Aes192, Aes192Enc, Aes192Dec, aes192, U24, "AES-192"); +define_aes_impl!(Aes256, Aes256Enc, Aes256Dec, aes256, U32, "AES-256"); diff --git a/src/hazmat.rs b/src/hazmat.rs new file mode 100644 index 0000000..9b5555e --- /dev/null +++ b/src/hazmat.rs @@ -0,0 +1,159 @@ +//! ⚠️ Low-level "hazmat" AES functions. +//! +//! # ☢️️ WARNING: HAZARDOUS API ☢️ +//! +//! This module contains an extremely low-level cryptographic primitive +//! which is likewise extremely difficult to use correctly. +//! +//! There are very few valid uses cases for this API. It's intended to be used +//! for implementing well-reviewed higher-level constructions. +//! +//! We do NOT recommending using it to implement any algorithm which has not +//! received extensive peer review by cryptographers. + +use crate::{soft::fixslice::hazmat as soft, Block, Block8}; + +#[cfg(all(target_arch = "aarch64", aes_armv8, not(aes_force_soft)))] +use crate::armv8::hazmat as intrinsics; + +#[cfg(all(any(target_arch = "x86_64", target_arch = "x86"), not(aes_force_soft)))] +use crate::ni::hazmat as intrinsics; + +#[cfg(all( + any( + target_arch = "x86", + target_arch = "x86_64", + all(target_arch = "aarch64", aes_armv8) + ), + not(aes_force_soft) +))] +cpufeatures::new!(aes_intrinsics, "aes"); + +/// Execute the provided body if CPU intrinsics are available. +// TODO(tarcieri): more `cfg-if`-like macro with an else branch? +macro_rules! if_intrinsics_available { + ($body:expr) => {{ + #[cfg(all( + any( + target_arch = "x86", + target_arch = "x86_64", + all(target_arch = "aarch64", aes_armv8) + ), + not(aes_force_soft) + ))] + if aes_intrinsics::get() { + unsafe { $body } + return; + } + }}; +} + +/// ⚠️ AES cipher (encrypt) round function. +/// +/// This API performs the following steps as described in FIPS 197 Appendix C: +/// +/// - `s_box`: state after `SubBytes()` +/// - `s_row`: state after `ShiftRows()` +/// - `m_col`: state after `MixColumns()` +/// - `k_sch`: key schedule value for `round[r]` +/// +/// This series of operations is equivalent to the Intel AES-NI `AESENC` instruction. +/// +/// # ☢️️ WARNING: HAZARDOUS API ☢️ +/// +/// Use this function with great care! See the [module-level documentation][crate::hazmat] +/// for more information. +pub fn cipher_round(block: &mut Block, round_key: &Block) { + if_intrinsics_available! { + intrinsics::cipher_round(block, round_key) + } + + soft::cipher_round(block, round_key); +} + +/// ⚠️ AES cipher (encrypt) round function: parallel version. +/// +/// Equivalent to [`cipher_round`], but acts on 8 blocks-at-a-time, applying +/// the same number of round keys. +/// +/// # ☢️️ WARNING: HAZARDOUS API ☢️ +/// +/// Use this function with great care! See the [module-level documentation][crate::hazmat] +/// for more information. +pub fn cipher_round_par(blocks: &mut Block8, round_keys: &Block8) { + if_intrinsics_available! { + intrinsics::cipher_round_par(blocks, round_keys) + } + + soft::cipher_round_par(blocks, round_keys); +} + +/// ⚠️ AES equivalent inverse cipher (decrypt) round function. +/// +/// This API performs the following steps as described in FIPS 197 Appendix C: +/// +/// - `is_box`: state after `InvSubBytes()` +/// - `is_row`: state after `InvShiftRows()` +/// - `im_col`: state after `InvMixColumns()` +/// - `ik_sch`: key schedule value for `round[r]` +/// +/// This series of operations is equivalent to the Intel AES-NI `AESDEC` instruction. +/// +/// # ☢️️ WARNING: HAZARDOUS API ☢️ +/// +/// Use this function with great care! See the [module-level documentation][crate::hazmat] +/// for more information. +pub fn equiv_inv_cipher_round(block: &mut Block, round_key: &Block) { + if_intrinsics_available! { + intrinsics::equiv_inv_cipher_round(block, round_key) + } + + soft::equiv_inv_cipher_round(block, round_key); +} + +/// ⚠️ AES equivalent inverse cipher (decrypt) round function: parallel version. +/// +/// Equivalent to [`equiv_inv_cipher_round`], but acts on 8 blocks-at-a-time, +/// applying the same number of round keys. +/// +/// # ☢️️ WARNING: HAZARDOUS API ☢️ +/// +/// Use this function with great care! See the [module-level documentation][crate::hazmat] +/// for more information. +pub fn equiv_inv_cipher_round_par(blocks: &mut Block8, round_keys: &Block8) { + if_intrinsics_available! { + intrinsics::equiv_inv_cipher_round_par(blocks, round_keys) + } + + soft::equiv_inv_cipher_round_par(blocks, round_keys); +} + +/// ⚠️ AES mix columns function. +/// +/// # ☢️️ WARNING: HAZARDOUS API ☢️ +/// +/// Use this function with great care! See the [module-level documentation][crate::hazmat] +/// for more information. +pub fn mix_columns(block: &mut Block) { + if_intrinsics_available! { + intrinsics::mix_columns(block) + } + + soft::mix_columns(block); +} + +/// ⚠️ AES inverse mix columns function. +/// +/// This function is equivalent to the Intel AES-NI `AESIMC` instruction. +/// +/// # ☢️️ WARNING: HAZARDOUS API ☢️ +/// +/// Use this function with great care! See the [module-level documentation][crate::hazmat] +/// for more information. +pub fn inv_mix_columns(block: &mut Block) { + if_intrinsics_available! { + intrinsics::inv_mix_columns(block) + } + + soft::inv_mix_columns(block); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..f43b21c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,232 @@ +//! Pure Rust implementation of the [Advanced Encryption Standard][AES] +//! (AES, a.k.a. Rijndael). +//! +//! # ⚠️ Security Warning: Hazmat! +//! +//! This crate implements only the low-level block cipher function, and is intended +//! for use for implementing higher-level constructions *only*. It is NOT +//! intended for direct use in applications. +//! +//! USE AT YOUR OWN RISK! +//! +//! # Supported backends +//! This crate provides multiple backends including a portable pure Rust +//! backend as well as ones based on CPU intrinsics. +//! +//! By default, it performs runtime detection of CPU intrinsics and uses them +//! if they are available. +//! +//! ## "soft" portable backend +//! As a baseline implementation, this crate provides a constant-time pure Rust +//! implementation based on [fixslicing], a more advanced form of bitslicing +//! implemented entirely in terms of bitwise arithmetic with no use of any +//! lookup tables or data-dependent branches. +//! +//! Enabling the `aes_compact` configuration flag will reduce the code size of this +//! backend at the cost of decreased performance (using a modified form of +//! the fixslicing technique called "semi-fixslicing"). +//! +//! ## ARMv8 intrinsics (nightly-only) +//! On `aarch64` targets including `aarch64-apple-darwin` (Apple M1) and Linux +//! targets such as `aarch64-unknown-linux-gnu` and `aarch64-unknown-linux-musl`, +//! support for using AES intrinsics provided by the ARMv8 Cryptography Extensions +//! is available when using the nightly compiler, and can be enabled using the +//! `aes_armv8` configuration flag. +//! +//! On Linux and macOS, when the `aes_armv8` flag is enabled support for AES +//! intrinsics is autodetected at runtime. On other platforms the `aes` +//! target feature must be enabled via RUSTFLAGS. +//! +//! ## `x86`/`x86_64` intrinsics (AES-NI) +//! By default this crate uses runtime detection on `i686`/`x86_64` targets +//! in order to determine if AES-NI is available, and if it is not, it will +//! fallback to using a constant-time software implementation. +//! +//! Passing `RUSTFLAGS=-Ctarget-feature=+aes,+ssse3` explicitly at compile-time +//! will override runtime detection and ensure that AES-NI is always used. +//! Programs built in this manner will crash with an illegal instruction on +//! CPUs which do not have AES-NI enabled. +//! +//! Note: runtime detection is not possible on SGX targets. Please use the +//! afforementioned `RUSTFLAGS` to leverage AES-NI on these targets. +//! +//! # Examples +//! ``` +//! use aes::Aes128; +//! use aes::cipher::{ +//! BlockCipher, BlockEncrypt, BlockDecrypt, KeyInit, +//! generic_array::GenericArray, +//! }; +//! +//! let key = GenericArray::from([0u8; 16]); +//! let mut block = GenericArray::from([42u8; 16]); +//! +//! // Initialize cipher +//! let cipher = Aes128::new(&key); +//! +//! let block_copy = block.clone(); +//! +//! // Encrypt block in-place +//! cipher.encrypt_block(&mut block); +//! +//! // And decrypt it back +//! cipher.decrypt_block(&mut block); +//! assert_eq!(block, block_copy); +//! +//! // implementation supports parallel block processing +//! // number of blocks processed in parallel depends in general +//! // on hardware capabilities +//! let mut blocks = [block; 100]; +//! cipher.encrypt_blocks(&mut blocks); +//! +//! for block in blocks.iter_mut() { +//! cipher.decrypt_block(block); +//! assert_eq!(block, &block_copy); +//! } +//! +//! cipher.decrypt_blocks(&mut blocks); +//! +//! for block in blocks.iter_mut() { +//! cipher.encrypt_block(block); +//! assert_eq!(block, &block_copy); +//! } +//! ``` +//! +//! For implementation of block cipher modes of operation see +//! [`block-modes`] repository. +//! +//! # Configuration Flags +//! +//! You can modify crate using the following configuration flags: +//! +//! - `aes_armv8`: enable ARMv8 AES intrinsics (nightly-only). +//! - `aes_force_soft`: force software implementation. +//! - `aes_compact`: reduce code size at the cost of slower performance +//! (affects only software backend). +//! +//! It can be enabled using `RUSTFLAGS` environmental variable +//! (e.g. `RUSTFLAGS="--cfg aes_compact"`) or by modifying `.cargo/config`. +//! +//! [AES]: https://en.wikipedia.org/wiki/Advanced_Encryption_Standard +//! [fixslicing]: https://eprint.iacr.org/2020/1123.pdf +//! [AES-NI]: https://en.wikipedia.org/wiki/AES_instruction_set +//! [`block-modes`]: https://github.com/RustCrypto/block-modes/ + +#![no_std] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/26acc39f/logo.svg" +)] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![warn(missing_docs, rust_2018_idioms)] +#![cfg_attr(all(aes_armv8, target_arch = "aarch64"), feature(stdsimd))] + +#[cfg(feature = "hazmat")] +#[cfg_attr(docsrs, doc(cfg(feature = "hazmat")))] +pub mod hazmat; + +mod soft; + +use cfg_if::cfg_if; + +cfg_if! { + if #[cfg(all(target_arch = "aarch64", aes_armv8, not(aes_force_soft)))] { + mod armv8; + mod autodetect; + pub use autodetect::*; + } else if #[cfg(all( + any(target_arch = "x86", target_arch = "x86_64"), + not(aes_force_soft) + ))] { + mod autodetect; + mod ni; + pub use autodetect::*; + } else { + pub use soft::*; + } +} + +pub use cipher; +use cipher::{ + consts::{U16, U8}, + generic_array::GenericArray, +}; + +/// 128-bit AES block +pub type Block = GenericArray; +/// Eight 128-bit AES blocks +pub type Block8 = GenericArray; + +#[cfg(test)] +mod tests { + #[cfg(feature = "zeroize")] + #[test] + fn zeroize_works() { + use super::soft; + + fn test_for(val: T) { + use core::mem::{size_of, ManuallyDrop}; + + let mut val = ManuallyDrop::new(val); + let ptr = &val as *const _ as *const u8; + let len = size_of::>(); + + unsafe { ManuallyDrop::drop(&mut val) }; + + let slice = unsafe { core::slice::from_raw_parts(ptr, len) }; + + assert!(slice.iter().all(|&byte| byte == 0)); + } + + let key_128 = [42; 16].into(); + let key_192 = [42; 24].into(); + let key_256 = [42; 32].into(); + + use cipher::KeyInit as _; + test_for(soft::Aes128::new(&key_128)); + test_for(soft::Aes128Enc::new(&key_128)); + test_for(soft::Aes128Dec::new(&key_128)); + test_for(soft::Aes192::new(&key_192)); + test_for(soft::Aes192Enc::new(&key_192)); + test_for(soft::Aes192Dec::new(&key_192)); + test_for(soft::Aes256::new(&key_256)); + test_for(soft::Aes256Enc::new(&key_256)); + test_for(soft::Aes256Dec::new(&key_256)); + + #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), not(aes_force_soft)))] + { + use super::ni; + + cpufeatures::new!(aes_intrinsics, "aes"); + if aes_intrinsics::get() { + test_for(ni::Aes128::new(&key_128)); + test_for(ni::Aes128Enc::new(&key_128)); + test_for(ni::Aes128Dec::new(&key_128)); + test_for(ni::Aes192::new(&key_192)); + test_for(ni::Aes192Enc::new(&key_192)); + test_for(ni::Aes192Dec::new(&key_192)); + test_for(ni::Aes256::new(&key_256)); + test_for(ni::Aes256Enc::new(&key_256)); + test_for(ni::Aes256Dec::new(&key_256)); + } + } + + #[cfg(all(target_arch = "aarch64", aes_armv8, not(aes_force_soft)))] + { + use super::armv8; + + cpufeatures::new!(aes_intrinsics, "aes"); + if aes_intrinsics::get() { + test_for(armv8::Aes128::new(&key_128)); + test_for(armv8::Aes128Enc::new(&key_128)); + test_for(armv8::Aes128Dec::new(&key_128)); + test_for(armv8::Aes192::new(&key_192)); + test_for(armv8::Aes192Enc::new(&key_192)); + test_for(armv8::Aes192Dec::new(&key_192)); + test_for(armv8::Aes256::new(&key_256)); + test_for(armv8::Aes256Enc::new(&key_256)); + test_for(armv8::Aes256Dec::new(&key_256)); + } + } + } +} diff --git a/src/ni.rs b/src/ni.rs new file mode 100644 index 0000000..15b49ef --- /dev/null +++ b/src/ni.rs @@ -0,0 +1,361 @@ +//! AES block ciphers implementation using AES-NI instruction set. +//! +//! Ciphers functionality is accessed using `BlockCipher` trait from the +//! [`cipher`](https://docs.rs/cipher) crate. +//! +//! # Vulnerability +//! Lazy FP state restory vulnerability can allow local process to leak content +//! of the FPU register, in which round keys are stored. This vulnerability +//! can be mitigated at the operating system level by installing relevant +//! patches. (i.e. keep your OS updated!) More info: +//! - [Intel advisory](https://www.intel.com/content/www/us/en/security-center/advisory/intel-sa-00145.html) +//! - [Wikipedia](https://en.wikipedia.org/wiki/Lazy_FP_state_restore) +//! +//! # Related documents +//! - [Intel AES-NI whitepaper](https://software.intel.com/sites/default/files/article/165683/aes-wp-2012-09-22-v01.pdf) +//! - [Use of the AES Instruction Set](https://www.cosic.esat.kuleuven.be/ecrypt/AESday/slides/Use_of_the_AES_Instruction_Set.pdf) + +#[macro_use] +mod utils; + +mod aes128; +mod aes192; +mod aes256; + +#[cfg(test)] +mod test_expand; + +#[cfg(feature = "hazmat")] +pub(crate) mod hazmat; + +#[cfg(target_arch = "x86")] +use core::arch::x86 as arch; +#[cfg(target_arch = "x86_64")] +use core::arch::x86_64 as arch; + +use crate::{Block, Block8}; +use cipher::{ + consts::{U16, U24, U32, U8}, + inout::InOut, + AlgorithmName, BlockBackend, BlockCipher, BlockClosure, BlockDecrypt, BlockEncrypt, + BlockSizeUser, Key, KeyInit, KeySizeUser, ParBlocksSizeUser, +}; +use core::fmt; + +macro_rules! define_aes_impl { + ( + $name:tt, + $name_enc:ident, + $name_dec:ident, + $name_back_enc:ident, + $name_back_dec:ident, + $module:tt, + $key_size:ty, + $doc:expr $(,)? + ) => { + #[doc=$doc] + #[doc = "block cipher"] + #[derive(Clone)] + pub struct $name { + encrypt: $name_enc, + decrypt: $name_dec, + } + + impl $name { + #[inline(always)] + pub(crate) fn get_enc_backend(&self) -> $name_back_enc<'_> { + self.encrypt.get_enc_backend() + } + + #[inline(always)] + pub(crate) fn get_dec_backend(&self) -> $name_back_dec<'_> { + self.decrypt.get_dec_backend() + } + } + + impl BlockCipher for $name {} + + impl KeySizeUser for $name { + type KeySize = $key_size; + } + + impl KeyInit for $name { + #[inline] + fn new(key: &Key) -> Self { + let encrypt = $name_enc::new(key); + let decrypt = $name_dec::from(&encrypt); + Self { encrypt, decrypt } + } + } + + impl From<$name_enc> for $name { + #[inline] + fn from(encrypt: $name_enc) -> $name { + let decrypt = (&encrypt).into(); + Self { encrypt, decrypt } + } + } + + impl From<&$name_enc> for $name { + #[inline] + fn from(encrypt: &$name_enc) -> $name { + let decrypt = encrypt.into(); + let encrypt = encrypt.clone(); + Self { encrypt, decrypt } + } + } + + impl BlockSizeUser for $name { + type BlockSize = U16; + } + + impl BlockEncrypt for $name { + fn encrypt_with_backend(&self, f: impl BlockClosure) { + self.encrypt.encrypt_with_backend(f) + } + } + + impl BlockDecrypt for $name { + fn decrypt_with_backend(&self, f: impl BlockClosure) { + self.decrypt.decrypt_with_backend(f) + } + } + + impl fmt::Debug for $name { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str(concat!(stringify!($name), " { .. }")) + } + } + + impl AlgorithmName for $name { + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(stringify!($name)) + } + } + + #[cfg(feature = "zeroize")] + impl zeroize::ZeroizeOnDrop for $name {} + + #[doc=$doc] + #[doc = "block cipher (encrypt-only)"] + #[derive(Clone)] + pub struct $name_enc { + round_keys: $module::RoundKeys, + } + + impl $name_enc { + #[inline(always)] + pub(crate) fn get_enc_backend(&self) -> $name_back_enc<'_> { + $name_back_enc(self) + } + } + + impl BlockCipher for $name_enc {} + + impl KeySizeUser for $name_enc { + type KeySize = $key_size; + } + + impl KeyInit for $name_enc { + fn new(key: &Key) -> Self { + // SAFETY: we enforce that this code is called only when + // target features required by `expand` were properly checked. + Self { + round_keys: unsafe { $module::expand_key(key.as_ref()) }, + } + } + } + + impl BlockSizeUser for $name_enc { + type BlockSize = U16; + } + + impl BlockEncrypt for $name_enc { + fn encrypt_with_backend(&self, f: impl BlockClosure) { + f.call(&mut self.get_enc_backend()) + } + } + + impl fmt::Debug for $name_enc { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str(concat!(stringify!($name_enc), " { .. }")) + } + } + + impl AlgorithmName for $name_enc { + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(stringify!($name_enc)) + } + } + + impl Drop for $name_enc { + #[inline] + fn drop(&mut self) { + #[cfg(feature = "zeroize")] + zeroize::Zeroize::zeroize(&mut self.round_keys); + } + } + + #[cfg(feature = "zeroize")] + impl zeroize::ZeroizeOnDrop for $name_enc {} + + #[doc=$doc] + #[doc = "block cipher (decrypt-only)"] + #[derive(Clone)] + pub struct $name_dec { + round_keys: $module::RoundKeys, + } + + impl $name_dec { + #[inline(always)] + pub(crate) fn get_dec_backend(&self) -> $name_back_dec<'_> { + $name_back_dec(self) + } + } + + impl BlockCipher for $name_dec {} + + impl KeySizeUser for $name_dec { + type KeySize = $key_size; + } + + impl KeyInit for $name_dec { + fn new(key: &Key) -> Self { + $name_enc::new(key).into() + } + } + + impl From<$name_enc> for $name_dec { + #[inline] + fn from(enc: $name_enc) -> $name_dec { + Self::from(&enc) + } + } + + impl From<&$name_enc> for $name_dec { + #[inline] + fn from(enc: &$name_enc) -> $name_dec { + let round_keys = unsafe { $module::inv_expanded_keys(&enc.round_keys) }; + Self { round_keys } + } + } + + impl BlockSizeUser for $name_dec { + type BlockSize = U16; + } + + impl BlockDecrypt for $name_dec { + fn decrypt_with_backend(&self, f: impl BlockClosure) { + f.call(&mut self.get_dec_backend()); + } + } + + impl fmt::Debug for $name_dec { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str(concat!(stringify!($name_dec), " { .. }")) + } + } + + impl AlgorithmName for $name_dec { + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(stringify!($name_dec)) + } + } + + impl Drop for $name_dec { + #[inline] + fn drop(&mut self) { + #[cfg(feature = "zeroize")] + zeroize::Zeroize::zeroize(&mut self.round_keys); + } + } + + #[cfg(feature = "zeroize")] + impl zeroize::ZeroizeOnDrop for $name_dec {} + + pub(crate) struct $name_back_enc<'a>(&'a $name_enc); + + impl<'a> BlockSizeUser for $name_back_enc<'a> { + type BlockSize = U16; + } + + impl<'a> ParBlocksSizeUser for $name_back_enc<'a> { + type ParBlocksSize = U8; + } + + impl<'a> BlockBackend for $name_back_enc<'a> { + #[inline(always)] + fn proc_block(&mut self, block: InOut<'_, '_, Block>) { + unsafe { + $module::encrypt1(&self.0.round_keys, block); + } + } + + #[inline(always)] + fn proc_par_blocks(&mut self, blocks: InOut<'_, '_, Block8>) { + unsafe { + $module::encrypt8(&self.0.round_keys, blocks); + } + } + } + + pub(crate) struct $name_back_dec<'a>(&'a $name_dec); + + impl<'a> BlockSizeUser for $name_back_dec<'a> { + type BlockSize = U16; + } + + impl<'a> ParBlocksSizeUser for $name_back_dec<'a> { + type ParBlocksSize = U8; + } + + impl<'a> BlockBackend for $name_back_dec<'a> { + #[inline(always)] + fn proc_block(&mut self, block: InOut<'_, '_, Block>) { + unsafe { + $module::decrypt1(&self.0.round_keys, block); + } + } + + #[inline(always)] + fn proc_par_blocks(&mut self, blocks: InOut<'_, '_, Block8>) { + unsafe { + $module::decrypt8(&self.0.round_keys, blocks); + } + } + } + }; +} + +define_aes_impl!( + Aes128, + Aes128Enc, + Aes128Dec, + Aes128BackEnc, + Aes128BackDec, + aes128, + U16, + "AES-128", +); + +define_aes_impl!( + Aes192, + Aes192Enc, + Aes192Dec, + Aes192BackEnc, + Aes192BackDec, + aes192, + U24, + "AES-192", +); + +define_aes_impl!( + Aes256, + Aes256Enc, + Aes256Dec, + Aes256BackEnc, + Aes256BackDec, + aes256, + U32, + "AES-256", +); diff --git a/src/ni/aes128.rs b/src/ni/aes128.rs new file mode 100644 index 0000000..b0836a1 --- /dev/null +++ b/src/ni/aes128.rs @@ -0,0 +1,145 @@ +use super::{arch::*, utils::*}; +use crate::{Block, Block8}; +use cipher::inout::InOut; +use core::mem; + +/// AES-128 round keys +pub(super) type RoundKeys = [__m128i; 11]; + +#[inline] +#[target_feature(enable = "aes")] +pub(super) unsafe fn encrypt1(keys: &RoundKeys, block: InOut<'_, '_, Block>) { + let (in_ptr, out_ptr) = block.into_raw(); + let mut b = _mm_loadu_si128(in_ptr as *const __m128i); + b = _mm_xor_si128(b, keys[0]); + b = _mm_aesenc_si128(b, keys[1]); + b = _mm_aesenc_si128(b, keys[2]); + b = _mm_aesenc_si128(b, keys[3]); + b = _mm_aesenc_si128(b, keys[4]); + b = _mm_aesenc_si128(b, keys[5]); + b = _mm_aesenc_si128(b, keys[6]); + b = _mm_aesenc_si128(b, keys[7]); + b = _mm_aesenc_si128(b, keys[8]); + b = _mm_aesenc_si128(b, keys[9]); + b = _mm_aesenclast_si128(b, keys[10]); + _mm_storeu_si128(out_ptr as *mut __m128i, b); +} + +#[inline] +#[target_feature(enable = "aes")] +pub(super) unsafe fn encrypt8(keys: &RoundKeys, blocks: InOut<'_, '_, Block8>) { + let (in_ptr, out_ptr) = blocks.into_raw(); + let mut b = load8(in_ptr); + xor8(&mut b, keys[0]); + aesenc8(&mut b, keys[1]); + aesenc8(&mut b, keys[2]); + aesenc8(&mut b, keys[3]); + aesenc8(&mut b, keys[4]); + aesenc8(&mut b, keys[5]); + aesenc8(&mut b, keys[6]); + aesenc8(&mut b, keys[7]); + aesenc8(&mut b, keys[8]); + aesenc8(&mut b, keys[9]); + aesenclast8(&mut b, keys[10]); + store8(out_ptr, b); +} + +#[inline] +#[target_feature(enable = "aes")] +pub(super) unsafe fn decrypt1(keys: &RoundKeys, block: InOut<'_, '_, Block>) { + let (in_ptr, out_ptr) = block.into_raw(); + let mut b = _mm_loadu_si128(in_ptr as *const __m128i); + b = _mm_xor_si128(b, keys[10]); + b = _mm_aesdec_si128(b, keys[9]); + b = _mm_aesdec_si128(b, keys[8]); + b = _mm_aesdec_si128(b, keys[7]); + b = _mm_aesdec_si128(b, keys[6]); + b = _mm_aesdec_si128(b, keys[5]); + b = _mm_aesdec_si128(b, keys[4]); + b = _mm_aesdec_si128(b, keys[3]); + b = _mm_aesdec_si128(b, keys[2]); + b = _mm_aesdec_si128(b, keys[1]); + b = _mm_aesdeclast_si128(b, keys[0]); + _mm_storeu_si128(out_ptr as *mut __m128i, b); +} + +#[inline] +#[target_feature(enable = "aes")] +pub(super) unsafe fn decrypt8(keys: &RoundKeys, blocks: InOut<'_, '_, Block8>) { + let (in_ptr, out_ptr) = blocks.into_raw(); + let mut b = load8(in_ptr); + xor8(&mut b, keys[10]); + aesdec8(&mut b, keys[9]); + aesdec8(&mut b, keys[8]); + aesdec8(&mut b, keys[7]); + aesdec8(&mut b, keys[6]); + aesdec8(&mut b, keys[5]); + aesdec8(&mut b, keys[4]); + aesdec8(&mut b, keys[3]); + aesdec8(&mut b, keys[2]); + aesdec8(&mut b, keys[1]); + aesdeclast8(&mut b, keys[0]); + store8(out_ptr, b); +} + +macro_rules! expand_round { + ($keys:expr, $pos:expr, $round:expr) => { + let mut t1 = $keys[$pos - 1]; + let mut t2; + let mut t3; + + t2 = _mm_aeskeygenassist_si128(t1, $round); + t2 = _mm_shuffle_epi32(t2, 0xff); + t3 = _mm_slli_si128(t1, 0x4); + t1 = _mm_xor_si128(t1, t3); + t3 = _mm_slli_si128(t3, 0x4); + t1 = _mm_xor_si128(t1, t3); + t3 = _mm_slli_si128(t3, 0x4); + t1 = _mm_xor_si128(t1, t3); + t1 = _mm_xor_si128(t1, t2); + + $keys[$pos] = t1; + }; +} + +#[inline] +#[target_feature(enable = "aes")] +pub(super) unsafe fn expand_key(key: &[u8; 16]) -> RoundKeys { + // SAFETY: `RoundKeys` is a `[__m128i; 11]` which can be initialized + // with all zeroes. + let mut keys: RoundKeys = mem::zeroed(); + + let k = _mm_loadu_si128(key.as_ptr() as *const __m128i); + keys[0] = k; + + expand_round!(keys, 1, 0x01); + expand_round!(keys, 2, 0x02); + expand_round!(keys, 3, 0x04); + expand_round!(keys, 4, 0x08); + expand_round!(keys, 5, 0x10); + expand_round!(keys, 6, 0x20); + expand_round!(keys, 7, 0x40); + expand_round!(keys, 8, 0x80); + expand_round!(keys, 9, 0x1B); + expand_round!(keys, 10, 0x36); + + keys +} + +#[inline] +#[target_feature(enable = "aes")] +pub(super) unsafe fn inv_expanded_keys(keys: &RoundKeys) -> RoundKeys { + [ + keys[0], + _mm_aesimc_si128(keys[1]), + _mm_aesimc_si128(keys[2]), + _mm_aesimc_si128(keys[3]), + _mm_aesimc_si128(keys[4]), + _mm_aesimc_si128(keys[5]), + _mm_aesimc_si128(keys[6]), + _mm_aesimc_si128(keys[7]), + _mm_aesimc_si128(keys[8]), + _mm_aesimc_si128(keys[9]), + keys[10], + ] +} diff --git a/src/ni/aes192.rs b/src/ni/aes192.rs new file mode 100644 index 0000000..eee1f21 --- /dev/null +++ b/src/ni/aes192.rs @@ -0,0 +1,197 @@ +use super::{arch::*, utils::*}; +use crate::{Block, Block8}; +use cipher::inout::InOut; +use core::{mem, ptr}; + +/// AES-192 round keys +pub(super) type RoundKeys = [__m128i; 13]; + +#[inline] +#[target_feature(enable = "aes")] +pub(super) unsafe fn encrypt1(keys: &RoundKeys, block: InOut<'_, '_, Block>) { + let (in_ptr, out_ptr) = block.into_raw(); + let mut b = _mm_loadu_si128(in_ptr as *const __m128i); + b = _mm_xor_si128(b, keys[0]); + b = _mm_aesenc_si128(b, keys[1]); + b = _mm_aesenc_si128(b, keys[2]); + b = _mm_aesenc_si128(b, keys[3]); + b = _mm_aesenc_si128(b, keys[4]); + b = _mm_aesenc_si128(b, keys[5]); + b = _mm_aesenc_si128(b, keys[6]); + b = _mm_aesenc_si128(b, keys[7]); + b = _mm_aesenc_si128(b, keys[8]); + b = _mm_aesenc_si128(b, keys[9]); + b = _mm_aesenc_si128(b, keys[10]); + b = _mm_aesenc_si128(b, keys[11]); + b = _mm_aesenclast_si128(b, keys[12]); + _mm_storeu_si128(out_ptr as *mut __m128i, b); +} + +#[inline] +#[target_feature(enable = "aes")] +pub(super) unsafe fn encrypt8(keys: &RoundKeys, blocks: InOut<'_, '_, Block8>) { + let (in_ptr, out_ptr) = blocks.into_raw(); + let mut b = load8(in_ptr); + xor8(&mut b, keys[0]); + aesenc8(&mut b, keys[1]); + aesenc8(&mut b, keys[2]); + aesenc8(&mut b, keys[3]); + aesenc8(&mut b, keys[4]); + aesenc8(&mut b, keys[5]); + aesenc8(&mut b, keys[6]); + aesenc8(&mut b, keys[7]); + aesenc8(&mut b, keys[8]); + aesenc8(&mut b, keys[9]); + aesenc8(&mut b, keys[10]); + aesenc8(&mut b, keys[11]); + aesenclast8(&mut b, keys[12]); + store8(out_ptr, b); +} + +#[inline] +#[target_feature(enable = "aes")] +pub(super) unsafe fn decrypt1(keys: &RoundKeys, block: InOut<'_, '_, Block>) { + let (in_ptr, out_ptr) = block.into_raw(); + let mut b = _mm_loadu_si128(in_ptr as *const __m128i); + b = _mm_xor_si128(b, keys[12]); + b = _mm_aesdec_si128(b, keys[11]); + b = _mm_aesdec_si128(b, keys[10]); + b = _mm_aesdec_si128(b, keys[9]); + b = _mm_aesdec_si128(b, keys[8]); + b = _mm_aesdec_si128(b, keys[7]); + b = _mm_aesdec_si128(b, keys[6]); + b = _mm_aesdec_si128(b, keys[5]); + b = _mm_aesdec_si128(b, keys[4]); + b = _mm_aesdec_si128(b, keys[3]); + b = _mm_aesdec_si128(b, keys[2]); + b = _mm_aesdec_si128(b, keys[1]); + b = _mm_aesdeclast_si128(b, keys[0]); + _mm_storeu_si128(out_ptr as *mut __m128i, b); +} + +#[inline] +#[target_feature(enable = "aes")] +pub(super) unsafe fn decrypt8(keys: &RoundKeys, blocks: InOut<'_, '_, Block8>) { + let (in_ptr, out_ptr) = blocks.into_raw(); + let mut b = load8(in_ptr); + xor8(&mut b, keys[12]); + aesdec8(&mut b, keys[11]); + aesdec8(&mut b, keys[10]); + aesdec8(&mut b, keys[9]); + aesdec8(&mut b, keys[8]); + aesdec8(&mut b, keys[7]); + aesdec8(&mut b, keys[6]); + aesdec8(&mut b, keys[5]); + aesdec8(&mut b, keys[4]); + aesdec8(&mut b, keys[3]); + aesdec8(&mut b, keys[2]); + aesdec8(&mut b, keys[1]); + aesdeclast8(&mut b, keys[0]); + store8(out_ptr, b); +} + +macro_rules! expand_round { + ($t1:expr, $t3:expr, $round:expr) => {{ + let mut t1 = $t1; + let mut t2; + let mut t3 = $t3; + let mut t4; + + t2 = _mm_aeskeygenassist_si128(t3, $round); + t2 = _mm_shuffle_epi32(t2, 0x55); + t4 = _mm_slli_si128(t1, 0x4); + t1 = _mm_xor_si128(t1, t4); + t4 = _mm_slli_si128(t4, 0x4); + t1 = _mm_xor_si128(t1, t4); + t4 = _mm_slli_si128(t4, 0x4); + t1 = _mm_xor_si128(t1, t4); + t1 = _mm_xor_si128(t1, t2); + t2 = _mm_shuffle_epi32(t1, 0xff); + t4 = _mm_slli_si128(t3, 0x4); + t3 = _mm_xor_si128(t3, t4); + t3 = _mm_xor_si128(t3, t2); + + (t1, t3) + }}; +} + +macro_rules! shuffle { + ($a:expr, $b:expr, $imm:expr) => { + mem::transmute::<_, __m128i>(_mm_shuffle_pd(mem::transmute($a), mem::transmute($b), $imm)) + }; +} + +#[inline] +#[target_feature(enable = "aes")] +pub(super) unsafe fn expand_key(key: &[u8; 24]) -> RoundKeys { + // SAFETY: `RoundKeys` is a `[__m128i; 13]` which can be initialized + // with all zeroes. + let mut keys: RoundKeys = mem::zeroed(); + // we are being extra pedantic here to remove out-of-bound access. + // this should be optimized out into movups, movsd sequence + // note that unaligned load MUST be used here, even though we read + // from the array (compiler missoptimizes aligned load) + let (k0, k1l) = { + let mut t = [0u8; 32]; + ptr::write(t.as_mut_ptr() as *mut [u8; 24], *key); + + ( + _mm_loadu_si128(t.as_ptr() as *const __m128i), + _mm_loadu_si128(t.as_ptr().offset(16) as *const __m128i), + ) + }; + + keys[0] = k0; + + let (k1_2, k2r) = expand_round!(k0, k1l, 0x01); + keys[1] = shuffle!(k1l, k1_2, 0); + keys[2] = shuffle!(k1_2, k2r, 1); + + let (k3, k4l) = expand_round!(k1_2, k2r, 0x02); + keys[3] = k3; + + let (k4_5, k5r) = expand_round!(k3, k4l, 0x04); + let k4 = shuffle!(k4l, k4_5, 0); + let k5 = shuffle!(k4_5, k5r, 1); + keys[4] = k4; + keys[5] = k5; + + let (k6, k7l) = expand_round!(k4_5, k5r, 0x08); + keys[6] = k6; + + let (k7_8, k8r) = expand_round!(k6, k7l, 0x10); + keys[7] = shuffle!(k7l, k7_8, 0); + keys[8] = shuffle!(k7_8, k8r, 1); + + let (k9, k10l) = expand_round!(k7_8, k8r, 0x20); + keys[9] = k9; + + let (k10_11, k11r) = expand_round!(k9, k10l, 0x40); + keys[10] = shuffle!(k10l, k10_11, 0); + keys[11] = shuffle!(k10_11, k11r, 1); + + let (k12, _) = expand_round!(k10_11, k11r, 0x80); + keys[12] = k12; + + keys +} + +#[inline] +#[target_feature(enable = "aes")] +pub(super) unsafe fn inv_expanded_keys(keys: &RoundKeys) -> RoundKeys { + [ + keys[0], + _mm_aesimc_si128(keys[1]), + _mm_aesimc_si128(keys[2]), + _mm_aesimc_si128(keys[3]), + _mm_aesimc_si128(keys[4]), + _mm_aesimc_si128(keys[5]), + _mm_aesimc_si128(keys[6]), + _mm_aesimc_si128(keys[7]), + _mm_aesimc_si128(keys[8]), + _mm_aesimc_si128(keys[9]), + _mm_aesimc_si128(keys[10]), + _mm_aesimc_si128(keys[11]), + keys[12], + ] +} diff --git a/src/ni/aes256.rs b/src/ni/aes256.rs new file mode 100644 index 0000000..bea090a --- /dev/null +++ b/src/ni/aes256.rs @@ -0,0 +1,196 @@ +use super::{arch::*, utils::*}; +use crate::{Block, Block8}; +use cipher::inout::InOut; +use core::mem; + +/// AES-192 round keys +pub(super) type RoundKeys = [__m128i; 15]; + +#[inline] +#[target_feature(enable = "aes")] +pub(super) unsafe fn encrypt1(keys: &RoundKeys, block: InOut<'_, '_, Block>) { + let (in_ptr, out_ptr) = block.into_raw(); + let mut b = _mm_loadu_si128(in_ptr as *const __m128i); + b = _mm_xor_si128(b, keys[0]); + b = _mm_aesenc_si128(b, keys[1]); + b = _mm_aesenc_si128(b, keys[2]); + b = _mm_aesenc_si128(b, keys[3]); + b = _mm_aesenc_si128(b, keys[4]); + b = _mm_aesenc_si128(b, keys[5]); + b = _mm_aesenc_si128(b, keys[6]); + b = _mm_aesenc_si128(b, keys[7]); + b = _mm_aesenc_si128(b, keys[8]); + b = _mm_aesenc_si128(b, keys[9]); + b = _mm_aesenc_si128(b, keys[10]); + b = _mm_aesenc_si128(b, keys[11]); + b = _mm_aesenc_si128(b, keys[12]); + b = _mm_aesenc_si128(b, keys[13]); + b = _mm_aesenclast_si128(b, keys[14]); + _mm_storeu_si128(out_ptr as *mut __m128i, b); +} + +#[inline] +#[target_feature(enable = "aes")] +pub(super) unsafe fn encrypt8(keys: &RoundKeys, blocks: InOut<'_, '_, Block8>) { + let (in_ptr, out_ptr) = blocks.into_raw(); + let mut b = load8(in_ptr); + xor8(&mut b, keys[0]); + aesenc8(&mut b, keys[1]); + aesenc8(&mut b, keys[2]); + aesenc8(&mut b, keys[3]); + aesenc8(&mut b, keys[4]); + aesenc8(&mut b, keys[5]); + aesenc8(&mut b, keys[6]); + aesenc8(&mut b, keys[7]); + aesenc8(&mut b, keys[8]); + aesenc8(&mut b, keys[9]); + aesenc8(&mut b, keys[10]); + aesenc8(&mut b, keys[11]); + aesenc8(&mut b, keys[12]); + aesenc8(&mut b, keys[13]); + aesenclast8(&mut b, keys[14]); + store8(out_ptr, b); +} + +#[inline] +#[target_feature(enable = "aes")] +pub(super) unsafe fn decrypt1(keys: &RoundKeys, block: InOut<'_, '_, Block>) { + let (in_ptr, out_ptr) = block.into_raw(); + let mut b = _mm_loadu_si128(in_ptr as *const __m128i); + b = _mm_xor_si128(b, keys[14]); + b = _mm_aesdec_si128(b, keys[13]); + b = _mm_aesdec_si128(b, keys[12]); + b = _mm_aesdec_si128(b, keys[11]); + b = _mm_aesdec_si128(b, keys[10]); + b = _mm_aesdec_si128(b, keys[9]); + b = _mm_aesdec_si128(b, keys[8]); + b = _mm_aesdec_si128(b, keys[7]); + b = _mm_aesdec_si128(b, keys[6]); + b = _mm_aesdec_si128(b, keys[5]); + b = _mm_aesdec_si128(b, keys[4]); + b = _mm_aesdec_si128(b, keys[3]); + b = _mm_aesdec_si128(b, keys[2]); + b = _mm_aesdec_si128(b, keys[1]); + b = _mm_aesdeclast_si128(b, keys[0]); + _mm_storeu_si128(out_ptr as *mut __m128i, b); +} + +#[inline] +#[target_feature(enable = "aes")] +pub(super) unsafe fn decrypt8(keys: &RoundKeys, blocks: InOut<'_, '_, Block8>) { + let (in_ptr, out_ptr) = blocks.into_raw(); + let mut b = load8(in_ptr); + xor8(&mut b, keys[14]); + aesdec8(&mut b, keys[13]); + aesdec8(&mut b, keys[12]); + aesdec8(&mut b, keys[11]); + aesdec8(&mut b, keys[10]); + aesdec8(&mut b, keys[9]); + aesdec8(&mut b, keys[8]); + aesdec8(&mut b, keys[7]); + aesdec8(&mut b, keys[6]); + aesdec8(&mut b, keys[5]); + aesdec8(&mut b, keys[4]); + aesdec8(&mut b, keys[3]); + aesdec8(&mut b, keys[2]); + aesdec8(&mut b, keys[1]); + aesdeclast8(&mut b, keys[0]); + store8(out_ptr, b); +} + +macro_rules! expand_round { + ($keys:expr, $pos:expr, $round:expr) => { + let mut t1 = $keys[$pos - 2]; + let mut t2; + let mut t3 = $keys[$pos - 1]; + let mut t4; + + t2 = _mm_aeskeygenassist_si128(t3, $round); + t2 = _mm_shuffle_epi32(t2, 0xff); + t4 = _mm_slli_si128(t1, 0x4); + t1 = _mm_xor_si128(t1, t4); + t4 = _mm_slli_si128(t4, 0x4); + t1 = _mm_xor_si128(t1, t4); + t4 = _mm_slli_si128(t4, 0x4); + t1 = _mm_xor_si128(t1, t4); + t1 = _mm_xor_si128(t1, t2); + + $keys[$pos] = t1; + + t4 = _mm_aeskeygenassist_si128(t1, 0x00); + t2 = _mm_shuffle_epi32(t4, 0xaa); + t4 = _mm_slli_si128(t3, 0x4); + t3 = _mm_xor_si128(t3, t4); + t4 = _mm_slli_si128(t4, 0x4); + t3 = _mm_xor_si128(t3, t4); + t4 = _mm_slli_si128(t4, 0x4); + t3 = _mm_xor_si128(t3, t4); + t3 = _mm_xor_si128(t3, t2); + + $keys[$pos + 1] = t3; + }; +} + +macro_rules! expand_round_last { + ($keys:expr, $pos:expr, $round:expr) => { + let mut t1 = $keys[$pos - 2]; + let mut t2; + let t3 = $keys[$pos - 1]; + let mut t4; + + t2 = _mm_aeskeygenassist_si128(t3, $round); + t2 = _mm_shuffle_epi32(t2, 0xff); + t4 = _mm_slli_si128(t1, 0x4); + t1 = _mm_xor_si128(t1, t4); + t4 = _mm_slli_si128(t4, 0x4); + t1 = _mm_xor_si128(t1, t4); + t4 = _mm_slli_si128(t4, 0x4); + t1 = _mm_xor_si128(t1, t4); + t1 = _mm_xor_si128(t1, t2); + + $keys[$pos] = t1; + }; +} + +#[inline(always)] +pub(super) unsafe fn expand_key(key: &[u8; 32]) -> RoundKeys { + // SAFETY: `RoundKeys` is a `[__m128i; 15]` which can be initialized + // with all zeroes. + let mut keys: RoundKeys = mem::zeroed(); + + let kp = key.as_ptr() as *const __m128i; + keys[0] = _mm_loadu_si128(kp); + keys[1] = _mm_loadu_si128(kp.add(1)); + + expand_round!(keys, 2, 0x01); + expand_round!(keys, 4, 0x02); + expand_round!(keys, 6, 0x04); + expand_round!(keys, 8, 0x08); + expand_round!(keys, 10, 0x10); + expand_round!(keys, 12, 0x20); + expand_round_last!(keys, 14, 0x40); + + keys +} + +#[inline] +#[target_feature(enable = "aes")] +pub(super) unsafe fn inv_expanded_keys(keys: &RoundKeys) -> RoundKeys { + [ + keys[0], + _mm_aesimc_si128(keys[1]), + _mm_aesimc_si128(keys[2]), + _mm_aesimc_si128(keys[3]), + _mm_aesimc_si128(keys[4]), + _mm_aesimc_si128(keys[5]), + _mm_aesimc_si128(keys[6]), + _mm_aesimc_si128(keys[7]), + _mm_aesimc_si128(keys[8]), + _mm_aesimc_si128(keys[9]), + _mm_aesimc_si128(keys[10]), + _mm_aesimc_si128(keys[11]), + _mm_aesimc_si128(keys[12]), + _mm_aesimc_si128(keys[13]), + keys[14], + ] +} diff --git a/src/ni/hazmat.rs b/src/ni/hazmat.rs new file mode 100644 index 0000000..a2a735a --- /dev/null +++ b/src/ni/hazmat.rs @@ -0,0 +1,80 @@ +//! Low-level "hazmat" AES functions: AES-NI support. +//! +//! Note: this isn't actually used in the `Aes128`/`Aes192`/`Aes256` +//! implementations in this crate, but instead provides raw AES-NI accelerated +//! access to the AES round function gated under the `hazmat` crate feature. + +use super::{ + arch::*, + utils::{load8, store8}, +}; +use crate::{Block, Block8}; + +/// AES cipher (encrypt) round function. +#[target_feature(enable = "aes")] +pub(crate) unsafe fn cipher_round(block: &mut Block, round_key: &Block) { + // Safety: `loadu` and `storeu` support unaligned access + let b = _mm_loadu_si128(block.as_ptr() as *const __m128i); + let k = _mm_loadu_si128(round_key.as_ptr() as *const __m128i); + let out = _mm_aesenc_si128(b, k); + _mm_storeu_si128(block.as_mut_ptr() as *mut __m128i, out); +} + +/// AES cipher (encrypt) round function: parallel version. +#[target_feature(enable = "aes")] +pub(crate) unsafe fn cipher_round_par(blocks: &mut Block8, round_keys: &Block8) { + let xmm_keys = load8(round_keys); + let mut xmm_blocks = load8(blocks); + + for i in 0..8 { + xmm_blocks[i] = _mm_aesenc_si128(xmm_blocks[i], xmm_keys[i]); + } + + store8(blocks, xmm_blocks); +} + +/// AES cipher (encrypt) round function. +#[target_feature(enable = "aes")] +pub(crate) unsafe fn equiv_inv_cipher_round(block: &mut Block, round_key: &Block) { + // Safety: `loadu` and `storeu` support unaligned access + let b = _mm_loadu_si128(block.as_ptr() as *const __m128i); + let k = _mm_loadu_si128(round_key.as_ptr() as *const __m128i); + let out = _mm_aesdec_si128(b, k); + _mm_storeu_si128(block.as_mut_ptr() as *mut __m128i, out); +} + +/// AES cipher (encrypt) round function: parallel version. +#[target_feature(enable = "aes")] +pub(crate) unsafe fn equiv_inv_cipher_round_par(blocks: &mut Block8, round_keys: &Block8) { + let xmm_keys = load8(round_keys); + let mut xmm_blocks = load8(blocks); + + for i in 0..8 { + xmm_blocks[i] = _mm_aesdec_si128(xmm_blocks[i], xmm_keys[i]); + } + + store8(blocks, xmm_blocks); +} + +/// AES mix columns function. +#[target_feature(enable = "aes")] +pub(crate) unsafe fn mix_columns(block: &mut Block) { + // Safety: `loadu` and `storeu` support unaligned access + let mut state = _mm_loadu_si128(block.as_ptr() as *const __m128i); + + // Emulate mix columns by performing three inverse mix columns operations + state = _mm_aesimc_si128(state); + state = _mm_aesimc_si128(state); + state = _mm_aesimc_si128(state); + + _mm_storeu_si128(block.as_mut_ptr() as *mut __m128i, state); +} + +/// AES inverse mix columns function. +#[target_feature(enable = "aes")] +pub(crate) unsafe fn inv_mix_columns(block: &mut Block) { + // Safety: `loadu` and `storeu` support unaligned access + let b = _mm_loadu_si128(block.as_ptr() as *const __m128i); + let out = _mm_aesimc_si128(b); + _mm_storeu_si128(block.as_mut_ptr() as *mut __m128i, out); +} diff --git a/src/ni/test_expand.rs b/src/ni/test_expand.rs new file mode 100644 index 0000000..6ab87c5 --- /dev/null +++ b/src/ni/test_expand.rs @@ -0,0 +1,275 @@ +use super::utils::check; +use hex_literal::hex; + +#[test] +fn aes128_expand_key_test() { + use super::aes128::expand_key; + + let keys = [0x00; 16]; + check( + unsafe { &expand_key(&keys) }, + &[ + [0x0000000000000000, 0x0000000000000000], + [0x6263636362636363, 0x6263636362636363], + [0x9b9898c9f9fbfbaa, 0x9b9898c9f9fbfbaa], + [0x90973450696ccffa, 0xf2f457330b0fac99], + [0xee06da7b876a1581, 0x759e42b27e91ee2b], + [0x7f2e2b88f8443e09, 0x8dda7cbbf34b9290], + [0xec614b851425758c, 0x99ff09376ab49ba7], + [0x217517873550620b, 0xacaf6b3cc61bf09b], + [0x0ef903333ba96138, 0x97060a04511dfa9f], + [0xb1d4d8e28a7db9da, 0x1d7bb3de4c664941], + [0xb4ef5bcb3e92e211, 0x23e951cf6f8f188e], + ], + ); + + let keys = [0xff; 16]; + check( + unsafe { &expand_key(&keys) }, + &[ + [0xffffffffffffffff, 0xffffffffffffffff], + [0xe8e9e9e917161616, 0xe8e9e9e917161616], + [0xadaeae19bab8b80f, 0x525151e6454747f0], + [0x090e2277b3b69a78, 0xe1e7cb9ea4a08c6e], + [0xe16abd3e52dc2746, 0xb33becd8179b60b6], + [0xe5baf3ceb766d488, 0x045d385013c658e6], + [0x71d07db3c6b6a93b, 0xc2eb916bd12dc98d], + [0xe90d208d2fbb89b6, 0xed5018dd3c7dd150], + [0x96337366b988fad0, 0x54d8e20d68a5335d], + [0x8bf03f233278c5f3, 0x66a027fe0e0514a3], + [0xd60a3588e472f07b, 0x82d2d7858cd7c326], + ], + ); + + let keys = hex!("000102030405060708090a0b0c0d0e0f"); + check( + unsafe { &expand_key(&keys) }, + &[ + [0x0001020304050607, 0x08090a0b0c0d0e0f], + [0xd6aa74fdd2af72fa, 0xdaa678f1d6ab76fe], + [0xb692cf0b643dbdf1, 0xbe9bc5006830b3fe], + [0xb6ff744ed2c2c9bf, 0x6c590cbf0469bf41], + [0x47f7f7bc95353e03, 0xf96c32bcfd058dfd], + [0x3caaa3e8a99f9deb, 0x50f3af57adf622aa], + [0x5e390f7df7a69296, 0xa7553dc10aa31f6b], + [0x14f9701ae35fe28c, 0x440adf4d4ea9c026], + [0x47438735a41c65b9, 0xe016baf4aebf7ad2], + [0x549932d1f0855768, 0x1093ed9cbe2c974e], + [0x13111d7fe3944a17, 0xf307a78b4d2b30c5], + ], + ); + + let keys = hex!("6920e299a5202a6d656e636869746f2a"); + check( + unsafe { &expand_key(&keys) }, + &[ + [0x6920e299a5202a6d, 0x656e636869746f2a], + [0xfa8807605fa82d0d, 0x3ac64e6553b2214f], + [0xcf75838d90ddae80, 0xaa1be0e5f9a9c1aa], + [0x180d2f1488d08194, 0x22cb6171db62a0db], + [0xbaed96ad323d1739, 0x10f67648cb94d693], + [0x881b4ab2ba265d8b, 0xaad02bc36144fd50], + [0xb34f195d096944d6, 0xa3b96f15c2fd9245], + [0xa7007778ae6933ae, 0x0dd05cbbcf2dcefe], + [0xff8bccf251e2ff5c, 0x5c32a3e7931f6d19], + [0x24b7182e7555e772, 0x29674495ba78298c], + [0xae127cdadb479ba8, 0xf220df3d4858f6b1], + ], + ); + + let keys = hex!("2b7e151628aed2a6abf7158809cf4f3c"); + check( + unsafe { &expand_key(&keys) }, + &[ + [0x2b7e151628aed2a6, 0xabf7158809cf4f3c], + [0xa0fafe1788542cb1, 0x23a339392a6c7605], + [0xf2c295f27a96b943, 0x5935807a7359f67f], + [0x3d80477d4716fe3e, 0x1e237e446d7a883b], + [0xef44a541a8525b7f, 0xb671253bdb0bad00], + [0xd4d1c6f87c839d87, 0xcaf2b8bc11f915bc], + [0x6d88a37a110b3efd, 0xdbf98641ca0093fd], + [0x4e54f70e5f5fc9f3, 0x84a64fb24ea6dc4f], + [0xead27321b58dbad2, 0x312bf5607f8d292f], + [0xac7766f319fadc21, 0x28d12941575c006e], + [0xd014f9a8c9ee2589, 0xe13f0cc8b6630ca6], + ], + ); +} + +#[test] +fn aes192_expand_key_test() { + use super::aes192::expand_key; + + let keys = [0x00; 24]; + check( + unsafe { &expand_key(&keys) }, + &[ + [0x0000000000000000, 0x0000000000000000], + [0x0000000000000000, 0x6263636362636363], + [0x6263636362636363, 0x6263636362636363], + [0x9b9898c9f9fbfbaa, 0x9b9898c9f9fbfbaa], + [0x9b9898c9f9fbfbaa, 0x90973450696ccffa], + [0xf2f457330b0fac99, 0x90973450696ccffa], + [0xc81d19a9a171d653, 0x53858160588a2df9], + [0xc81d19a9a171d653, 0x7bebf49bda9a22c8], + [0x891fa3a8d1958e51, 0x198897f8b8f941ab], + [0xc26896f718f2b43f, 0x91ed1797407899c6], + [0x59f00e3ee1094f95, 0x83ecbc0f9b1e0830], + [0x0af31fa74a8b8661, 0x137b885ff272c7ca], + [0x432ac886d834c0b6, 0xd2c7df11984c5970], + ], + ); + + let keys = [0xff; 24]; + check( + unsafe { &expand_key(&keys) }, + &[ + [0xffffffffffffffff, 0xffffffffffffffff], + [0xffffffffffffffff, 0xe8e9e9e917161616], + [0xe8e9e9e917161616, 0xe8e9e9e917161616], + [0xadaeae19bab8b80f, 0x525151e6454747f0], + [0xadaeae19bab8b80f, 0xc5c2d8ed7f7a60e2], + [0x2d2b3104686c76f4, 0xc5c2d8ed7f7a60e2], + [0x1712403f686820dd, 0x454311d92d2f672d], + [0xe8edbfc09797df22, 0x8f8cd3b7e7e4f36a], + [0xa2a7e2b38f88859e, 0x67653a5ef0f2e57c], + [0x2655c33bc1b13051, 0x6316d2e2ec9e577c], + [0x8bfb6d227b09885e, 0x67919b1aa620ab4b], + [0xc53679a929a82ed5, 0xa25343f7d95acba9], + [0x598e482fffaee364, 0x3a989acd1330b418], + ], + ); + + let keys = hex!("000102030405060708090a0b0c0d0e0f1011121314151617"); + check( + unsafe { &expand_key(&keys) }, + &[ + [0x0001020304050607, 0x08090a0b0c0d0e0f], + [0x1011121314151617, 0x5846f2f95c43f4fe], + [0x544afef55847f0fa, 0x4856e2e95c43f4fe], + [0x40f949b31cbabd4d, 0x48f043b810b7b342], + [0x58e151ab04a2a555, 0x7effb5416245080c], + [0x2ab54bb43a02f8f6, 0x62e3a95d66410c08], + [0xf501857297448d7e, 0xbdf1c6ca87f33e3c], + [0xe510976183519b69, 0x34157c9ea351f1e0], + [0x1ea0372a99530916, 0x7c439e77ff12051e], + [0xdd7e0e887e2fff68, 0x608fc842f9dcc154], + [0x859f5f237a8d5a3d, 0xc0c02952beefd63a], + [0xde601e7827bcdf2c, 0xa223800fd8aeda32], + [0xa4970a331a78dc09, 0xc418c271e3a41d5d], + ], + ); + + let keys = hex!("8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b"); + check( + unsafe { &expand_key(&keys) }, + &[ + [0x8e73b0f7da0e6452, 0xc810f32b809079e5], + [0x62f8ead2522c6b7b, 0xfe0c91f72402f5a5], + [0xec12068e6c827f6b, 0x0e7a95b95c56fec2], + [0x4db7b4bd69b54118, 0x85a74796e92538fd], + [0xe75fad44bb095386, 0x485af05721efb14f], + [0xa448f6d94d6dce24, 0xaa326360113b30e6], + [0xa25e7ed583b1cf9a, 0x27f939436a94f767], + [0xc0a69407d19da4e1, 0xec1786eb6fa64971], + [0x485f703222cb8755, 0xe26d135233f0b7b3], + [0x40beeb282f18a259, 0x6747d26b458c553e], + [0xa7e1466c9411f1df, 0x821f750aad07d753], + [0xca4005388fcc5006, 0x282d166abc3ce7b5], + [0xe98ba06f448c773c, 0x8ecc720401002202], + ], + ); +} + +#[test] +fn aes256_expand_key_test() { + use super::aes256::expand_key; + + let keys = [0x00; 32]; + check( + unsafe { &expand_key(&keys) }, + &[ + [0x0000000000000000, 0x0000000000000000], + [0x0000000000000000, 0x0000000000000000], + [0x6263636362636363, 0x6263636362636363], + [0xaafbfbfbaafbfbfb, 0xaafbfbfbaafbfbfb], + [0x6f6c6ccf0d0f0fac, 0x6f6c6ccf0d0f0fac], + [0x7d8d8d6ad7767691, 0x7d8d8d6ad7767691], + [0x5354edc15e5be26d, 0x31378ea23c38810e], + [0x968a81c141fcf750, 0x3c717a3aeb070cab], + [0x9eaa8f28c0f16d45, 0xf1c6e3e7cdfe62e9], + [0x2b312bdf6acddc8f, 0x56bca6b5bdbbaa1e], + [0x6406fd52a4f79017, 0x553173f098cf1119], + [0x6dbba90b07767584, 0x51cad331ec71792f], + [0xe7b0e89c4347788b, 0x16760b7b8eb91a62], + [0x74ed0ba1739b7e25, 0x2251ad14ce20d43b], + [0x10f80a1753bf729c, 0x45c979e7cb706385], + ], + ); + + let keys = [0xff; 32]; + check( + unsafe { &expand_key(&keys) }, + &[ + [0xffffffffffffffff, 0xffffffffffffffff], + [0xffffffffffffffff, 0xffffffffffffffff], + [0xe8e9e9e917161616, 0xe8e9e9e917161616], + [0x0fb8b8b8f0474747, 0x0fb8b8b8f0474747], + [0x4a4949655d5f5f73, 0xb5b6b69aa2a0a08c], + [0x355858dcc51f1f9b, 0xcaa7a7233ae0e064], + [0xafa80ae5f2f75596, 0x4741e30ce5e14380], + [0xeca0421129bf5d8a, 0xe318faa9d9f81acd], + [0xe60ab7d014fde246, 0x53bc014ab65d42ca], + [0xa2ec6e658b5333ef, 0x684bc946b1b3d38b], + [0x9b6c8a188f91685e, 0xdc2d69146a702bde], + [0xa0bd9f782beeac97, 0x43a565d1f216b65a], + [0xfc22349173b35ccf, 0xaf9e35dbc5ee1e05], + [0x0695ed132d7b4184, 0x6ede24559cc8920f], + [0x546d424f27de1e80, 0x88402b5b4dae355e], + ], + ); + + let keys = hex!("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"); + check( + unsafe { &expand_key(&keys) }, + &[ + [0x0001020304050607, 0x08090a0b0c0d0e0f], + [0x1011121314151617, 0x18191a1b1c1d1e1f], + [0xa573c29fa176c498, 0xa97fce93a572c09c], + [0x1651a8cd0244beda, 0x1a5da4c10640bade], + [0xae87dff00ff11b68, 0xa68ed5fb03fc1567], + [0x6de1f1486fa54f92, 0x75f8eb5373b8518d], + [0xc656827fc9a79917, 0x6f294cec6cd5598b], + [0x3de23a75524775e7, 0x27bf9eb45407cf39], + [0x0bdc905fc27b0948, 0xad5245a4c1871c2f], + [0x45f5a66017b2d387, 0x300d4d33640a820a], + [0x7ccff71cbeb4fe54, 0x13e6bbf0d261a7df], + [0xf01afafee7a82979, 0xd7a5644ab3afe640], + [0x2541fe719bf50025, 0x8813bbd55a721c0a], + [0x4e5a6699a9f24fe0, 0x7e572baacdf8cdea], + [0x24fc79ccbf0979e9, 0x371ac23c6d68de36], + ], + ); + + let keys = hex!("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); + check( + unsafe { &expand_key(&keys) }, + &[ + [0x603deb1015ca71be, 0x2b73aef0857d7781], + [0x1f352c073b6108d7, 0x2d9810a30914dff4], + [0x9ba354118e6925af, 0xa51a8b5f2067fcde], + [0xa8b09c1a93d194cd, 0xbe49846eb75d5b9a], + [0xd59aecb85bf3c917, 0xfee94248de8ebe96], + [0xb5a9328a2678a647, 0x983122292f6c79b3], + [0x812c81addadf48ba, 0x24360af2fab8b464], + [0x98c5bfc9bebd198e, 0x268c3ba709e04214], + [0x68007bacb2df3316, 0x96e939e46c518d80], + [0xc814e20476a9fb8a, 0x5025c02d59c58239], + [0xde1369676ccc5a71, 0xfa2563959674ee15], + [0x5886ca5d2e2f31d7, 0x7e0af1fa27cf73c3], + [0x749c47ab18501dda, 0xe2757e4f7401905a], + [0xcafaaae3e4d59b34, 0x9adf6acebd10190d], + [0xfe4890d1e6188d0b, 0x046df344706c631e], + ], + ); +} diff --git a/src/ni/utils.rs b/src/ni/utils.rs new file mode 100644 index 0000000..1bd6522 --- /dev/null +++ b/src/ni/utils.rs @@ -0,0 +1,92 @@ +//! Utility functions + +// TODO(tarcieri): check performance impact / generated assembly changes +#![allow(clippy::needless_range_loop)] + +use super::arch::*; +use crate::{Block, Block8}; + +pub type U128x8 = [__m128i; 8]; + +#[cfg(test)] +pub(crate) fn check(a: &[__m128i], b: &[[u64; 2]]) { + for (v1, v2) in a.iter().zip(b) { + let t1: [u64; 2] = unsafe { core::mem::transmute(*v1) }; + let t2 = [v2[0].to_be(), v2[1].to_be()]; + assert_eq!(t1, t2); + } +} + +#[inline(always)] +pub(crate) fn load8(blocks: *const Block8) -> U128x8 { + unsafe { + let p = blocks as *const Block; + [ + _mm_loadu_si128(p.add(0) as *const __m128i), + _mm_loadu_si128(p.add(1) as *const __m128i), + _mm_loadu_si128(p.add(2) as *const __m128i), + _mm_loadu_si128(p.add(3) as *const __m128i), + _mm_loadu_si128(p.add(4) as *const __m128i), + _mm_loadu_si128(p.add(5) as *const __m128i), + _mm_loadu_si128(p.add(6) as *const __m128i), + _mm_loadu_si128(p.add(7) as *const __m128i), + ] + } +} + +#[inline(always)] +pub(crate) fn store8(blocks: *mut Block8, b: U128x8) { + unsafe { + let p = blocks as *mut Block; + _mm_storeu_si128(p.add(0) as *mut __m128i, b[0]); + _mm_storeu_si128(p.add(1) as *mut __m128i, b[1]); + _mm_storeu_si128(p.add(2) as *mut __m128i, b[2]); + _mm_storeu_si128(p.add(3) as *mut __m128i, b[3]); + _mm_storeu_si128(p.add(4) as *mut __m128i, b[4]); + _mm_storeu_si128(p.add(5) as *mut __m128i, b[5]); + _mm_storeu_si128(p.add(6) as *mut __m128i, b[6]); + _mm_storeu_si128(p.add(7) as *mut __m128i, b[7]); + } +} + +#[inline(always)] +pub(crate) fn xor8(b: &mut U128x8, key: __m128i) { + unsafe { + b[0] = _mm_xor_si128(b[0], key); + b[1] = _mm_xor_si128(b[1], key); + b[2] = _mm_xor_si128(b[2], key); + b[3] = _mm_xor_si128(b[3], key); + b[4] = _mm_xor_si128(b[4], key); + b[5] = _mm_xor_si128(b[5], key); + b[6] = _mm_xor_si128(b[6], key); + b[7] = _mm_xor_si128(b[7], key); + } +} + +#[inline(always)] +pub(crate) fn aesenc8(buffer: &mut U128x8, key: __m128i) { + for i in 0..8 { + buffer[i] = unsafe { _mm_aesenc_si128(buffer[i], key) }; + } +} + +#[inline(always)] +pub(crate) fn aesenclast8(buffer: &mut U128x8, key: __m128i) { + for i in 0..8 { + buffer[i] = unsafe { _mm_aesenclast_si128(buffer[i], key) }; + } +} + +#[inline(always)] +pub(crate) fn aesdec8(buffer: &mut U128x8, key: __m128i) { + for i in 0..8 { + buffer[i] = unsafe { _mm_aesdec_si128(buffer[i], key) }; + } +} + +#[inline(always)] +pub(crate) fn aesdeclast8(buffer: &mut U128x8, key: __m128i) { + for i in 0..8 { + buffer[i] = unsafe { _mm_aesdeclast_si128(buffer[i], key) }; + } +} diff --git a/src/soft.rs b/src/soft.rs new file mode 100644 index 0000000..5f90b1e --- /dev/null +++ b/src/soft.rs @@ -0,0 +1,342 @@ +//! AES block cipher constant-time implementation. +//! +//! The implementation uses a technique called [fixslicing][1], an improved +//! form of bitslicing which represents ciphers in a way which enables +//! very efficient constant-time implementations in software. +//! +//! [1]: https://eprint.iacr.org/2020/1123.pdf + +#![deny(unsafe_code)] + +#[cfg_attr(not(target_pointer_width = "64"), path = "soft/fixslice32.rs")] +#[cfg_attr(target_pointer_width = "64", path = "soft/fixslice64.rs")] +pub(crate) mod fixslice; + +use crate::Block; +use cipher::{ + consts::{U16, U24, U32}, + inout::InOut, + AlgorithmName, BlockBackend, BlockCipher, BlockClosure, BlockDecrypt, BlockEncrypt, + BlockSizeUser, Key, KeyInit, KeySizeUser, ParBlocksSizeUser, +}; +use core::fmt; +use fixslice::{BatchBlocks, FixsliceBlocks, FixsliceKeys128, FixsliceKeys192, FixsliceKeys256}; + +macro_rules! define_aes_impl { + ( + $name:tt, + $name_enc:ident, + $name_dec:ident, + $name_back_enc:ident, + $name_back_dec:ident, + $key_size:ty, + $fixslice_keys:ty, + $fixslice_key_schedule:path, + $fixslice_decrypt:path, + $fixslice_encrypt:path, + $doc:expr $(,)? + ) => { + #[doc=$doc] + #[doc = "block cipher"] + #[derive(Clone)] + pub struct $name { + keys: $fixslice_keys, + } + + impl $name { + #[inline(always)] + pub(crate) fn get_enc_backend(&self) -> $name_back_enc<'_> { + $name_back_enc(self) + } + + #[inline(always)] + pub(crate) fn get_dec_backend(&self) -> $name_back_dec<'_> { + $name_back_dec(self) + } + } + + impl KeySizeUser for $name { + type KeySize = $key_size; + } + + impl KeyInit for $name { + #[inline] + fn new(key: &Key) -> Self { + Self { + keys: $fixslice_key_schedule(key.as_ref()), + } + } + } + + impl BlockSizeUser for $name { + type BlockSize = U16; + } + + impl BlockCipher for $name {} + + impl BlockEncrypt for $name { + fn encrypt_with_backend(&self, f: impl BlockClosure) { + f.call(&mut self.get_enc_backend()) + } + } + + impl BlockDecrypt for $name { + fn decrypt_with_backend(&self, f: impl BlockClosure) { + f.call(&mut self.get_dec_backend()) + } + } + + impl From<$name_enc> for $name { + #[inline] + fn from(enc: $name_enc) -> $name { + enc.inner + } + } + + impl From<&$name_enc> for $name { + #[inline] + fn from(enc: &$name_enc) -> $name { + enc.inner.clone() + } + } + + impl fmt::Debug for $name { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str(concat!(stringify!($name), " { .. }")) + } + } + + impl AlgorithmName for $name { + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(stringify!($name)) + } + } + + impl Drop for $name { + #[inline] + fn drop(&mut self) { + #[cfg(feature = "zeroize")] + zeroize::Zeroize::zeroize(&mut self.keys); + } + } + + #[cfg(feature = "zeroize")] + impl zeroize::ZeroizeOnDrop for $name {} + + #[doc=$doc] + #[doc = "block cipher (encrypt-only)"] + #[derive(Clone)] + pub struct $name_enc { + inner: $name, + } + + impl $name_enc { + #[inline(always)] + pub(crate) fn get_enc_backend(&self) -> $name_back_enc<'_> { + self.inner.get_enc_backend() + } + } + + impl BlockCipher for $name_enc {} + + impl KeySizeUser for $name_enc { + type KeySize = $key_size; + } + + impl KeyInit for $name_enc { + #[inline(always)] + fn new(key: &Key) -> Self { + let inner = $name::new(key); + Self { inner } + } + } + + impl BlockSizeUser for $name_enc { + type BlockSize = U16; + } + + impl BlockEncrypt for $name_enc { + fn encrypt_with_backend(&self, f: impl BlockClosure) { + f.call(&mut self.get_enc_backend()) + } + } + + impl fmt::Debug for $name_enc { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str(concat!(stringify!($name_enc), " { .. }")) + } + } + + impl AlgorithmName for $name_enc { + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(stringify!($name_enc)) + } + } + + #[cfg(feature = "zeroize")] + impl zeroize::ZeroizeOnDrop for $name_enc {} + + #[doc=$doc] + #[doc = "block cipher (decrypt-only)"] + #[derive(Clone)] + pub struct $name_dec { + inner: $name, + } + + impl $name_dec { + #[inline(always)] + pub(crate) fn get_dec_backend(&self) -> $name_back_dec<'_> { + self.inner.get_dec_backend() + } + } + + impl BlockCipher for $name_dec {} + + impl KeySizeUser for $name_dec { + type KeySize = $key_size; + } + + impl KeyInit for $name_dec { + #[inline(always)] + fn new(key: &Key) -> Self { + let inner = $name::new(key); + Self { inner } + } + } + + impl From<$name_enc> for $name_dec { + #[inline] + fn from(enc: $name_enc) -> $name_dec { + Self { inner: enc.inner } + } + } + + impl From<&$name_enc> for $name_dec { + #[inline] + fn from(enc: &$name_enc) -> $name_dec { + Self { + inner: enc.inner.clone(), + } + } + } + + impl BlockSizeUser for $name_dec { + type BlockSize = U16; + } + + impl BlockDecrypt for $name_dec { + fn decrypt_with_backend(&self, f: impl BlockClosure) { + f.call(&mut self.get_dec_backend()); + } + } + + impl fmt::Debug for $name_dec { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str(concat!(stringify!($name_dec), " { .. }")) + } + } + + impl AlgorithmName for $name_dec { + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(stringify!($name_dec)) + } + } + + #[cfg(feature = "zeroize")] + impl zeroize::ZeroizeOnDrop for $name_dec {} + + pub(crate) struct $name_back_enc<'a>(&'a $name); + + impl<'a> BlockSizeUser for $name_back_enc<'a> { + type BlockSize = U16; + } + + impl<'a> ParBlocksSizeUser for $name_back_enc<'a> { + type ParBlocksSize = FixsliceBlocks; + } + + impl<'a> BlockBackend for $name_back_enc<'a> { + #[inline(always)] + fn proc_block(&mut self, mut block: InOut<'_, '_, Block>) { + let mut blocks = BatchBlocks::default(); + blocks[0] = block.clone_in().into(); + let res = $fixslice_encrypt(&self.0.keys, &blocks); + *block.get_out() = res[0].into(); + } + + #[inline(always)] + fn proc_par_blocks(&mut self, mut blocks: InOut<'_, '_, BatchBlocks>) { + let res = $fixslice_encrypt(&self.0.keys, blocks.get_in()); + *blocks.get_out() = res; + } + } + + pub(crate) struct $name_back_dec<'a>(&'a $name); + + impl<'a> BlockSizeUser for $name_back_dec<'a> { + type BlockSize = U16; + } + + impl<'a> ParBlocksSizeUser for $name_back_dec<'a> { + type ParBlocksSize = FixsliceBlocks; + } + + impl<'a> BlockBackend for $name_back_dec<'a> { + #[inline(always)] + fn proc_block(&mut self, mut block: InOut<'_, '_, Block>) { + let mut blocks = BatchBlocks::default(); + blocks[0] = block.clone_in(); + let res = $fixslice_decrypt(&self.0.keys, &blocks); + *block.get_out() = res[0]; + } + + #[inline(always)] + fn proc_par_blocks(&mut self, mut blocks: InOut<'_, '_, BatchBlocks>) { + let res = $fixslice_decrypt(&self.0.keys, blocks.get_in()); + *blocks.get_out() = res; + } + } + }; +} + +define_aes_impl!( + Aes128, + Aes128Enc, + Aes128Dec, + Aes128BackEnc, + Aes128BackDec, + U16, + FixsliceKeys128, + fixslice::aes128_key_schedule, + fixslice::aes128_decrypt, + fixslice::aes128_encrypt, + "AES-128", +); + +define_aes_impl!( + Aes192, + Aes192Enc, + Aes192Dec, + Aes192BackEnc, + Aes192BackDec, + U24, + FixsliceKeys192, + fixslice::aes192_key_schedule, + fixslice::aes192_decrypt, + fixslice::aes192_encrypt, + "AES-192", +); + +define_aes_impl!( + Aes256, + Aes256Enc, + Aes256Dec, + Aes256BackEnc, + Aes256BackDec, + U32, + FixsliceKeys256, + fixslice::aes256_key_schedule, + fixslice::aes256_decrypt, + fixslice::aes256_encrypt, + "AES-256", +); diff --git a/src/soft/fixslice32.rs b/src/soft/fixslice32.rs new file mode 100644 index 0000000..45b674d --- /dev/null +++ b/src/soft/fixslice32.rs @@ -0,0 +1,1479 @@ +//! Fixsliced implementations of AES-128, AES-192 and AES-256 (32-bit) +//! adapted from the C implementation +//! +//! All implementations are fully bitsliced and do not rely on any +//! Look-Up Table (LUT). +//! +//! See the paper at for more details. +//! +//! # Author (original C code) +//! +//! Alexandre Adomnicai, Nanyang Technological University, Singapore +//! +//! +//! Originally licensed MIT. Relicensed as Apache 2.0+MIT with permission. + +#![allow(clippy::unreadable_literal)] + +use crate::Block; +use cipher::{consts::U2, generic_array::GenericArray}; + +/// AES block batch size for this implementation +pub(crate) type FixsliceBlocks = U2; + +pub(crate) type BatchBlocks = GenericArray; + +/// AES-128 round keys +pub(crate) type FixsliceKeys128 = [u32; 88]; + +/// AES-192 round keys +pub(crate) type FixsliceKeys192 = [u32; 104]; + +/// AES-256 round keys +pub(crate) type FixsliceKeys256 = [u32; 120]; + +/// 256-bit internal state +pub(crate) type State = [u32; 8]; + +/// Fully bitsliced AES-128 key schedule to match the fully-fixsliced representation. +pub(crate) fn aes128_key_schedule(key: &[u8; 16]) -> FixsliceKeys128 { + let mut rkeys = [0u32; 88]; + + bitslice(&mut rkeys[..8], key, key); + + let mut rk_off = 0; + for rcon in 0..10 { + memshift32(&mut rkeys, rk_off); + rk_off += 8; + + sub_bytes(&mut rkeys[rk_off..(rk_off + 8)]); + sub_bytes_nots(&mut rkeys[rk_off..(rk_off + 8)]); + + if rcon < 8 { + add_round_constant_bit(&mut rkeys[rk_off..(rk_off + 8)], rcon); + } else { + add_round_constant_bit(&mut rkeys[rk_off..(rk_off + 8)], rcon - 8); + add_round_constant_bit(&mut rkeys[rk_off..(rk_off + 8)], rcon - 7); + add_round_constant_bit(&mut rkeys[rk_off..(rk_off + 8)], rcon - 5); + add_round_constant_bit(&mut rkeys[rk_off..(rk_off + 8)], rcon - 4); + } + + xor_columns(&mut rkeys, rk_off, 8, ror_distance(1, 3)); + } + + // Adjust to match fixslicing format + #[cfg(aes_compact)] + { + for i in (8..88).step_by(16) { + inv_shift_rows_1(&mut rkeys[i..(i + 8)]); + } + } + #[cfg(not(aes_compact))] + { + for i in (8..72).step_by(32) { + inv_shift_rows_1(&mut rkeys[i..(i + 8)]); + inv_shift_rows_2(&mut rkeys[(i + 8)..(i + 16)]); + inv_shift_rows_3(&mut rkeys[(i + 16)..(i + 24)]); + } + inv_shift_rows_1(&mut rkeys[72..80]); + } + + // Account for NOTs removed from sub_bytes + for i in 1..11 { + sub_bytes_nots(&mut rkeys[(i * 8)..(i * 8 + 8)]); + } + + rkeys +} + +/// Fully bitsliced AES-192 key schedule to match the fully-fixsliced representation. +pub(crate) fn aes192_key_schedule(key: &[u8; 24]) -> FixsliceKeys192 { + let mut rkeys = [0u32; 104]; + let mut tmp = [0u32; 8]; + + bitslice(&mut rkeys[..8], &key[..16], &key[..16]); + bitslice(&mut tmp, &key[8..], &key[8..]); + + let mut rcon = 0; + let mut rk_off = 8; + + loop { + for i in 0..8 { + rkeys[rk_off + i] = + (0x0f0f0f0f & (tmp[i] >> 4)) | (0xf0f0f0f0 & (rkeys[(rk_off - 8) + i] << 4)); + } + + sub_bytes(&mut tmp); + sub_bytes_nots(&mut tmp); + + add_round_constant_bit(&mut tmp, rcon); + rcon += 1; + + for i in 0..8 { + let mut ti = rkeys[rk_off + i]; + ti ^= 0x30303030 & ror(tmp[i], ror_distance(1, 1)); + ti ^= 0xc0c0c0c0 & (ti << 2); + tmp[i] = ti; + } + rkeys[rk_off..(rk_off + 8)].copy_from_slice(&tmp); + rk_off += 8; + + for i in 0..8 { + let ui = tmp[i]; + let mut ti = (0x0f0f0f0f & (rkeys[(rk_off - 16) + i] >> 4)) | (0xf0f0f0f0 & (ui << 4)); + ti ^= 0x03030303 & (ui >> 6); + tmp[i] = + ti ^ (0xfcfcfcfc & (ti << 2)) ^ (0xf0f0f0f0 & (ti << 4)) ^ (0xc0c0c0c0 & (ti << 6)); + } + rkeys[rk_off..(rk_off + 8)].copy_from_slice(&tmp); + rk_off += 8; + + sub_bytes(&mut tmp); + sub_bytes_nots(&mut tmp); + + add_round_constant_bit(&mut tmp, rcon); + rcon += 1; + + for i in 0..8 { + let mut ti = (0x0f0f0f0f & (rkeys[(rk_off - 16) + i] >> 4)) + | (0xf0f0f0f0 & (rkeys[(rk_off - 8) + i] << 4)); + ti ^= 0x03030303 & ror(tmp[i], ror_distance(1, 3)); + rkeys[rk_off + i] = + ti ^ (0xfcfcfcfc & (ti << 2)) ^ (0xf0f0f0f0 & (ti << 4)) ^ (0xc0c0c0c0 & (ti << 6)); + } + rk_off += 8; + + if rcon >= 8 { + break; + } + + for i in 0..8 { + let ui = rkeys[(rk_off - 8) + i]; + let mut ti = rkeys[(rk_off - 16) + i]; + ti ^= 0x30303030 & (ui >> 2); + ti ^= 0xc0c0c0c0 & (ti << 2); + tmp[i] = ti; + } + } + + // Adjust to match fixslicing format + #[cfg(aes_compact)] + { + for i in (8..104).step_by(16) { + inv_shift_rows_1(&mut rkeys[i..(i + 8)]); + } + } + #[cfg(not(aes_compact))] + { + for i in (0..96).step_by(32) { + inv_shift_rows_1(&mut rkeys[(i + 8)..(i + 16)]); + inv_shift_rows_2(&mut rkeys[(i + 16)..(i + 24)]); + inv_shift_rows_3(&mut rkeys[(i + 24)..(i + 32)]); + } + } + + // Account for NOTs removed from sub_bytes + for i in 1..13 { + sub_bytes_nots(&mut rkeys[(i * 8)..(i * 8 + 8)]); + } + + rkeys +} + +/// Fully bitsliced AES-256 key schedule to match the fully-fixsliced representation. +pub(crate) fn aes256_key_schedule(key: &[u8; 32]) -> FixsliceKeys256 { + let mut rkeys = [0u32; 120]; + + bitslice(&mut rkeys[..8], &key[..16], &key[..16]); + bitslice(&mut rkeys[8..16], &key[16..], &key[16..]); + + let mut rk_off = 8; + + let mut rcon = 0; + loop { + memshift32(&mut rkeys, rk_off); + rk_off += 8; + + sub_bytes(&mut rkeys[rk_off..(rk_off + 8)]); + sub_bytes_nots(&mut rkeys[rk_off..(rk_off + 8)]); + + add_round_constant_bit(&mut rkeys[rk_off..(rk_off + 8)], rcon); + xor_columns(&mut rkeys, rk_off, 16, ror_distance(1, 3)); + rcon += 1; + + if rcon == 7 { + break; + } + + memshift32(&mut rkeys, rk_off); + rk_off += 8; + + sub_bytes(&mut rkeys[rk_off..(rk_off + 8)]); + sub_bytes_nots(&mut rkeys[rk_off..(rk_off + 8)]); + + xor_columns(&mut rkeys, rk_off, 16, ror_distance(0, 3)); + } + + // Adjust to match fixslicing format + #[cfg(aes_compact)] + { + for i in (8..120).step_by(16) { + inv_shift_rows_1(&mut rkeys[i..(i + 8)]); + } + } + #[cfg(not(aes_compact))] + { + for i in (8..104).step_by(32) { + inv_shift_rows_1(&mut rkeys[i..(i + 8)]); + inv_shift_rows_2(&mut rkeys[(i + 8)..(i + 16)]); + inv_shift_rows_3(&mut rkeys[(i + 16)..(i + 24)]); + } + inv_shift_rows_1(&mut rkeys[104..112]); + } + + // Account for NOTs removed from sub_bytes + for i in 1..15 { + sub_bytes_nots(&mut rkeys[(i * 8)..(i * 8 + 8)]); + } + + rkeys +} + +/// Fully-fixsliced AES-128 decryption (the InvShiftRows is completely omitted). +/// +/// Decrypts four blocks in-place and in parallel. +pub(crate) fn aes128_decrypt(rkeys: &FixsliceKeys128, blocks: &BatchBlocks) -> BatchBlocks { + let mut state = State::default(); + + bitslice(&mut state, &blocks[0], &blocks[1]); + + add_round_key(&mut state, &rkeys[80..]); + inv_sub_bytes(&mut state); + + #[cfg(not(aes_compact))] + { + inv_shift_rows_2(&mut state); + } + + let mut rk_off = 72; + loop { + #[cfg(aes_compact)] + { + inv_shift_rows_2(&mut state); + } + + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + inv_mix_columns_1(&mut state); + inv_sub_bytes(&mut state); + rk_off -= 8; + + if rk_off == 0 { + break; + } + + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + inv_mix_columns_0(&mut state); + inv_sub_bytes(&mut state); + rk_off -= 8; + + #[cfg(not(aes_compact))] + { + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + inv_mix_columns_3(&mut state); + inv_sub_bytes(&mut state); + rk_off -= 8; + + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + inv_mix_columns_2(&mut state); + inv_sub_bytes(&mut state); + rk_off -= 8; + } + } + + add_round_key(&mut state, &rkeys[..8]); + + inv_bitslice(&state) +} + +/// Fully-fixsliced AES-128 encryption (the ShiftRows is completely omitted). +/// +/// Encrypts four blocks in-place and in parallel. +pub(crate) fn aes128_encrypt(rkeys: &FixsliceKeys128, blocks: &BatchBlocks) -> BatchBlocks { + let mut state = State::default(); + + bitslice(&mut state, &blocks[0], &blocks[1]); + + add_round_key(&mut state, &rkeys[..8]); + + let mut rk_off = 8; + loop { + sub_bytes(&mut state); + mix_columns_1(&mut state); + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + rk_off += 8; + + #[cfg(aes_compact)] + { + shift_rows_2(&mut state); + } + + if rk_off == 80 { + break; + } + + #[cfg(not(aes_compact))] + { + sub_bytes(&mut state); + mix_columns_2(&mut state); + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + rk_off += 8; + + sub_bytes(&mut state); + mix_columns_3(&mut state); + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + rk_off += 8; + } + + sub_bytes(&mut state); + mix_columns_0(&mut state); + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + rk_off += 8; + } + + #[cfg(not(aes_compact))] + { + shift_rows_2(&mut state); + } + + sub_bytes(&mut state); + add_round_key(&mut state, &rkeys[80..]); + + inv_bitslice(&state) +} + +/// Fully-fixsliced AES-192 decryption (the InvShiftRows is completely omitted). +/// +/// Decrypts four blocks in-place and in parallel. +pub(crate) fn aes192_decrypt(rkeys: &FixsliceKeys192, blocks: &BatchBlocks) -> BatchBlocks { + let mut state = State::default(); + + bitslice(&mut state, &blocks[0], &blocks[1]); + + add_round_key(&mut state, &rkeys[96..]); + inv_sub_bytes(&mut state); + + let mut rk_off = 88; + loop { + #[cfg(aes_compact)] + { + inv_shift_rows_2(&mut state); + } + #[cfg(not(aes_compact))] + { + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + inv_mix_columns_3(&mut state); + inv_sub_bytes(&mut state); + rk_off -= 8; + + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + inv_mix_columns_2(&mut state); + inv_sub_bytes(&mut state); + rk_off -= 8; + } + + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + inv_mix_columns_1(&mut state); + inv_sub_bytes(&mut state); + rk_off -= 8; + + if rk_off == 0 { + break; + } + + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + inv_mix_columns_0(&mut state); + inv_sub_bytes(&mut state); + rk_off -= 8; + } + + add_round_key(&mut state, &rkeys[..8]); + + inv_bitslice(&state) +} + +/// Fully-fixsliced AES-192 encryption (the ShiftRows is completely omitted). +/// +/// Encrypts four blocks in-place and in parallel. +pub(crate) fn aes192_encrypt(rkeys: &FixsliceKeys192, blocks: &BatchBlocks) -> BatchBlocks { + let mut state = State::default(); + + bitslice(&mut state, &blocks[0], &blocks[1]); + + add_round_key(&mut state, &rkeys[..8]); + + let mut rk_off = 8; + loop { + sub_bytes(&mut state); + mix_columns_1(&mut state); + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + rk_off += 8; + + #[cfg(aes_compact)] + { + shift_rows_2(&mut state); + } + #[cfg(not(aes_compact))] + { + sub_bytes(&mut state); + mix_columns_2(&mut state); + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + rk_off += 8; + + sub_bytes(&mut state); + mix_columns_3(&mut state); + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + rk_off += 8; + } + + if rk_off == 96 { + break; + } + + sub_bytes(&mut state); + mix_columns_0(&mut state); + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + rk_off += 8; + } + + sub_bytes(&mut state); + add_round_key(&mut state, &rkeys[96..]); + + inv_bitslice(&state) +} + +/// Fully-fixsliced AES-256 decryption (the InvShiftRows is completely omitted). +/// +/// Decrypts four blocks in-place and in parallel. +pub(crate) fn aes256_decrypt(rkeys: &FixsliceKeys256, blocks: &BatchBlocks) -> BatchBlocks { + let mut state = State::default(); + + bitslice(&mut state, &blocks[0], &blocks[1]); + + add_round_key(&mut state, &rkeys[112..]); + inv_sub_bytes(&mut state); + + #[cfg(not(aes_compact))] + { + inv_shift_rows_2(&mut state); + } + + let mut rk_off = 104; + loop { + #[cfg(aes_compact)] + { + inv_shift_rows_2(&mut state); + } + + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + inv_mix_columns_1(&mut state); + inv_sub_bytes(&mut state); + rk_off -= 8; + + if rk_off == 0 { + break; + } + + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + inv_mix_columns_0(&mut state); + inv_sub_bytes(&mut state); + rk_off -= 8; + + #[cfg(not(aes_compact))] + { + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + inv_mix_columns_3(&mut state); + inv_sub_bytes(&mut state); + rk_off -= 8; + + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + inv_mix_columns_2(&mut state); + inv_sub_bytes(&mut state); + rk_off -= 8; + } + } + + add_round_key(&mut state, &rkeys[..8]); + + inv_bitslice(&state) +} + +/// Fully-fixsliced AES-256 encryption (the ShiftRows is completely omitted). +/// +/// Encrypts four blocks in-place and in parallel. +pub(crate) fn aes256_encrypt(rkeys: &FixsliceKeys256, blocks: &BatchBlocks) -> BatchBlocks { + let mut state = State::default(); + + bitslice(&mut state, &blocks[0], &blocks[1]); + + add_round_key(&mut state, &rkeys[..8]); + + let mut rk_off = 8; + loop { + sub_bytes(&mut state); + mix_columns_1(&mut state); + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + rk_off += 8; + + #[cfg(aes_compact)] + { + shift_rows_2(&mut state); + } + + if rk_off == 112 { + break; + } + + #[cfg(not(aes_compact))] + { + sub_bytes(&mut state); + mix_columns_2(&mut state); + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + rk_off += 8; + + sub_bytes(&mut state); + mix_columns_3(&mut state); + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + rk_off += 8; + } + + sub_bytes(&mut state); + mix_columns_0(&mut state); + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + rk_off += 8; + } + + #[cfg(not(aes_compact))] + { + shift_rows_2(&mut state); + } + + sub_bytes(&mut state); + add_round_key(&mut state, &rkeys[112..]); + + inv_bitslice(&state) +} + +/// Note that the 4 bitwise NOT (^= 0xffffffff) are accounted for here so that it is a true +/// inverse of 'sub_bytes'. +fn inv_sub_bytes(state: &mut [u32]) { + debug_assert_eq!(state.len(), 8); + + // Scheduled using https://github.com/Ko-/aes-armcortexm/tree/public/scheduler + // Inline "stack" comments reflect suggested stores and loads (ARM Cortex-M3 and M4) + + let u7 = state[0]; + let u6 = state[1]; + let u5 = state[2]; + let u4 = state[3]; + let u3 = state[4]; + let u2 = state[5]; + let u1 = state[6]; + let u0 = state[7]; + + let t23 = u0 ^ u3; + let t8 = u1 ^ t23; + let m2 = t23 & t8; + let t4 = u4 ^ t8; + let t22 = u1 ^ u3; + let t2 = u0 ^ u1; + let t1 = u3 ^ u4; + // t23 -> stack + let t9 = u7 ^ t1; + // t8 -> stack + let m7 = t22 & t9; + // t9 -> stack + let t24 = u4 ^ u7; + // m7 -> stack + let t10 = t2 ^ t24; + // u4 -> stack + let m14 = t2 & t10; + let r5 = u6 ^ u7; + // m2 -> stack + let t3 = t1 ^ r5; + // t2 -> stack + let t13 = t2 ^ r5; + let t19 = t22 ^ r5; + // t3 -> stack + let t17 = u2 ^ t19; + // t4 -> stack + let t25 = u2 ^ t1; + let r13 = u1 ^ u6; + // t25 -> stack + let t20 = t24 ^ r13; + // t17 -> stack + let m9 = t20 & t17; + // t20 -> stack + let r17 = u2 ^ u5; + // t22 -> stack + let t6 = t22 ^ r17; + // t13 -> stack + let m1 = t13 & t6; + let y5 = u0 ^ r17; + let m4 = t19 & y5; + let m5 = m4 ^ m1; + let m17 = m5 ^ t24; + let r18 = u5 ^ u6; + let t27 = t1 ^ r18; + let t15 = t10 ^ t27; + // t6 -> stack + let m11 = t1 & t15; + let m15 = m14 ^ m11; + let m21 = m17 ^ m15; + // t1 -> stack + // t4 <- stack + let m12 = t4 & t27; + let m13 = m12 ^ m11; + let t14 = t10 ^ r18; + let m3 = t14 ^ m1; + // m2 <- stack + let m16 = m3 ^ m2; + let m20 = m16 ^ m13; + // u4 <- stack + let r19 = u2 ^ u4; + let t16 = r13 ^ r19; + // t3 <- stack + let t26 = t3 ^ t16; + let m6 = t3 & t16; + let m8 = t26 ^ m6; + // t10 -> stack + // m7 <- stack + let m18 = m8 ^ m7; + let m22 = m18 ^ m13; + let m25 = m22 & m20; + let m26 = m21 ^ m25; + let m10 = m9 ^ m6; + let m19 = m10 ^ m15; + // t25 <- stack + let m23 = m19 ^ t25; + let m28 = m23 ^ m25; + let m24 = m22 ^ m23; + let m30 = m26 & m24; + let m39 = m23 ^ m30; + let m48 = m39 & y5; + let m57 = m39 & t19; + // m48 -> stack + let m36 = m24 ^ m25; + let m31 = m20 & m23; + let m27 = m20 ^ m21; + let m32 = m27 & m31; + let m29 = m28 & m27; + let m37 = m21 ^ m29; + // m39 -> stack + let m42 = m37 ^ m39; + let m52 = m42 & t15; + // t27 -> stack + // t1 <- stack + let m61 = m42 & t1; + let p0 = m52 ^ m61; + let p16 = m57 ^ m61; + // m57 -> stack + // t20 <- stack + let m60 = m37 & t20; + // p16 -> stack + // t17 <- stack + let m51 = m37 & t17; + let m33 = m27 ^ m25; + let m38 = m32 ^ m33; + let m43 = m37 ^ m38; + let m49 = m43 & t16; + let p6 = m49 ^ m60; + let p13 = m49 ^ m51; + let m58 = m43 & t3; + // t9 <- stack + let m50 = m38 & t9; + // t22 <- stack + let m59 = m38 & t22; + // p6 -> stack + let p1 = m58 ^ m59; + let p7 = p0 ^ p1; + let m34 = m21 & m22; + let m35 = m24 & m34; + let m40 = m35 ^ m36; + let m41 = m38 ^ m40; + let m45 = m42 ^ m41; + // t27 <- stack + let m53 = m45 & t27; + let p8 = m50 ^ m53; + let p23 = p7 ^ p8; + // t4 <- stack + let m62 = m45 & t4; + let p14 = m49 ^ m62; + let s6 = p14 ^ p23; + // t10 <- stack + let m54 = m41 & t10; + let p2 = m54 ^ m62; + let p22 = p2 ^ p7; + let s0 = p13 ^ p22; + let p17 = m58 ^ p2; + let p15 = m54 ^ m59; + // t2 <- stack + let m63 = m41 & t2; + // m39 <- stack + let m44 = m39 ^ m40; + // p17 -> stack + // t6 <- stack + let m46 = m44 & t6; + let p5 = m46 ^ m51; + // p23 -> stack + let p18 = m63 ^ p5; + let p24 = p5 ^ p7; + // m48 <- stack + let p12 = m46 ^ m48; + let s3 = p12 ^ p22; + // t13 <- stack + let m55 = m44 & t13; + let p9 = m55 ^ m63; + // p16 <- stack + let s7 = p9 ^ p16; + // t8 <- stack + let m47 = m40 & t8; + let p3 = m47 ^ m50; + let p19 = p2 ^ p3; + let s5 = p19 ^ p24; + let p11 = p0 ^ p3; + let p26 = p9 ^ p11; + // t23 <- stack + let m56 = m40 & t23; + let p4 = m48 ^ m56; + // p6 <- stack + let p20 = p4 ^ p6; + let p29 = p15 ^ p20; + let s1 = p26 ^ p29; + // m57 <- stack + let p10 = m57 ^ p4; + let p27 = p10 ^ p18; + // p23 <- stack + let s4 = p23 ^ p27; + let p25 = p6 ^ p10; + let p28 = p11 ^ p25; + // p17 <- stack + let s2 = p17 ^ p28; + + state[0] = s7; + state[1] = s6; + state[2] = s5; + state[3] = s4; + state[4] = s3; + state[5] = s2; + state[6] = s1; + state[7] = s0; +} + +/// Bitsliced implementation of the AES Sbox based on Boyar, Peralta and Calik. +/// +/// See: +/// +/// Note that the 4 bitwise NOT (^= 0xffffffff) are moved to the key schedule. +fn sub_bytes(state: &mut [u32]) { + debug_assert_eq!(state.len(), 8); + + // Scheduled using https://github.com/Ko-/aes-armcortexm/tree/public/scheduler + // Inline "stack" comments reflect suggested stores and loads (ARM Cortex-M3 and M4) + + let u7 = state[0]; + let u6 = state[1]; + let u5 = state[2]; + let u4 = state[3]; + let u3 = state[4]; + let u2 = state[5]; + let u1 = state[6]; + let u0 = state[7]; + + let y14 = u3 ^ u5; + let y13 = u0 ^ u6; + let y12 = y13 ^ y14; + let t1 = u4 ^ y12; + let y15 = t1 ^ u5; + let t2 = y12 & y15; + let y6 = y15 ^ u7; + let y20 = t1 ^ u1; + // y12 -> stack + let y9 = u0 ^ u3; + // y20 -> stack + let y11 = y20 ^ y9; + // y9 -> stack + let t12 = y9 & y11; + // y6 -> stack + let y7 = u7 ^ y11; + let y8 = u0 ^ u5; + let t0 = u1 ^ u2; + let y10 = y15 ^ t0; + // y15 -> stack + let y17 = y10 ^ y11; + // y14 -> stack + let t13 = y14 & y17; + let t14 = t13 ^ t12; + // y17 -> stack + let y19 = y10 ^ y8; + // y10 -> stack + let t15 = y8 & y10; + let t16 = t15 ^ t12; + let y16 = t0 ^ y11; + // y11 -> stack + let y21 = y13 ^ y16; + // y13 -> stack + let t7 = y13 & y16; + // y16 -> stack + let y18 = u0 ^ y16; + let y1 = t0 ^ u7; + let y4 = y1 ^ u3; + // u7 -> stack + let t5 = y4 & u7; + let t6 = t5 ^ t2; + let t18 = t6 ^ t16; + let t22 = t18 ^ y19; + let y2 = y1 ^ u0; + let t10 = y2 & y7; + let t11 = t10 ^ t7; + let t20 = t11 ^ t16; + let t24 = t20 ^ y18; + let y5 = y1 ^ u6; + let t8 = y5 & y1; + let t9 = t8 ^ t7; + let t19 = t9 ^ t14; + let t23 = t19 ^ y21; + let y3 = y5 ^ y8; + // y6 <- stack + let t3 = y3 & y6; + let t4 = t3 ^ t2; + // y20 <- stack + let t17 = t4 ^ y20; + let t21 = t17 ^ t14; + let t26 = t21 & t23; + let t27 = t24 ^ t26; + let t31 = t22 ^ t26; + let t25 = t21 ^ t22; + // y4 -> stack + let t28 = t25 & t27; + let t29 = t28 ^ t22; + let z14 = t29 & y2; + let z5 = t29 & y7; + let t30 = t23 ^ t24; + let t32 = t31 & t30; + let t33 = t32 ^ t24; + let t35 = t27 ^ t33; + let t36 = t24 & t35; + let t38 = t27 ^ t36; + let t39 = t29 & t38; + let t40 = t25 ^ t39; + let t43 = t29 ^ t40; + // y16 <- stack + let z3 = t43 & y16; + let tc12 = z3 ^ z5; + // tc12 -> stack + // y13 <- stack + let z12 = t43 & y13; + let z13 = t40 & y5; + let z4 = t40 & y1; + let tc6 = z3 ^ z4; + let t34 = t23 ^ t33; + let t37 = t36 ^ t34; + let t41 = t40 ^ t37; + // y10 <- stack + let z8 = t41 & y10; + let z17 = t41 & y8; + let t44 = t33 ^ t37; + // y15 <- stack + let z0 = t44 & y15; + // z17 -> stack + // y12 <- stack + let z9 = t44 & y12; + let z10 = t37 & y3; + let z1 = t37 & y6; + let tc5 = z1 ^ z0; + let tc11 = tc6 ^ tc5; + // y4 <- stack + let z11 = t33 & y4; + let t42 = t29 ^ t33; + let t45 = t42 ^ t41; + // y17 <- stack + let z7 = t45 & y17; + let tc8 = z7 ^ tc6; + // y14 <- stack + let z16 = t45 & y14; + // y11 <- stack + let z6 = t42 & y11; + let tc16 = z6 ^ tc8; + // z14 -> stack + // y9 <- stack + let z15 = t42 & y9; + let tc20 = z15 ^ tc16; + let tc1 = z15 ^ z16; + let tc2 = z10 ^ tc1; + let tc21 = tc2 ^ z11; + let tc3 = z9 ^ tc2; + let s0 = tc3 ^ tc16; + let s3 = tc3 ^ tc11; + let s1 = s3 ^ tc16; + let tc13 = z13 ^ tc1; + // u7 <- stack + let z2 = t33 & u7; + let tc4 = z0 ^ z2; + let tc7 = z12 ^ tc4; + let tc9 = z8 ^ tc7; + let tc10 = tc8 ^ tc9; + // z14 <- stack + let tc17 = z14 ^ tc10; + let s5 = tc21 ^ tc17; + let tc26 = tc17 ^ tc20; + // z17 <- stack + let s2 = tc26 ^ z17; + // tc12 <- stack + let tc14 = tc4 ^ tc12; + let tc18 = tc13 ^ tc14; + let s6 = tc10 ^ tc18; + let s7 = z12 ^ tc18; + let s4 = tc14 ^ s3; + + state[0] = s7; + state[1] = s6; + state[2] = s5; + state[3] = s4; + state[4] = s3; + state[5] = s2; + state[6] = s1; + state[7] = s0; +} + +/// NOT operations that are omitted in S-box +#[inline] +fn sub_bytes_nots(state: &mut [u32]) { + debug_assert_eq!(state.len(), 8); + state[0] ^= 0xffffffff; + state[1] ^= 0xffffffff; + state[5] ^= 0xffffffff; + state[6] ^= 0xffffffff; +} + +/// Computation of the MixColumns transformation in the fixsliced representation, with different +/// rotations used according to the round number mod 4. +/// +/// Based on Käsper-Schwabe, similar to https://github.com/Ko-/aes-armcortexm. +macro_rules! define_mix_columns { + ( + $name:ident, + $name_inv:ident, + $first_rotate:path, + $second_rotate:path + ) => { + #[rustfmt::skip] + fn $name(state: &mut State) { + let (a0, a1, a2, a3, a4, a5, a6, a7) = ( + state[0], state[1], state[2], state[3], state[4], state[5], state[6], state[7] + ); + let (b0, b1, b2, b3, b4, b5, b6, b7) = ( + $first_rotate(a0), + $first_rotate(a1), + $first_rotate(a2), + $first_rotate(a3), + $first_rotate(a4), + $first_rotate(a5), + $first_rotate(a6), + $first_rotate(a7), + ); + let (c0, c1, c2, c3, c4, c5, c6, c7) = ( + a0 ^ b0, + a1 ^ b1, + a2 ^ b2, + a3 ^ b3, + a4 ^ b4, + a5 ^ b5, + a6 ^ b6, + a7 ^ b7, + ); + state[0] = b0 ^ c7 ^ $second_rotate(c0); + state[1] = b1 ^ c0 ^ c7 ^ $second_rotate(c1); + state[2] = b2 ^ c1 ^ $second_rotate(c2); + state[3] = b3 ^ c2 ^ c7 ^ $second_rotate(c3); + state[4] = b4 ^ c3 ^ c7 ^ $second_rotate(c4); + state[5] = b5 ^ c4 ^ $second_rotate(c5); + state[6] = b6 ^ c5 ^ $second_rotate(c6); + state[7] = b7 ^ c6 ^ $second_rotate(c7); + } + + #[rustfmt::skip] + fn $name_inv(state: &mut State) { + let (a0, a1, a2, a3, a4, a5, a6, a7) = ( + state[0], state[1], state[2], state[3], state[4], state[5], state[6], state[7] + ); + let (b0, b1, b2, b3, b4, b5, b6, b7) = ( + $first_rotate(a0), + $first_rotate(a1), + $first_rotate(a2), + $first_rotate(a3), + $first_rotate(a4), + $first_rotate(a5), + $first_rotate(a6), + $first_rotate(a7), + ); + let (c0, c1, c2, c3, c4, c5, c6, c7) = ( + a0 ^ b0, + a1 ^ b1, + a2 ^ b2, + a3 ^ b3, + a4 ^ b4, + a5 ^ b5, + a6 ^ b6, + a7 ^ b7, + ); + let (d0, d1, d2, d3, d4, d5, d6, d7) = ( + a0 ^ c7, + a1 ^ c0 ^ c7, + a2 ^ c1, + a3 ^ c2 ^ c7, + a4 ^ c3 ^ c7, + a5 ^ c4, + a6 ^ c5, + a7 ^ c6, + ); + let (e0, e1, e2, e3, e4, e5, e6, e7) = ( + c0 ^ d6, + c1 ^ d6 ^ d7, + c2 ^ d0 ^ d7, + c3 ^ d1 ^ d6, + c4 ^ d2 ^ d6 ^ d7, + c5 ^ d3 ^ d7, + c6 ^ d4, + c7 ^ d5, + ); + state[0] = d0 ^ e0 ^ $second_rotate(e0); + state[1] = d1 ^ e1 ^ $second_rotate(e1); + state[2] = d2 ^ e2 ^ $second_rotate(e2); + state[3] = d3 ^ e3 ^ $second_rotate(e3); + state[4] = d4 ^ e4 ^ $second_rotate(e4); + state[5] = d5 ^ e5 ^ $second_rotate(e5); + state[6] = d6 ^ e6 ^ $second_rotate(e6); + state[7] = d7 ^ e7 ^ $second_rotate(e7); + } + } +} + +define_mix_columns!( + mix_columns_0, + inv_mix_columns_0, + rotate_rows_1, + rotate_rows_2 +); + +define_mix_columns!( + mix_columns_1, + inv_mix_columns_1, + rotate_rows_and_columns_1_1, + rotate_rows_and_columns_2_2 +); + +#[cfg(not(aes_compact))] +define_mix_columns!( + mix_columns_2, + inv_mix_columns_2, + rotate_rows_and_columns_1_2, + rotate_rows_2 +); + +#[cfg(not(aes_compact))] +define_mix_columns!( + mix_columns_3, + inv_mix_columns_3, + rotate_rows_and_columns_1_3, + rotate_rows_and_columns_2_2 +); + +#[inline] +fn delta_swap_1(a: &mut u32, shift: u32, mask: u32) { + let t = (*a ^ ((*a) >> shift)) & mask; + *a ^= t ^ (t << shift); +} + +#[inline] +fn delta_swap_2(a: &mut u32, b: &mut u32, shift: u32, mask: u32) { + let t = (*a ^ ((*b) >> shift)) & mask; + *a ^= t; + *b ^= t << shift; +} + +/// Applies ShiftRows once on an AES state (or key). +#[cfg(any(not(aes_compact), feature = "hazmat"))] +#[inline] +fn shift_rows_1(state: &mut [u32]) { + debug_assert_eq!(state.len(), 8); + for x in state.iter_mut() { + delta_swap_1(x, 4, 0x0c0f0300); + delta_swap_1(x, 2, 0x33003300); + } +} + +/// Applies ShiftRows twice on an AES state (or key). +#[inline] +fn shift_rows_2(state: &mut [u32]) { + debug_assert_eq!(state.len(), 8); + for x in state.iter_mut() { + delta_swap_1(x, 4, 0x0f000f00); + } +} + +/// Applies ShiftRows three times on an AES state (or key). +#[inline] +fn shift_rows_3(state: &mut [u32]) { + debug_assert_eq!(state.len(), 8); + for x in state.iter_mut() { + delta_swap_1(x, 4, 0x030f0c00); + delta_swap_1(x, 2, 0x33003300); + } +} + +#[inline(always)] +fn inv_shift_rows_1(state: &mut [u32]) { + shift_rows_3(state); +} + +#[inline(always)] +fn inv_shift_rows_2(state: &mut [u32]) { + shift_rows_2(state); +} + +#[cfg(not(aes_compact))] +#[inline(always)] +fn inv_shift_rows_3(state: &mut [u32]) { + shift_rows_1(state); +} + +/// XOR the columns after the S-box during the key schedule round function. +/// +/// The `idx_xor` parameter refers to the index of the previous round key that is +/// involved in the XOR computation (should be 8 and 16 for AES-128 and AES-256, +/// respectively). +/// +/// The `idx_ror` parameter refers to the rotation value, which varies between the +/// different key schedules. +fn xor_columns(rkeys: &mut [u32], offset: usize, idx_xor: usize, idx_ror: u32) { + for i in 0..8 { + let off_i = offset + i; + let rk = rkeys[off_i - idx_xor] ^ (0x03030303 & ror(rkeys[off_i], idx_ror)); + rkeys[off_i] = + rk ^ (0xfcfcfcfc & (rk << 2)) ^ (0xf0f0f0f0 & (rk << 4)) ^ (0xc0c0c0c0 & (rk << 6)); + } +} + +/// Bitslice two 128-bit input blocks input0, input1 into a 256-bit internal state. +fn bitslice(output: &mut [u32], input0: &[u8], input1: &[u8]) { + debug_assert_eq!(output.len(), 8); + debug_assert_eq!(input0.len(), 16); + debug_assert_eq!(input1.len(), 16); + + // Bitslicing is a bit index manipulation. 256 bits of data means each bit is positioned at an + // 8-bit index. AES data is 2 blocks, each one a 4x4 column-major matrix of bytes, so the + // index is initially ([b]lock, [c]olumn, [r]ow, [p]osition): + // b0 c1 c0 r1 r0 p2 p1 p0 + // + // The desired bitsliced data groups first by bit position, then row, column, block: + // p2 p1 p0 r1 r0 c1 c0 b0 + + // Interleave the columns on input (note the order of input) + // b0 c1 c0 __ __ __ __ __ => c1 c0 b0 __ __ __ __ __ + let mut t0 = u32::from_le_bytes(input0[0x00..0x04].try_into().unwrap()); + let mut t2 = u32::from_le_bytes(input0[0x04..0x08].try_into().unwrap()); + let mut t4 = u32::from_le_bytes(input0[0x08..0x0c].try_into().unwrap()); + let mut t6 = u32::from_le_bytes(input0[0x0c..0x10].try_into().unwrap()); + let mut t1 = u32::from_le_bytes(input1[0x00..0x04].try_into().unwrap()); + let mut t3 = u32::from_le_bytes(input1[0x04..0x08].try_into().unwrap()); + let mut t5 = u32::from_le_bytes(input1[0x08..0x0c].try_into().unwrap()); + let mut t7 = u32::from_le_bytes(input1[0x0c..0x10].try_into().unwrap()); + + // Bit Index Swap 5 <-> 0: + // __ __ b0 __ __ __ __ p0 => __ __ p0 __ __ __ __ b0 + let m0 = 0x55555555; + delta_swap_2(&mut t1, &mut t0, 1, m0); + delta_swap_2(&mut t3, &mut t2, 1, m0); + delta_swap_2(&mut t5, &mut t4, 1, m0); + delta_swap_2(&mut t7, &mut t6, 1, m0); + + // Bit Index Swap 6 <-> 1: + // __ c0 __ __ __ __ p1 __ => __ p1 __ __ __ __ c0 __ + let m1 = 0x33333333; + delta_swap_2(&mut t2, &mut t0, 2, m1); + delta_swap_2(&mut t3, &mut t1, 2, m1); + delta_swap_2(&mut t6, &mut t4, 2, m1); + delta_swap_2(&mut t7, &mut t5, 2, m1); + + // Bit Index Swap 7 <-> 2: + // c1 __ __ __ __ p2 __ __ => p2 __ __ __ __ c1 __ __ + let m2 = 0x0f0f0f0f; + delta_swap_2(&mut t4, &mut t0, 4, m2); + delta_swap_2(&mut t5, &mut t1, 4, m2); + delta_swap_2(&mut t6, &mut t2, 4, m2); + delta_swap_2(&mut t7, &mut t3, 4, m2); + + // Final bitsliced bit index, as desired: + // p2 p1 p0 r1 r0 c1 c0 b0 + output[0] = t0; + output[1] = t1; + output[2] = t2; + output[3] = t3; + output[4] = t4; + output[5] = t5; + output[6] = t6; + output[7] = t7; +} + +/// Un-bitslice a 256-bit internal state into two 128-bit blocks of output. +fn inv_bitslice(input: &[u32]) -> BatchBlocks { + debug_assert_eq!(input.len(), 8); + + // Unbitslicing is a bit index manipulation. 256 bits of data means each bit is positioned at + // an 8-bit index. AES data is 2 blocks, each one a 4x4 column-major matrix of bytes, so the + // desired index for the output is ([b]lock, [c]olumn, [r]ow, [p]osition): + // b0 c1 c0 r1 r0 p2 p1 p0 + // + // The initially bitsliced data groups first by bit position, then row, column, block: + // p2 p1 p0 r1 r0 c1 c0 b0 + + let mut t0 = input[0]; + let mut t1 = input[1]; + let mut t2 = input[2]; + let mut t3 = input[3]; + let mut t4 = input[4]; + let mut t5 = input[5]; + let mut t6 = input[6]; + let mut t7 = input[7]; + + // TODO: these bit index swaps are identical to those in 'packing' + + // Bit Index Swap 5 <-> 0: + // __ __ p0 __ __ __ __ b0 => __ __ b0 __ __ __ __ p0 + let m0 = 0x55555555; + delta_swap_2(&mut t1, &mut t0, 1, m0); + delta_swap_2(&mut t3, &mut t2, 1, m0); + delta_swap_2(&mut t5, &mut t4, 1, m0); + delta_swap_2(&mut t7, &mut t6, 1, m0); + + // Bit Index Swap 6 <-> 1: + // __ p1 __ __ __ __ c0 __ => __ c0 __ __ __ __ p1 __ + let m1 = 0x33333333; + delta_swap_2(&mut t2, &mut t0, 2, m1); + delta_swap_2(&mut t3, &mut t1, 2, m1); + delta_swap_2(&mut t6, &mut t4, 2, m1); + delta_swap_2(&mut t7, &mut t5, 2, m1); + + // Bit Index Swap 7 <-> 2: + // p2 __ __ __ __ c1 __ __ => c1 __ __ __ __ p2 __ __ + let m2 = 0x0f0f0f0f; + delta_swap_2(&mut t4, &mut t0, 4, m2); + delta_swap_2(&mut t5, &mut t1, 4, m2); + delta_swap_2(&mut t6, &mut t2, 4, m2); + delta_swap_2(&mut t7, &mut t3, 4, m2); + + let mut output = BatchBlocks::default(); + // De-interleave the columns on output (note the order of output) + // c1 c0 b0 __ __ __ __ __ => b0 c1 c0 __ __ __ __ __ + output[0][0x00..0x04].copy_from_slice(&t0.to_le_bytes()); + output[0][0x04..0x08].copy_from_slice(&t2.to_le_bytes()); + output[0][0x08..0x0c].copy_from_slice(&t4.to_le_bytes()); + output[0][0x0c..0x10].copy_from_slice(&t6.to_le_bytes()); + output[1][0x00..0x04].copy_from_slice(&t1.to_le_bytes()); + output[1][0x04..0x08].copy_from_slice(&t3.to_le_bytes()); + output[1][0x08..0x0c].copy_from_slice(&t5.to_le_bytes()); + output[1][0x0c..0x10].copy_from_slice(&t7.to_le_bytes()); + + // Final AES bit index, as desired: + // b0 c1 c0 r1 r0 p2 p1 p0 + output +} + +/// Copy 32-bytes within the provided slice to an 8-byte offset +fn memshift32(buffer: &mut [u32], src_offset: usize) { + debug_assert_eq!(src_offset % 8, 0); + + let dst_offset = src_offset + 8; + debug_assert!(dst_offset + 8 <= buffer.len()); + + for i in (0..8).rev() { + buffer[dst_offset + i] = buffer[src_offset + i]; + } +} + +/// XOR the round key to the internal state. The round keys are expected to be +/// pre-computed and to be packed in the fixsliced representation. +#[inline] +fn add_round_key(state: &mut State, rkey: &[u32]) { + debug_assert_eq!(rkey.len(), 8); + for (a, b) in state.iter_mut().zip(rkey) { + *a ^= b; + } +} + +#[inline(always)] +fn add_round_constant_bit(state: &mut [u32], bit: usize) { + state[bit] ^= 0x0000c000; +} + +#[inline(always)] +fn ror(x: u32, y: u32) -> u32 { + x.rotate_right(y) +} + +#[inline(always)] +fn ror_distance(rows: u32, cols: u32) -> u32 { + (rows << 3) + (cols << 1) +} + +#[inline(always)] +fn rotate_rows_1(x: u32) -> u32 { + ror(x, ror_distance(1, 0)) +} + +#[inline(always)] +fn rotate_rows_2(x: u32) -> u32 { + ror(x, ror_distance(2, 0)) +} + +#[inline(always)] +#[rustfmt::skip] +fn rotate_rows_and_columns_1_1(x: u32) -> u32 { + (ror(x, ror_distance(1, 1)) & 0x3f3f3f3f) | + (ror(x, ror_distance(0, 1)) & 0xc0c0c0c0) +} + +#[cfg(not(aes_compact))] +#[inline(always)] +#[rustfmt::skip] +fn rotate_rows_and_columns_1_2(x: u32) -> u32 { + (ror(x, ror_distance(1, 2)) & 0x0f0f0f0f) | + (ror(x, ror_distance(0, 2)) & 0xf0f0f0f0) +} + +#[cfg(not(aes_compact))] +#[inline(always)] +#[rustfmt::skip] +fn rotate_rows_and_columns_1_3(x: u32) -> u32 { + (ror(x, ror_distance(1, 3)) & 0x03030303) | + (ror(x, ror_distance(0, 3)) & 0xfcfcfcfc) +} + +#[inline(always)] +#[rustfmt::skip] +fn rotate_rows_and_columns_2_2(x: u32) -> u32 { + (ror(x, ror_distance(2, 2)) & 0x0f0f0f0f) | + (ror(x, ror_distance(1, 2)) & 0xf0f0f0f0) +} + +/// Low-level "hazmat" AES functions. +/// +/// Note: this isn't actually used in the `Aes128`/`Aes192`/`Aes256` +/// implementations in this crate, but instead provides raw access to +/// the AES round function gated under the `hazmat` crate feature. +#[cfg(feature = "hazmat")] +pub(crate) mod hazmat { + use super::{ + bitslice, inv_bitslice, inv_mix_columns_0, inv_shift_rows_1, inv_sub_bytes, mix_columns_0, + shift_rows_1, sub_bytes, sub_bytes_nots, State, + }; + use crate::{Block, Block8}; + + /// XOR the `src` block into the `dst` block in-place. + fn xor_in_place(dst: &mut Block, src: &Block) { + for (a, b) in dst.iter_mut().zip(src.as_slice()) { + *a ^= *b; + } + } + + /// Perform a bitslice operation, loading a single block. + fn bitslice_block(block: &Block) -> State { + let mut state = State::default(); + bitslice(&mut state, block, block); + state + } + + /// Perform an inverse bitslice operation, extracting a single block. + fn inv_bitslice_block(block: &mut Block, state: &State) { + let out = inv_bitslice(state); + block.copy_from_slice(&out[0]); + } + + /// AES cipher (encrypt) round function. + #[inline] + pub(crate) fn cipher_round(block: &mut Block, round_key: &Block) { + let mut state = bitslice_block(block); + sub_bytes(&mut state); + sub_bytes_nots(&mut state); + shift_rows_1(&mut state); + mix_columns_0(&mut state); + inv_bitslice_block(block, &state); + xor_in_place(block, round_key); + } + + /// AES cipher (encrypt) round function: parallel version. + #[inline] + pub(crate) fn cipher_round_par(blocks: &mut Block8, round_keys: &Block8) { + for (chunk, keys) in blocks.chunks_exact_mut(2).zip(round_keys.chunks_exact(2)) { + let mut state = State::default(); + bitslice(&mut state, &chunk[0], &chunk[1]); + sub_bytes(&mut state); + sub_bytes_nots(&mut state); + shift_rows_1(&mut state); + mix_columns_0(&mut state); + let res = inv_bitslice(&state); + + for i in 0..2 { + chunk[i] = res[i]; + xor_in_place(&mut chunk[i], &keys[i]); + } + } + } + + /// AES cipher (encrypt) round function. + #[inline] + pub(crate) fn equiv_inv_cipher_round(block: &mut Block, round_key: &Block) { + let mut state = bitslice_block(block); + sub_bytes_nots(&mut state); + inv_sub_bytes(&mut state); + inv_shift_rows_1(&mut state); + inv_mix_columns_0(&mut state); + inv_bitslice_block(block, &state); + xor_in_place(block, round_key); + } + + /// AES cipher (encrypt) round function: parallel version. + #[inline] + pub(crate) fn equiv_inv_cipher_round_par(blocks: &mut Block8, round_keys: &Block8) { + for (chunk, keys) in blocks.chunks_exact_mut(2).zip(round_keys.chunks_exact(2)) { + let mut state = State::default(); + bitslice(&mut state, &chunk[0], &chunk[1]); + sub_bytes_nots(&mut state); + inv_sub_bytes(&mut state); + inv_shift_rows_1(&mut state); + inv_mix_columns_0(&mut state); + let res = inv_bitslice(&state); + + for i in 0..2 { + chunk[i] = res[i]; + xor_in_place(&mut chunk[i], &keys[i]); + } + } + } + + /// AES mix columns function. + #[inline] + pub(crate) fn mix_columns(block: &mut Block) { + let mut state = bitslice_block(block); + mix_columns_0(&mut state); + inv_bitslice_block(block, &state); + } + + /// AES inverse mix columns function. + #[inline] + pub(crate) fn inv_mix_columns(block: &mut Block) { + let mut state = bitslice_block(block); + inv_mix_columns_0(&mut state); + inv_bitslice_block(block, &state); + } +} diff --git a/src/soft/fixslice64.rs b/src/soft/fixslice64.rs new file mode 100644 index 0000000..09dbcbe --- /dev/null +++ b/src/soft/fixslice64.rs @@ -0,0 +1,1534 @@ +//! Fixsliced implementations of AES-128, AES-192 and AES-256 (64-bit) +//! adapted from the C implementation. +//! +//! All implementations are fully bitsliced and do not rely on any +//! Look-Up Table (LUT). +//! +//! See the paper at for more details. +//! +//! # Author (original C code) +//! +//! Alexandre Adomnicai, Nanyang Technological University, Singapore +//! +//! +//! Originally licensed MIT. Relicensed as Apache 2.0+MIT with permission. + +#![allow(clippy::unreadable_literal)] + +use crate::Block; +use cipher::{consts::U4, generic_array::GenericArray}; + +/// AES block batch size for this implementation +pub(crate) type FixsliceBlocks = U4; + +pub(crate) type BatchBlocks = GenericArray; + +/// AES-128 round keys +pub(crate) type FixsliceKeys128 = [u64; 88]; + +/// AES-192 round keys +pub(crate) type FixsliceKeys192 = [u64; 104]; + +/// AES-256 round keys +pub(crate) type FixsliceKeys256 = [u64; 120]; + +/// 512-bit internal state +pub(crate) type State = [u64; 8]; + +/// Fully bitsliced AES-128 key schedule to match the fully-fixsliced representation. +pub(crate) fn aes128_key_schedule(key: &[u8; 16]) -> FixsliceKeys128 { + let mut rkeys = [0u64; 88]; + + bitslice(&mut rkeys[..8], key, key, key, key); + + let mut rk_off = 0; + for rcon in 0..10 { + memshift32(&mut rkeys, rk_off); + rk_off += 8; + + sub_bytes(&mut rkeys[rk_off..(rk_off + 8)]); + sub_bytes_nots(&mut rkeys[rk_off..(rk_off + 8)]); + + if rcon < 8 { + add_round_constant_bit(&mut rkeys[rk_off..(rk_off + 8)], rcon); + } else { + add_round_constant_bit(&mut rkeys[rk_off..(rk_off + 8)], rcon - 8); + add_round_constant_bit(&mut rkeys[rk_off..(rk_off + 8)], rcon - 7); + add_round_constant_bit(&mut rkeys[rk_off..(rk_off + 8)], rcon - 5); + add_round_constant_bit(&mut rkeys[rk_off..(rk_off + 8)], rcon - 4); + } + + xor_columns(&mut rkeys, rk_off, 8, ror_distance(1, 3)); + } + + // Adjust to match fixslicing format + #[cfg(aes_compact)] + { + for i in (8..88).step_by(16) { + inv_shift_rows_1(&mut rkeys[i..(i + 8)]); + } + } + #[cfg(not(aes_compact))] + { + for i in (8..72).step_by(32) { + inv_shift_rows_1(&mut rkeys[i..(i + 8)]); + inv_shift_rows_2(&mut rkeys[(i + 8)..(i + 16)]); + inv_shift_rows_3(&mut rkeys[(i + 16)..(i + 24)]); + } + inv_shift_rows_1(&mut rkeys[72..80]); + } + + // Account for NOTs removed from sub_bytes + for i in 1..11 { + sub_bytes_nots(&mut rkeys[(i * 8)..(i * 8 + 8)]); + } + + rkeys +} + +/// Fully bitsliced AES-192 key schedule to match the fully-fixsliced representation. +pub(crate) fn aes192_key_schedule(key: &[u8; 24]) -> FixsliceKeys192 { + let mut rkeys = [0u64; 104]; + let mut tmp = [0u64; 8]; + + bitslice( + &mut rkeys[..8], + &key[..16], + &key[..16], + &key[..16], + &key[..16], + ); + bitslice(&mut tmp, &key[8..], &key[8..], &key[8..], &key[8..]); + + let mut rcon = 0; + let mut rk_off = 8; + + loop { + for i in 0..8 { + rkeys[rk_off + i] = (0x00ff00ff00ff00ff & (tmp[i] >> 8)) + | (0xff00ff00ff00ff00 & (rkeys[(rk_off - 8) + i] << 8)); + } + + sub_bytes(&mut tmp); + sub_bytes_nots(&mut tmp); + + add_round_constant_bit(&mut tmp, rcon); + rcon += 1; + + for i in 0..8 { + let mut ti = rkeys[rk_off + i]; + ti ^= 0x0f000f000f000f00 & ror(tmp[i], ror_distance(1, 1)); + ti ^= 0xf000f000f000f000 & (ti << 4); + tmp[i] = ti; + } + rkeys[rk_off..(rk_off + 8)].copy_from_slice(&tmp); + rk_off += 8; + + for i in 0..8 { + let ui = tmp[i]; + let mut ti = (0x00ff00ff00ff00ff & (rkeys[(rk_off - 16) + i] >> 8)) + | (0xff00ff00ff00ff00 & (ui << 8)); + ti ^= 0x000f000f000f000f & (ui >> 12); + tmp[i] = ti + ^ (0xfff0fff0fff0fff0 & (ti << 4)) + ^ (0xff00ff00ff00ff00 & (ti << 8)) + ^ (0xf000f000f000f000 & (ti << 12)); + } + rkeys[rk_off..(rk_off + 8)].copy_from_slice(&tmp); + rk_off += 8; + + sub_bytes(&mut tmp); + sub_bytes_nots(&mut tmp); + + add_round_constant_bit(&mut tmp, rcon); + rcon += 1; + + for i in 0..8 { + let mut ti = (0x00ff00ff00ff00ff & (rkeys[(rk_off - 16) + i] >> 8)) + | (0xff00ff00ff00ff00 & (rkeys[(rk_off - 8) + i] << 8)); + ti ^= 0x000f000f000f000f & ror(tmp[i], ror_distance(1, 3)); + rkeys[rk_off + i] = ti + ^ (0xfff0fff0fff0fff0 & (ti << 4)) + ^ (0xff00ff00ff00ff00 & (ti << 8)) + ^ (0xf000f000f000f000 & (ti << 12)); + } + rk_off += 8; + + if rcon >= 8 { + break; + } + + for i in 0..8 { + let ui = rkeys[(rk_off - 8) + i]; + let mut ti = rkeys[(rk_off - 16) + i]; + ti ^= 0x0f000f000f000f00 & (ui >> 4); + ti ^= 0xf000f000f000f000 & (ti << 4); + tmp[i] = ti; + } + } + + // Adjust to match fixslicing format + #[cfg(aes_compact)] + { + for i in (8..104).step_by(16) { + inv_shift_rows_1(&mut rkeys[i..(i + 8)]); + } + } + #[cfg(not(aes_compact))] + { + for i in (0..96).step_by(32) { + inv_shift_rows_1(&mut rkeys[(i + 8)..(i + 16)]); + inv_shift_rows_2(&mut rkeys[(i + 16)..(i + 24)]); + inv_shift_rows_3(&mut rkeys[(i + 24)..(i + 32)]); + } + } + + // Account for NOTs removed from sub_bytes + for i in 1..13 { + sub_bytes_nots(&mut rkeys[(i * 8)..(i * 8 + 8)]); + } + + rkeys +} + +/// Fully bitsliced AES-256 key schedule to match the fully-fixsliced representation. +pub(crate) fn aes256_key_schedule(key: &[u8; 32]) -> FixsliceKeys256 { + let mut rkeys = [0u64; 120]; + + bitslice( + &mut rkeys[..8], + &key[..16], + &key[..16], + &key[..16], + &key[..16], + ); + bitslice( + &mut rkeys[8..16], + &key[16..], + &key[16..], + &key[16..], + &key[16..], + ); + + let mut rk_off = 8; + + let mut rcon = 0; + loop { + memshift32(&mut rkeys, rk_off); + rk_off += 8; + + sub_bytes(&mut rkeys[rk_off..(rk_off + 8)]); + sub_bytes_nots(&mut rkeys[rk_off..(rk_off + 8)]); + + add_round_constant_bit(&mut rkeys[rk_off..(rk_off + 8)], rcon); + xor_columns(&mut rkeys, rk_off, 16, ror_distance(1, 3)); + rcon += 1; + + if rcon == 7 { + break; + } + + memshift32(&mut rkeys, rk_off); + rk_off += 8; + + sub_bytes(&mut rkeys[rk_off..(rk_off + 8)]); + sub_bytes_nots(&mut rkeys[rk_off..(rk_off + 8)]); + + xor_columns(&mut rkeys, rk_off, 16, ror_distance(0, 3)); + } + + // Adjust to match fixslicing format + #[cfg(aes_compact)] + { + for i in (8..120).step_by(16) { + inv_shift_rows_1(&mut rkeys[i..(i + 8)]); + } + } + #[cfg(not(aes_compact))] + { + for i in (8..104).step_by(32) { + inv_shift_rows_1(&mut rkeys[i..(i + 8)]); + inv_shift_rows_2(&mut rkeys[(i + 8)..(i + 16)]); + inv_shift_rows_3(&mut rkeys[(i + 16)..(i + 24)]); + } + inv_shift_rows_1(&mut rkeys[104..112]); + } + + // Account for NOTs removed from sub_bytes + for i in 1..15 { + sub_bytes_nots(&mut rkeys[(i * 8)..(i * 8 + 8)]); + } + + rkeys +} + +/// Fully-fixsliced AES-128 decryption (the InvShiftRows is completely omitted). +/// +/// Decrypts four blocks in-place and in parallel. +pub(crate) fn aes128_decrypt(rkeys: &FixsliceKeys128, blocks: &BatchBlocks) -> BatchBlocks { + let mut state = State::default(); + + bitslice(&mut state, &blocks[0], &blocks[1], &blocks[2], &blocks[3]); + + add_round_key(&mut state, &rkeys[80..]); + inv_sub_bytes(&mut state); + + #[cfg(not(aes_compact))] + { + inv_shift_rows_2(&mut state); + } + + let mut rk_off = 72; + loop { + #[cfg(aes_compact)] + { + inv_shift_rows_2(&mut state); + } + + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + inv_mix_columns_1(&mut state); + inv_sub_bytes(&mut state); + rk_off -= 8; + + if rk_off == 0 { + break; + } + + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + inv_mix_columns_0(&mut state); + inv_sub_bytes(&mut state); + rk_off -= 8; + + #[cfg(not(aes_compact))] + { + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + inv_mix_columns_3(&mut state); + inv_sub_bytes(&mut state); + rk_off -= 8; + + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + inv_mix_columns_2(&mut state); + inv_sub_bytes(&mut state); + rk_off -= 8; + } + } + + add_round_key(&mut state, &rkeys[..8]); + + inv_bitslice(&state) +} + +/// Fully-fixsliced AES-128 encryption (the ShiftRows is completely omitted). +/// +/// Encrypts four blocks in-place and in parallel. +pub(crate) fn aes128_encrypt(rkeys: &FixsliceKeys128, blocks: &BatchBlocks) -> BatchBlocks { + let mut state = State::default(); + + bitslice(&mut state, &blocks[0], &blocks[1], &blocks[2], &blocks[3]); + + add_round_key(&mut state, &rkeys[..8]); + + let mut rk_off = 8; + loop { + sub_bytes(&mut state); + mix_columns_1(&mut state); + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + rk_off += 8; + + #[cfg(aes_compact)] + { + shift_rows_2(&mut state); + } + + if rk_off == 80 { + break; + } + + #[cfg(not(aes_compact))] + { + sub_bytes(&mut state); + mix_columns_2(&mut state); + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + rk_off += 8; + + sub_bytes(&mut state); + mix_columns_3(&mut state); + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + rk_off += 8; + } + + sub_bytes(&mut state); + mix_columns_0(&mut state); + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + rk_off += 8; + } + + #[cfg(not(aes_compact))] + { + shift_rows_2(&mut state); + } + + sub_bytes(&mut state); + add_round_key(&mut state, &rkeys[80..]); + + inv_bitslice(&state) +} + +/// Fully-fixsliced AES-192 decryption (the InvShiftRows is completely omitted). +/// +/// Decrypts four blocks in-place and in parallel. +pub(crate) fn aes192_decrypt(rkeys: &FixsliceKeys192, blocks: &BatchBlocks) -> BatchBlocks { + let mut state = State::default(); + + bitslice(&mut state, &blocks[0], &blocks[1], &blocks[2], &blocks[3]); + + add_round_key(&mut state, &rkeys[96..]); + inv_sub_bytes(&mut state); + + let mut rk_off = 88; + loop { + #[cfg(aes_compact)] + { + inv_shift_rows_2(&mut state); + } + #[cfg(not(aes_compact))] + { + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + inv_mix_columns_3(&mut state); + inv_sub_bytes(&mut state); + rk_off -= 8; + + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + inv_mix_columns_2(&mut state); + inv_sub_bytes(&mut state); + rk_off -= 8; + } + + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + inv_mix_columns_1(&mut state); + inv_sub_bytes(&mut state); + rk_off -= 8; + + if rk_off == 0 { + break; + } + + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + inv_mix_columns_0(&mut state); + inv_sub_bytes(&mut state); + rk_off -= 8; + } + + add_round_key(&mut state, &rkeys[..8]); + + inv_bitslice(&state) +} + +/// Fully-fixsliced AES-192 encryption (the ShiftRows is completely omitted). +/// +/// Encrypts four blocks in-place and in parallel. +pub(crate) fn aes192_encrypt(rkeys: &FixsliceKeys192, blocks: &BatchBlocks) -> BatchBlocks { + let mut state = State::default(); + + bitslice(&mut state, &blocks[0], &blocks[1], &blocks[2], &blocks[3]); + + add_round_key(&mut state, &rkeys[..8]); + + let mut rk_off = 8; + loop { + sub_bytes(&mut state); + mix_columns_1(&mut state); + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + rk_off += 8; + + #[cfg(aes_compact)] + { + shift_rows_2(&mut state); + } + #[cfg(not(aes_compact))] + { + sub_bytes(&mut state); + mix_columns_2(&mut state); + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + rk_off += 8; + + sub_bytes(&mut state); + mix_columns_3(&mut state); + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + rk_off += 8; + } + + if rk_off == 96 { + break; + } + + sub_bytes(&mut state); + mix_columns_0(&mut state); + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + rk_off += 8; + } + + sub_bytes(&mut state); + add_round_key(&mut state, &rkeys[96..]); + + inv_bitslice(&state) +} + +/// Fully-fixsliced AES-256 decryption (the InvShiftRows is completely omitted). +/// +/// Decrypts four blocks in-place and in parallel. +pub(crate) fn aes256_decrypt(rkeys: &FixsliceKeys256, blocks: &BatchBlocks) -> BatchBlocks { + let mut state = State::default(); + + bitslice(&mut state, &blocks[0], &blocks[1], &blocks[2], &blocks[3]); + + add_round_key(&mut state, &rkeys[112..]); + inv_sub_bytes(&mut state); + + #[cfg(not(aes_compact))] + { + inv_shift_rows_2(&mut state); + } + + let mut rk_off = 104; + loop { + #[cfg(aes_compact)] + { + inv_shift_rows_2(&mut state); + } + + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + inv_mix_columns_1(&mut state); + inv_sub_bytes(&mut state); + rk_off -= 8; + + if rk_off == 0 { + break; + } + + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + inv_mix_columns_0(&mut state); + inv_sub_bytes(&mut state); + rk_off -= 8; + + #[cfg(not(aes_compact))] + { + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + inv_mix_columns_3(&mut state); + inv_sub_bytes(&mut state); + rk_off -= 8; + + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + inv_mix_columns_2(&mut state); + inv_sub_bytes(&mut state); + rk_off -= 8; + } + } + + add_round_key(&mut state, &rkeys[..8]); + + inv_bitslice(&state) +} + +/// Fully-fixsliced AES-256 encryption (the ShiftRows is completely omitted). +/// +/// Encrypts four blocks in-place and in parallel. +pub(crate) fn aes256_encrypt(rkeys: &FixsliceKeys256, blocks: &BatchBlocks) -> BatchBlocks { + let mut state = State::default(); + + bitslice(&mut state, &blocks[0], &blocks[1], &blocks[2], &blocks[3]); + + add_round_key(&mut state, &rkeys[..8]); + + let mut rk_off = 8; + loop { + sub_bytes(&mut state); + mix_columns_1(&mut state); + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + rk_off += 8; + + #[cfg(aes_compact)] + { + shift_rows_2(&mut state); + } + + if rk_off == 112 { + break; + } + + #[cfg(not(aes_compact))] + { + sub_bytes(&mut state); + mix_columns_2(&mut state); + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + rk_off += 8; + + sub_bytes(&mut state); + mix_columns_3(&mut state); + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + rk_off += 8; + } + + sub_bytes(&mut state); + mix_columns_0(&mut state); + add_round_key(&mut state, &rkeys[rk_off..(rk_off + 8)]); + rk_off += 8; + } + + #[cfg(not(aes_compact))] + { + shift_rows_2(&mut state); + } + + sub_bytes(&mut state); + add_round_key(&mut state, &rkeys[112..]); + + inv_bitslice(&state) +} + +/// Note that the 4 bitwise NOT (^= 0xffffffffffffffff) are accounted for here so that it is a true +/// inverse of 'sub_bytes'. +fn inv_sub_bytes(state: &mut [u64]) { + debug_assert_eq!(state.len(), 8); + + // Scheduled using https://github.com/Ko-/aes-armcortexm/tree/public/scheduler + // Inline "stack" comments reflect suggested stores and loads (ARM Cortex-M3 and M4) + + let u7 = state[0]; + let u6 = state[1]; + let u5 = state[2]; + let u4 = state[3]; + let u3 = state[4]; + let u2 = state[5]; + let u1 = state[6]; + let u0 = state[7]; + + let t23 = u0 ^ u3; + let t8 = u1 ^ t23; + let m2 = t23 & t8; + let t4 = u4 ^ t8; + let t22 = u1 ^ u3; + let t2 = u0 ^ u1; + let t1 = u3 ^ u4; + // t23 -> stack + let t9 = u7 ^ t1; + // t8 -> stack + let m7 = t22 & t9; + // t9 -> stack + let t24 = u4 ^ u7; + // m7 -> stack + let t10 = t2 ^ t24; + // u4 -> stack + let m14 = t2 & t10; + let r5 = u6 ^ u7; + // m2 -> stack + let t3 = t1 ^ r5; + // t2 -> stack + let t13 = t2 ^ r5; + let t19 = t22 ^ r5; + // t3 -> stack + let t17 = u2 ^ t19; + // t4 -> stack + let t25 = u2 ^ t1; + let r13 = u1 ^ u6; + // t25 -> stack + let t20 = t24 ^ r13; + // t17 -> stack + let m9 = t20 & t17; + // t20 -> stack + let r17 = u2 ^ u5; + // t22 -> stack + let t6 = t22 ^ r17; + // t13 -> stack + let m1 = t13 & t6; + let y5 = u0 ^ r17; + let m4 = t19 & y5; + let m5 = m4 ^ m1; + let m17 = m5 ^ t24; + let r18 = u5 ^ u6; + let t27 = t1 ^ r18; + let t15 = t10 ^ t27; + // t6 -> stack + let m11 = t1 & t15; + let m15 = m14 ^ m11; + let m21 = m17 ^ m15; + // t1 -> stack + // t4 <- stack + let m12 = t4 & t27; + let m13 = m12 ^ m11; + let t14 = t10 ^ r18; + let m3 = t14 ^ m1; + // m2 <- stack + let m16 = m3 ^ m2; + let m20 = m16 ^ m13; + // u4 <- stack + let r19 = u2 ^ u4; + let t16 = r13 ^ r19; + // t3 <- stack + let t26 = t3 ^ t16; + let m6 = t3 & t16; + let m8 = t26 ^ m6; + // t10 -> stack + // m7 <- stack + let m18 = m8 ^ m7; + let m22 = m18 ^ m13; + let m25 = m22 & m20; + let m26 = m21 ^ m25; + let m10 = m9 ^ m6; + let m19 = m10 ^ m15; + // t25 <- stack + let m23 = m19 ^ t25; + let m28 = m23 ^ m25; + let m24 = m22 ^ m23; + let m30 = m26 & m24; + let m39 = m23 ^ m30; + let m48 = m39 & y5; + let m57 = m39 & t19; + // m48 -> stack + let m36 = m24 ^ m25; + let m31 = m20 & m23; + let m27 = m20 ^ m21; + let m32 = m27 & m31; + let m29 = m28 & m27; + let m37 = m21 ^ m29; + // m39 -> stack + let m42 = m37 ^ m39; + let m52 = m42 & t15; + // t27 -> stack + // t1 <- stack + let m61 = m42 & t1; + let p0 = m52 ^ m61; + let p16 = m57 ^ m61; + // m57 -> stack + // t20 <- stack + let m60 = m37 & t20; + // p16 -> stack + // t17 <- stack + let m51 = m37 & t17; + let m33 = m27 ^ m25; + let m38 = m32 ^ m33; + let m43 = m37 ^ m38; + let m49 = m43 & t16; + let p6 = m49 ^ m60; + let p13 = m49 ^ m51; + let m58 = m43 & t3; + // t9 <- stack + let m50 = m38 & t9; + // t22 <- stack + let m59 = m38 & t22; + // p6 -> stack + let p1 = m58 ^ m59; + let p7 = p0 ^ p1; + let m34 = m21 & m22; + let m35 = m24 & m34; + let m40 = m35 ^ m36; + let m41 = m38 ^ m40; + let m45 = m42 ^ m41; + // t27 <- stack + let m53 = m45 & t27; + let p8 = m50 ^ m53; + let p23 = p7 ^ p8; + // t4 <- stack + let m62 = m45 & t4; + let p14 = m49 ^ m62; + let s6 = p14 ^ p23; + // t10 <- stack + let m54 = m41 & t10; + let p2 = m54 ^ m62; + let p22 = p2 ^ p7; + let s0 = p13 ^ p22; + let p17 = m58 ^ p2; + let p15 = m54 ^ m59; + // t2 <- stack + let m63 = m41 & t2; + // m39 <- stack + let m44 = m39 ^ m40; + // p17 -> stack + // t6 <- stack + let m46 = m44 & t6; + let p5 = m46 ^ m51; + // p23 -> stack + let p18 = m63 ^ p5; + let p24 = p5 ^ p7; + // m48 <- stack + let p12 = m46 ^ m48; + let s3 = p12 ^ p22; + // t13 <- stack + let m55 = m44 & t13; + let p9 = m55 ^ m63; + // p16 <- stack + let s7 = p9 ^ p16; + // t8 <- stack + let m47 = m40 & t8; + let p3 = m47 ^ m50; + let p19 = p2 ^ p3; + let s5 = p19 ^ p24; + let p11 = p0 ^ p3; + let p26 = p9 ^ p11; + // t23 <- stack + let m56 = m40 & t23; + let p4 = m48 ^ m56; + // p6 <- stack + let p20 = p4 ^ p6; + let p29 = p15 ^ p20; + let s1 = p26 ^ p29; + // m57 <- stack + let p10 = m57 ^ p4; + let p27 = p10 ^ p18; + // p23 <- stack + let s4 = p23 ^ p27; + let p25 = p6 ^ p10; + let p28 = p11 ^ p25; + // p17 <- stack + let s2 = p17 ^ p28; + + state[0] = s7; + state[1] = s6; + state[2] = s5; + state[3] = s4; + state[4] = s3; + state[5] = s2; + state[6] = s1; + state[7] = s0; +} + +/// Bitsliced implementation of the AES Sbox based on Boyar, Peralta and Calik. +/// +/// See: +/// +/// Note that the 4 bitwise NOT (^= 0xffffffffffffffff) are moved to the key schedule. +fn sub_bytes(state: &mut [u64]) { + debug_assert_eq!(state.len(), 8); + + // Scheduled using https://github.com/Ko-/aes-armcortexm/tree/public/scheduler + // Inline "stack" comments reflect suggested stores and loads (ARM Cortex-M3 and M4) + + let u7 = state[0]; + let u6 = state[1]; + let u5 = state[2]; + let u4 = state[3]; + let u3 = state[4]; + let u2 = state[5]; + let u1 = state[6]; + let u0 = state[7]; + + let y14 = u3 ^ u5; + let y13 = u0 ^ u6; + let y12 = y13 ^ y14; + let t1 = u4 ^ y12; + let y15 = t1 ^ u5; + let t2 = y12 & y15; + let y6 = y15 ^ u7; + let y20 = t1 ^ u1; + // y12 -> stack + let y9 = u0 ^ u3; + // y20 -> stack + let y11 = y20 ^ y9; + // y9 -> stack + let t12 = y9 & y11; + // y6 -> stack + let y7 = u7 ^ y11; + let y8 = u0 ^ u5; + let t0 = u1 ^ u2; + let y10 = y15 ^ t0; + // y15 -> stack + let y17 = y10 ^ y11; + // y14 -> stack + let t13 = y14 & y17; + let t14 = t13 ^ t12; + // y17 -> stack + let y19 = y10 ^ y8; + // y10 -> stack + let t15 = y8 & y10; + let t16 = t15 ^ t12; + let y16 = t0 ^ y11; + // y11 -> stack + let y21 = y13 ^ y16; + // y13 -> stack + let t7 = y13 & y16; + // y16 -> stack + let y18 = u0 ^ y16; + let y1 = t0 ^ u7; + let y4 = y1 ^ u3; + // u7 -> stack + let t5 = y4 & u7; + let t6 = t5 ^ t2; + let t18 = t6 ^ t16; + let t22 = t18 ^ y19; + let y2 = y1 ^ u0; + let t10 = y2 & y7; + let t11 = t10 ^ t7; + let t20 = t11 ^ t16; + let t24 = t20 ^ y18; + let y5 = y1 ^ u6; + let t8 = y5 & y1; + let t9 = t8 ^ t7; + let t19 = t9 ^ t14; + let t23 = t19 ^ y21; + let y3 = y5 ^ y8; + // y6 <- stack + let t3 = y3 & y6; + let t4 = t3 ^ t2; + // y20 <- stack + let t17 = t4 ^ y20; + let t21 = t17 ^ t14; + let t26 = t21 & t23; + let t27 = t24 ^ t26; + let t31 = t22 ^ t26; + let t25 = t21 ^ t22; + // y4 -> stack + let t28 = t25 & t27; + let t29 = t28 ^ t22; + let z14 = t29 & y2; + let z5 = t29 & y7; + let t30 = t23 ^ t24; + let t32 = t31 & t30; + let t33 = t32 ^ t24; + let t35 = t27 ^ t33; + let t36 = t24 & t35; + let t38 = t27 ^ t36; + let t39 = t29 & t38; + let t40 = t25 ^ t39; + let t43 = t29 ^ t40; + // y16 <- stack + let z3 = t43 & y16; + let tc12 = z3 ^ z5; + // tc12 -> stack + // y13 <- stack + let z12 = t43 & y13; + let z13 = t40 & y5; + let z4 = t40 & y1; + let tc6 = z3 ^ z4; + let t34 = t23 ^ t33; + let t37 = t36 ^ t34; + let t41 = t40 ^ t37; + // y10 <- stack + let z8 = t41 & y10; + let z17 = t41 & y8; + let t44 = t33 ^ t37; + // y15 <- stack + let z0 = t44 & y15; + // z17 -> stack + // y12 <- stack + let z9 = t44 & y12; + let z10 = t37 & y3; + let z1 = t37 & y6; + let tc5 = z1 ^ z0; + let tc11 = tc6 ^ tc5; + // y4 <- stack + let z11 = t33 & y4; + let t42 = t29 ^ t33; + let t45 = t42 ^ t41; + // y17 <- stack + let z7 = t45 & y17; + let tc8 = z7 ^ tc6; + // y14 <- stack + let z16 = t45 & y14; + // y11 <- stack + let z6 = t42 & y11; + let tc16 = z6 ^ tc8; + // z14 -> stack + // y9 <- stack + let z15 = t42 & y9; + let tc20 = z15 ^ tc16; + let tc1 = z15 ^ z16; + let tc2 = z10 ^ tc1; + let tc21 = tc2 ^ z11; + let tc3 = z9 ^ tc2; + let s0 = tc3 ^ tc16; + let s3 = tc3 ^ tc11; + let s1 = s3 ^ tc16; + let tc13 = z13 ^ tc1; + // u7 <- stack + let z2 = t33 & u7; + let tc4 = z0 ^ z2; + let tc7 = z12 ^ tc4; + let tc9 = z8 ^ tc7; + let tc10 = tc8 ^ tc9; + // z14 <- stack + let tc17 = z14 ^ tc10; + let s5 = tc21 ^ tc17; + let tc26 = tc17 ^ tc20; + // z17 <- stack + let s2 = tc26 ^ z17; + // tc12 <- stack + let tc14 = tc4 ^ tc12; + let tc18 = tc13 ^ tc14; + let s6 = tc10 ^ tc18; + let s7 = z12 ^ tc18; + let s4 = tc14 ^ s3; + + state[0] = s7; + state[1] = s6; + state[2] = s5; + state[3] = s4; + state[4] = s3; + state[5] = s2; + state[6] = s1; + state[7] = s0; +} + +/// NOT operations that are omitted in S-box +#[inline] +fn sub_bytes_nots(state: &mut [u64]) { + debug_assert_eq!(state.len(), 8); + state[0] ^= 0xffffffffffffffff; + state[1] ^= 0xffffffffffffffff; + state[5] ^= 0xffffffffffffffff; + state[6] ^= 0xffffffffffffffff; +} + +/// Computation of the MixColumns transformation in the fixsliced representation, with different +/// rotations used according to the round number mod 4. +/// +/// Based on Käsper-Schwabe, similar to https://github.com/Ko-/aes-armcortexm. +macro_rules! define_mix_columns { + ( + $name:ident, + $name_inv:ident, + $first_rotate:path, + $second_rotate:path + ) => { + #[rustfmt::skip] + fn $name(state: &mut State) { + let (a0, a1, a2, a3, a4, a5, a6, a7) = ( + state[0], state[1], state[2], state[3], state[4], state[5], state[6], state[7] + ); + let (b0, b1, b2, b3, b4, b5, b6, b7) = ( + $first_rotate(a0), + $first_rotate(a1), + $first_rotate(a2), + $first_rotate(a3), + $first_rotate(a4), + $first_rotate(a5), + $first_rotate(a6), + $first_rotate(a7), + ); + let (c0, c1, c2, c3, c4, c5, c6, c7) = ( + a0 ^ b0, + a1 ^ b1, + a2 ^ b2, + a3 ^ b3, + a4 ^ b4, + a5 ^ b5, + a6 ^ b6, + a7 ^ b7, + ); + state[0] = b0 ^ c7 ^ $second_rotate(c0); + state[1] = b1 ^ c0 ^ c7 ^ $second_rotate(c1); + state[2] = b2 ^ c1 ^ $second_rotate(c2); + state[3] = b3 ^ c2 ^ c7 ^ $second_rotate(c3); + state[4] = b4 ^ c3 ^ c7 ^ $second_rotate(c4); + state[5] = b5 ^ c4 ^ $second_rotate(c5); + state[6] = b6 ^ c5 ^ $second_rotate(c6); + state[7] = b7 ^ c6 ^ $second_rotate(c7); + } + + #[rustfmt::skip] + fn $name_inv(state: &mut State) { + let (a0, a1, a2, a3, a4, a5, a6, a7) = ( + state[0], state[1], state[2], state[3], state[4], state[5], state[6], state[7] + ); + let (b0, b1, b2, b3, b4, b5, b6, b7) = ( + $first_rotate(a0), + $first_rotate(a1), + $first_rotate(a2), + $first_rotate(a3), + $first_rotate(a4), + $first_rotate(a5), + $first_rotate(a6), + $first_rotate(a7), + ); + let (c0, c1, c2, c3, c4, c5, c6, c7) = ( + a0 ^ b0, + a1 ^ b1, + a2 ^ b2, + a3 ^ b3, + a4 ^ b4, + a5 ^ b5, + a6 ^ b6, + a7 ^ b7, + ); + let (d0, d1, d2, d3, d4, d5, d6, d7) = ( + a0 ^ c7, + a1 ^ c0 ^ c7, + a2 ^ c1, + a3 ^ c2 ^ c7, + a4 ^ c3 ^ c7, + a5 ^ c4, + a6 ^ c5, + a7 ^ c6, + ); + let (e0, e1, e2, e3, e4, e5, e6, e7) = ( + c0 ^ d6, + c1 ^ d6 ^ d7, + c2 ^ d0 ^ d7, + c3 ^ d1 ^ d6, + c4 ^ d2 ^ d6 ^ d7, + c5 ^ d3 ^ d7, + c6 ^ d4, + c7 ^ d5, + ); + state[0] = d0 ^ e0 ^ $second_rotate(e0); + state[1] = d1 ^ e1 ^ $second_rotate(e1); + state[2] = d2 ^ e2 ^ $second_rotate(e2); + state[3] = d3 ^ e3 ^ $second_rotate(e3); + state[4] = d4 ^ e4 ^ $second_rotate(e4); + state[5] = d5 ^ e5 ^ $second_rotate(e5); + state[6] = d6 ^ e6 ^ $second_rotate(e6); + state[7] = d7 ^ e7 ^ $second_rotate(e7); + } + } +} + +define_mix_columns!( + mix_columns_0, + inv_mix_columns_0, + rotate_rows_1, + rotate_rows_2 +); + +define_mix_columns!( + mix_columns_1, + inv_mix_columns_1, + rotate_rows_and_columns_1_1, + rotate_rows_and_columns_2_2 +); + +#[cfg(not(aes_compact))] +define_mix_columns!( + mix_columns_2, + inv_mix_columns_2, + rotate_rows_and_columns_1_2, + rotate_rows_2 +); + +#[cfg(not(aes_compact))] +define_mix_columns!( + mix_columns_3, + inv_mix_columns_3, + rotate_rows_and_columns_1_3, + rotate_rows_and_columns_2_2 +); + +#[inline] +fn delta_swap_1(a: &mut u64, shift: u32, mask: u64) { + let t = (*a ^ ((*a) >> shift)) & mask; + *a ^= t ^ (t << shift); +} + +#[inline] +fn delta_swap_2(a: &mut u64, b: &mut u64, shift: u32, mask: u64) { + let t = (*a ^ ((*b) >> shift)) & mask; + *a ^= t; + *b ^= t << shift; +} + +/// Applies ShiftRows once on an AES state (or key). +#[cfg(any(not(aes_compact), feature = "hazmat"))] +#[inline] +fn shift_rows_1(state: &mut [u64]) { + debug_assert_eq!(state.len(), 8); + for x in state.iter_mut() { + delta_swap_1(x, 8, 0x00f000ff000f0000); + delta_swap_1(x, 4, 0x0f0f00000f0f0000); + } +} + +/// Applies ShiftRows twice on an AES state (or key). +#[inline] +fn shift_rows_2(state: &mut [u64]) { + debug_assert_eq!(state.len(), 8); + for x in state.iter_mut() { + delta_swap_1(x, 8, 0x00ff000000ff0000); + } +} + +/// Applies ShiftRows three times on an AES state (or key). +#[inline] +fn shift_rows_3(state: &mut [u64]) { + debug_assert_eq!(state.len(), 8); + for x in state.iter_mut() { + delta_swap_1(x, 8, 0x000f00ff00f00000); + delta_swap_1(x, 4, 0x0f0f00000f0f0000); + } +} + +#[inline(always)] +fn inv_shift_rows_1(state: &mut [u64]) { + shift_rows_3(state); +} + +#[inline(always)] +fn inv_shift_rows_2(state: &mut [u64]) { + shift_rows_2(state); +} + +#[cfg(not(aes_compact))] +#[inline(always)] +fn inv_shift_rows_3(state: &mut [u64]) { + shift_rows_1(state); +} + +/// XOR the columns after the S-box during the key schedule round function. +/// +/// The `idx_xor` parameter refers to the index of the previous round key that is +/// involved in the XOR computation (should be 8 and 16 for AES-128 and AES-256, +/// respectively). +/// +/// The `idx_ror` parameter refers to the rotation value, which varies between the +/// different key schedules. +fn xor_columns(rkeys: &mut [u64], offset: usize, idx_xor: usize, idx_ror: u32) { + for i in 0..8 { + let off_i = offset + i; + let rk = rkeys[off_i - idx_xor] ^ (0x000f000f000f000f & ror(rkeys[off_i], idx_ror)); + rkeys[off_i] = rk + ^ (0xfff0fff0fff0fff0 & (rk << 4)) + ^ (0xff00ff00ff00ff00 & (rk << 8)) + ^ (0xf000f000f000f000 & (rk << 12)); + } +} + +/// Bitslice four 128-bit input blocks input0, input1, input2, input3 into a 512-bit internal state. +fn bitslice(output: &mut [u64], input0: &[u8], input1: &[u8], input2: &[u8], input3: &[u8]) { + debug_assert_eq!(output.len(), 8); + debug_assert_eq!(input0.len(), 16); + debug_assert_eq!(input1.len(), 16); + debug_assert_eq!(input2.len(), 16); + debug_assert_eq!(input3.len(), 16); + + // Bitslicing is a bit index manipulation. 512 bits of data means each bit is positioned at a + // 9-bit index. AES data is 4 blocks, each one a 4x4 column-major matrix of bytes, so the + // index is initially ([b]lock, [c]olumn, [r]ow, [p]osition): + // b1 b0 c1 c0 r1 r0 p2 p1 p0 + // + // The desired bitsliced data groups first by bit position, then row, column, block: + // p2 p1 p0 r1 r0 c1 c0 b1 b0 + + #[rustfmt::skip] + fn read_reordered(input: &[u8]) -> u64 { + (u64::from(input[0x0]) ) | + (u64::from(input[0x1]) << 0x10) | + (u64::from(input[0x2]) << 0x20) | + (u64::from(input[0x3]) << 0x30) | + (u64::from(input[0x8]) << 0x08) | + (u64::from(input[0x9]) << 0x18) | + (u64::from(input[0xa]) << 0x28) | + (u64::from(input[0xb]) << 0x38) + } + + // Reorder each block's bytes on input + // __ __ c1 c0 r1 r0 __ __ __ => __ __ c0 r1 r0 c1 __ __ __ + // Reorder by relabeling (note the order of input) + // b1 b0 c0 __ __ __ __ __ __ => c0 b1 b0 __ __ __ __ __ __ + let mut t0 = read_reordered(&input0[0x00..0x0c]); + let mut t4 = read_reordered(&input0[0x04..0x10]); + let mut t1 = read_reordered(&input1[0x00..0x0c]); + let mut t5 = read_reordered(&input1[0x04..0x10]); + let mut t2 = read_reordered(&input2[0x00..0x0c]); + let mut t6 = read_reordered(&input2[0x04..0x10]); + let mut t3 = read_reordered(&input3[0x00..0x0c]); + let mut t7 = read_reordered(&input3[0x04..0x10]); + + // Bit Index Swap 6 <-> 0: + // __ __ b0 __ __ __ __ __ p0 => __ __ p0 __ __ __ __ __ b0 + let m0 = 0x5555555555555555; + delta_swap_2(&mut t1, &mut t0, 1, m0); + delta_swap_2(&mut t3, &mut t2, 1, m0); + delta_swap_2(&mut t5, &mut t4, 1, m0); + delta_swap_2(&mut t7, &mut t6, 1, m0); + + // Bit Index Swap 7 <-> 1: + // __ b1 __ __ __ __ __ p1 __ => __ p1 __ __ __ __ __ b1 __ + let m1 = 0x3333333333333333; + delta_swap_2(&mut t2, &mut t0, 2, m1); + delta_swap_2(&mut t3, &mut t1, 2, m1); + delta_swap_2(&mut t6, &mut t4, 2, m1); + delta_swap_2(&mut t7, &mut t5, 2, m1); + + // Bit Index Swap 8 <-> 2: + // c0 __ __ __ __ __ p2 __ __ => p2 __ __ __ __ __ c0 __ __ + let m2 = 0x0f0f0f0f0f0f0f0f; + delta_swap_2(&mut t4, &mut t0, 4, m2); + delta_swap_2(&mut t5, &mut t1, 4, m2); + delta_swap_2(&mut t6, &mut t2, 4, m2); + delta_swap_2(&mut t7, &mut t3, 4, m2); + + // Final bitsliced bit index, as desired: + // p2 p1 p0 r1 r0 c1 c0 b1 b0 + output[0] = t0; + output[1] = t1; + output[2] = t2; + output[3] = t3; + output[4] = t4; + output[5] = t5; + output[6] = t6; + output[7] = t7; +} + +/// Un-bitslice a 512-bit internal state into four 128-bit blocks of output. +fn inv_bitslice(input: &[u64]) -> BatchBlocks { + debug_assert_eq!(input.len(), 8); + + // Unbitslicing is a bit index manipulation. 512 bits of data means each bit is positioned at + // a 9-bit index. AES data is 4 blocks, each one a 4x4 column-major matrix of bytes, so the + // desired index for the output is ([b]lock, [c]olumn, [r]ow, [p]osition): + // b1 b0 c1 c0 r1 r0 p2 p1 p0 + // + // The initially bitsliced data groups first by bit position, then row, column, block: + // p2 p1 p0 r1 r0 c1 c0 b1 b0 + + let mut t0 = input[0]; + let mut t1 = input[1]; + let mut t2 = input[2]; + let mut t3 = input[3]; + let mut t4 = input[4]; + let mut t5 = input[5]; + let mut t6 = input[6]; + let mut t7 = input[7]; + + // TODO: these bit index swaps are identical to those in 'packing' + + // Bit Index Swap 6 <-> 0: + // __ __ p0 __ __ __ __ __ b0 => __ __ b0 __ __ __ __ __ p0 + let m0 = 0x5555555555555555; + delta_swap_2(&mut t1, &mut t0, 1, m0); + delta_swap_2(&mut t3, &mut t2, 1, m0); + delta_swap_2(&mut t5, &mut t4, 1, m0); + delta_swap_2(&mut t7, &mut t6, 1, m0); + + // Bit Index Swap 7 <-> 1: + // __ p1 __ __ __ __ __ b1 __ => __ b1 __ __ __ __ __ p1 __ + let m1 = 0x3333333333333333; + delta_swap_2(&mut t2, &mut t0, 2, m1); + delta_swap_2(&mut t3, &mut t1, 2, m1); + delta_swap_2(&mut t6, &mut t4, 2, m1); + delta_swap_2(&mut t7, &mut t5, 2, m1); + + // Bit Index Swap 8 <-> 2: + // p2 __ __ __ __ __ c0 __ __ => c0 __ __ __ __ __ p2 __ __ + let m2 = 0x0f0f0f0f0f0f0f0f; + delta_swap_2(&mut t4, &mut t0, 4, m2); + delta_swap_2(&mut t5, &mut t1, 4, m2); + delta_swap_2(&mut t6, &mut t2, 4, m2); + delta_swap_2(&mut t7, &mut t3, 4, m2); + + #[rustfmt::skip] + fn write_reordered(columns: u64, output: &mut [u8]) { + output[0x0] = (columns ) as u8; + output[0x1] = (columns >> 0x10) as u8; + output[0x2] = (columns >> 0x20) as u8; + output[0x3] = (columns >> 0x30) as u8; + output[0x8] = (columns >> 0x08) as u8; + output[0x9] = (columns >> 0x18) as u8; + output[0xa] = (columns >> 0x28) as u8; + output[0xb] = (columns >> 0x38) as u8; + } + + let mut output = BatchBlocks::default(); + // Reorder by relabeling (note the order of output) + // c0 b1 b0 __ __ __ __ __ __ => b1 b0 c0 __ __ __ __ __ __ + // Reorder each block's bytes on output + // __ __ c0 r1 r0 c1 __ __ __ => __ __ c1 c0 r1 r0 __ __ __ + write_reordered(t0, &mut output[0][0x00..0x0c]); + write_reordered(t4, &mut output[0][0x04..0x10]); + write_reordered(t1, &mut output[1][0x00..0x0c]); + write_reordered(t5, &mut output[1][0x04..0x10]); + write_reordered(t2, &mut output[2][0x00..0x0c]); + write_reordered(t6, &mut output[2][0x04..0x10]); + write_reordered(t3, &mut output[3][0x00..0x0c]); + write_reordered(t7, &mut output[3][0x04..0x10]); + + // Final AES bit index, as desired: + // b1 b0 c1 c0 r1 r0 p2 p1 p0 + output +} + +/// Copy 32-bytes within the provided slice to an 8-byte offset +fn memshift32(buffer: &mut [u64], src_offset: usize) { + debug_assert_eq!(src_offset % 8, 0); + + let dst_offset = src_offset + 8; + debug_assert!(dst_offset + 8 <= buffer.len()); + + for i in (0..8).rev() { + buffer[dst_offset + i] = buffer[src_offset + i]; + } +} + +/// XOR the round key to the internal state. The round keys are expected to be +/// pre-computed and to be packed in the fixsliced representation. +#[inline] +fn add_round_key(state: &mut State, rkey: &[u64]) { + debug_assert_eq!(rkey.len(), 8); + for (a, b) in state.iter_mut().zip(rkey) { + *a ^= b; + } +} + +#[inline(always)] +fn add_round_constant_bit(state: &mut [u64], bit: usize) { + state[bit] ^= 0x00000000f0000000; +} + +#[inline(always)] +fn ror(x: u64, y: u32) -> u64 { + x.rotate_right(y) +} + +#[inline(always)] +fn ror_distance(rows: u32, cols: u32) -> u32 { + (rows << 4) + (cols << 2) +} + +#[inline(always)] +fn rotate_rows_1(x: u64) -> u64 { + ror(x, ror_distance(1, 0)) +} + +#[inline(always)] +fn rotate_rows_2(x: u64) -> u64 { + ror(x, ror_distance(2, 0)) +} + +#[inline(always)] +#[rustfmt::skip] +fn rotate_rows_and_columns_1_1(x: u64) -> u64 { + (ror(x, ror_distance(1, 1)) & 0x0fff0fff0fff0fff) | + (ror(x, ror_distance(0, 1)) & 0xf000f000f000f000) +} + +#[cfg(not(aes_compact))] +#[inline(always)] +#[rustfmt::skip] +fn rotate_rows_and_columns_1_2(x: u64) -> u64 { + (ror(x, ror_distance(1, 2)) & 0x00ff00ff00ff00ff) | + (ror(x, ror_distance(0, 2)) & 0xff00ff00ff00ff00) +} + +#[cfg(not(aes_compact))] +#[inline(always)] +#[rustfmt::skip] +fn rotate_rows_and_columns_1_3(x: u64) -> u64 { + (ror(x, ror_distance(1, 3)) & 0x000f000f000f000f) | + (ror(x, ror_distance(0, 3)) & 0xfff0fff0fff0fff0) +} + +#[inline(always)] +#[rustfmt::skip] +fn rotate_rows_and_columns_2_2(x: u64) -> u64 { + (ror(x, ror_distance(2, 2)) & 0x00ff00ff00ff00ff) | + (ror(x, ror_distance(1, 2)) & 0xff00ff00ff00ff00) +} + +/// Low-level "hazmat" AES functions. +/// +/// Note: this isn't actually used in the `Aes128`/`Aes192`/`Aes256` +/// implementations in this crate, but instead provides raw access to +/// the AES round function gated under the `hazmat` crate feature. +#[cfg(feature = "hazmat")] +pub(crate) mod hazmat { + use super::{ + bitslice, inv_bitslice, inv_mix_columns_0, inv_shift_rows_1, inv_sub_bytes, mix_columns_0, + shift_rows_1, sub_bytes, sub_bytes_nots, State, + }; + use crate::{Block, Block8}; + + /// XOR the `src` block into the `dst` block in-place. + fn xor_in_place(dst: &mut Block, src: &Block) { + for (a, b) in dst.iter_mut().zip(src.as_slice()) { + *a ^= *b; + } + } + + /// Perform a bitslice operation, loading a single block. + fn bitslice_block(block: &Block) -> State { + let mut state = State::default(); + bitslice(&mut state, block, block, block, block); + state + } + + /// Perform an inverse bitslice operation, extracting a single block. + fn inv_bitslice_block(block: &mut Block, state: &State) { + block.copy_from_slice(&inv_bitslice(state)[0]); + } + + /// AES cipher (encrypt) round function. + #[inline] + pub(crate) fn cipher_round(block: &mut Block, round_key: &Block) { + let mut state = bitslice_block(block); + sub_bytes(&mut state); + sub_bytes_nots(&mut state); + shift_rows_1(&mut state); + mix_columns_0(&mut state); + inv_bitslice_block(block, &state); + xor_in_place(block, round_key); + } + + /// AES cipher (encrypt) round function: parallel version. + #[inline] + pub(crate) fn cipher_round_par(blocks: &mut Block8, round_keys: &Block8) { + for (chunk, keys) in blocks.chunks_exact_mut(4).zip(round_keys.chunks_exact(4)) { + let mut state = State::default(); + bitslice(&mut state, &chunk[0], &chunk[1], &chunk[2], &chunk[3]); + sub_bytes(&mut state); + sub_bytes_nots(&mut state); + shift_rows_1(&mut state); + mix_columns_0(&mut state); + let res = inv_bitslice(&state); + + for i in 0..4 { + chunk[i] = res[i]; + xor_in_place(&mut chunk[i], &keys[i]); + } + } + } + + /// AES cipher (encrypt) round function. + #[inline] + pub(crate) fn equiv_inv_cipher_round(block: &mut Block, round_key: &Block) { + let mut state = State::default(); + bitslice(&mut state, block, block, block, block); + sub_bytes_nots(&mut state); + inv_sub_bytes(&mut state); + inv_shift_rows_1(&mut state); + inv_mix_columns_0(&mut state); + inv_bitslice_block(block, &state); + xor_in_place(block, round_key); + } + + /// AES cipher (encrypt) round function: parallel version. + #[inline] + pub(crate) fn equiv_inv_cipher_round_par(blocks: &mut Block8, round_keys: &Block8) { + for (chunk, keys) in blocks.chunks_exact_mut(4).zip(round_keys.chunks_exact(4)) { + let mut state = State::default(); + bitslice(&mut state, &chunk[0], &chunk[1], &chunk[2], &chunk[3]); + sub_bytes_nots(&mut state); + inv_sub_bytes(&mut state); + inv_shift_rows_1(&mut state); + inv_mix_columns_0(&mut state); + let res = inv_bitslice(&state); + + for i in 0..4 { + chunk[i] = res[i]; + xor_in_place(&mut chunk[i], &keys[i]); + } + } + } + + /// AES mix columns function. + #[inline] + pub(crate) fn mix_columns(block: &mut Block) { + let mut state = bitslice_block(block); + mix_columns_0(&mut state); + inv_bitslice_block(block, &state); + } + + /// AES inverse mix columns function. + #[inline] + pub(crate) fn inv_mix_columns(block: &mut Block) { + let mut state = bitslice_block(block); + inv_mix_columns_0(&mut state); + inv_bitslice_block(block, &state); + } +} diff --git a/tests/data/aes128.blb b/tests/data/aes128.blb new file mode 100644 index 0000000000000000000000000000000000000000..0accb99eb444c47207eebd13422e0505c3052994 GIT binary patch literal 27418 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�#whGFaOQqMXc`x5# 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 literal 0 HcmV?d00001 diff --git a/tests/data/aes192.blb b/tests/data/aes192.blb new file mode 100644 index 0000000000000000000000000000000000000000..b5f70fa00ce7ed9f68fdc43b979fc5fba5b3142d GIT binary patch literal 41322 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 RSD2B=VX++Wu?{1c{tv)^Sl0jm literal 0 HcmV?d00001 diff --git a/tests/hazmat.rs b/tests/hazmat.rs new file mode 100644 index 0000000..ce0e8f8 --- /dev/null +++ b/tests/hazmat.rs @@ -0,0 +1,155 @@ +//! Tests for low-level "hazmat" AES functions. + +// TODO(tarcieri): support for using the hazmat functions with the `soft` backend +#![cfg(feature = "hazmat")] + +use aes::{Block, Block8}; +use hex_literal::hex; + +/// Round function tests vectors. +struct RoundTestVector { + /// State at start of `round[r]`. + start: [u8; 16], + + /// Key schedule value for `round[r]`. + k_sch: [u8; 16], + + /// Cipher output. + output: [u8; 16], +} + +/// Cipher round function test vectors from FIPS 197 Appendix C.1. +const CIPHER_ROUND_TEST_VECTORS: &[RoundTestVector] = &[ + // round 1 + RoundTestVector { + start: hex!("00102030405060708090a0b0c0d0e0f0"), + k_sch: hex!("d6aa74fdd2af72fadaa678f1d6ab76fe"), + output: hex!("89d810e8855ace682d1843d8cb128fe4"), + }, + // round 2 + RoundTestVector { + start: hex!("89d810e8855ace682d1843d8cb128fe4"), + k_sch: hex!("b692cf0b643dbdf1be9bc5006830b3fe"), + output: hex!("4915598f55e5d7a0daca94fa1f0a63f7"), + }, + // round 3 + RoundTestVector { + start: hex!("4915598f55e5d7a0daca94fa1f0a63f7"), + k_sch: hex!("b6ff744ed2c2c9bf6c590cbf0469bf41"), + output: hex!("fa636a2825b339c940668a3157244d17"), + }, + // round 4 + RoundTestVector { + start: hex!("fa636a2825b339c940668a3157244d17"), + k_sch: hex!("47f7f7bc95353e03f96c32bcfd058dfd"), + output: hex!("247240236966b3fa6ed2753288425b6c"), + }, +]; + +/// Equivalent Inverse Cipher round function test vectors from FIPS 197 Appendix C.1. +const EQUIV_INV_CIPHER_ROUND_TEST_VECTORS: &[RoundTestVector] = &[ + // round 1 + RoundTestVector { + start: hex!("7ad5fda789ef4e272bca100b3d9ff59f"), + k_sch: hex!("13aa29be9c8faff6f770f58000f7bf03"), + output: hex!("54d990a16ba09ab596bbf40ea111702f"), + }, + // round 2 + RoundTestVector { + start: hex!("54d990a16ba09ab596bbf40ea111702f"), + k_sch: hex!("1362a4638f2586486bff5a76f7874a83"), + output: hex!("3e1c22c0b6fcbf768da85067f6170495"), + }, + // round 3 + RoundTestVector { + start: hex!("3e1c22c0b6fcbf768da85067f6170495"), + k_sch: hex!("8d82fc749c47222be4dadc3e9c7810f5"), + output: hex!("b458124c68b68a014b99f82e5f15554c"), + }, + // round 4 + RoundTestVector { + start: hex!("b458124c68b68a014b99f82e5f15554c"), + k_sch: hex!("72e3098d11c5de5f789dfe1578a2cccb"), + output: hex!("e8dab6901477d4653ff7f5e2e747dd4f"), + }, +]; + +#[test] +fn cipher_round_fips197_vectors() { + for vector in CIPHER_ROUND_TEST_VECTORS { + let mut block = Block::from(vector.start); + aes::hazmat::cipher_round(&mut block, &vector.k_sch.into()); + assert_eq!(block.as_slice(), &vector.output); + } +} + +#[test] +fn cipher_round_par_fips197_vectors() { + let mut blocks = Block8::default(); + let mut round_keys = Block8::default(); + + for i in 0..4 { + let vector = &CIPHER_ROUND_TEST_VECTORS[i]; + + blocks[i] = Block::from(vector.start); + blocks[i + 4] = Block::from(vector.start); + + round_keys[i] = Block::from(vector.k_sch); + round_keys[i + 4] = Block::from(vector.k_sch); + } + + aes::hazmat::cipher_round_par(&mut blocks, &round_keys); + + for i in 0..4 { + let vector = &CIPHER_ROUND_TEST_VECTORS[i]; + assert_eq!(blocks[i].as_slice(), &vector.output); + assert_eq!(blocks[i + 4].as_slice(), &vector.output); + } +} + +#[test] +fn equiv_inv_cipher_round_fips197_vectors() { + for vector in EQUIV_INV_CIPHER_ROUND_TEST_VECTORS { + let mut block = Block::from(vector.start); + aes::hazmat::equiv_inv_cipher_round(&mut block, &vector.k_sch.into()); + assert_eq!(block.as_slice(), &vector.output); + } +} + +#[test] +fn equiv_inv_cipher_round_par_fips197_vectors() { + let mut blocks = Block8::default(); + let mut round_keys = Block8::default(); + + for i in 0..4 { + let vector = &EQUIV_INV_CIPHER_ROUND_TEST_VECTORS[i]; + + blocks[i] = Block::from(vector.start); + blocks[i + 4] = Block::from(vector.start); + + round_keys[i] = Block::from(vector.k_sch); + round_keys[i + 4] = Block::from(vector.k_sch); + } + + aes::hazmat::equiv_inv_cipher_round_par(&mut blocks, &round_keys); + + for i in 0..4 { + let vector = &EQUIV_INV_CIPHER_ROUND_TEST_VECTORS[i]; + assert_eq!(blocks[i].as_slice(), &vector.output); + assert_eq!(blocks[i + 4].as_slice(), &vector.output); + } +} + +#[test] +fn mix_columns_fips197_vector() { + let mut block = Block::from(hex!("6353e08c0960e104cd70b751bacad0e7")); + aes::hazmat::mix_columns(&mut block); + assert_eq!(block.as_slice(), &hex!("5f72641557f5bc92f7be3b291db9f91a")) +} + +#[test] +fn inv_mix_columns_fips197_vector() { + let mut block = Block::from(hex!("bd6e7c3df2b5779e0b61216e8b10b689")); + aes::hazmat::inv_mix_columns(&mut block); + assert_eq!(block.as_slice(), &hex!("4773b91ff72f354361cb018ea1e6cf2c")) +} diff --git a/tests/mod.rs b/tests/mod.rs new file mode 100644 index 0000000..4164e4f --- /dev/null +++ b/tests/mod.rs @@ -0,0 +1,6 @@ +//! Test vectors are from NESSIE: +//! https://www.cosic.esat.kuleuven.be/nessie/testvectors/ + +cipher::block_cipher_test!(aes128_test, "aes128", aes::Aes128); +cipher::block_cipher_test!(aes192_test, "aes192", aes::Aes192); +cipher::block_cipher_test!(aes256_test, "aes256", aes::Aes256); -- 2.7.4