diff --git a/Cargo.lock b/Cargo.lock index 6106a31a..6758b7c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,9 +15,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.1" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6cd65a4b849ace0b7f6daeebcc1a1d111282227ca745458c61dbf670e52a597" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" dependencies = [ "anstyle", "anstyle-parse", @@ -29,15 +29,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" dependencies = [ "utf8parse", ] @@ -53,9 +53,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0238ca56c96dfa37bdf7c373c8886dd591322500aceeeccdb2216fe06dc2f796" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" dependencies = [ "anstyle", "windows-sys", @@ -178,7 +178,8 @@ checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" [[package]] name = "cmov" version = "0.3.0" -source = "git+https://github.com/RustCrypto/utils?branch=master#eb03093d97e2f2a61c4bf1135a2492af343ccaa3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aadf78c7ab2a10d7fd054aeb003c89ba2d3fb0dec68a100a057acf760040822b" [[package]] name = "colorchoice" @@ -222,6 +223,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "digest" version = "0.10.7" @@ -265,25 +275,14 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" dependencies = [ - "errno-dragonfly", "libc", "windows-sys", ] -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "expect-test" version = "1.4.1" @@ -296,9 +295,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "fnv" @@ -335,9 +334,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "hex" @@ -368,30 +367,30 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" [[package]] name = "libm" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "linkme" -version = "0.3.15" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f948366ad5bb46b5514ba7a7a80643726eef08b06632592699676748c8bc33b" +checksum = "4ad5707f9d2423042c4321155b22d5257c2f140e3f30d0a42dce882a6010010d" dependencies = [ "linkme-impl", ] [[package]] name = "linkme-impl" -version = "0.3.15" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc28438cad73dcc90ff3466fc329a9252b1b8ba668eb0d5668ba97088cf4eef0" +checksum = "b43a5344be08996a47cb02b1ddc737119578a1cd01ae60d91541864df5926db9" dependencies = [ "proc-macro2", "quote", @@ -400,9 +399,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.5" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" [[package]] name = "lockstitch" @@ -410,6 +409,7 @@ version = "0.12.4" dependencies = [ "aes", "cmov", + "ctr", "divan", "expect-test", "hex", @@ -422,9 +422,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", "libm", @@ -454,9 +454,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] @@ -546,9 +546,9 @@ dependencies = [ [[package]] name = "regex-lite" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f96ede7f386ba6e910092e7ccdc04176cface62abebea07ed6b46d870ed95ca2" +checksum = "83aa2b788f84cf0e43e6ae57bb0fcdbbe7604414a7d2bf997a311b062a6f4291" [[package]] name = "regex-syntax" @@ -558,9 +558,9 @@ checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "rustix" -version = "0.38.11" +version = "0.38.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0c3dde1fc030af041adc40e79c0e7fbcf431dd24870053d187d7c66e4b87453" +checksum = "5a74ee2d7c2581cd139b42447d7d9389b889bdaad3a73f1ebb16f2a3237bb19c" dependencies = [ "bitflags 2.4.0", "errno", @@ -610,9 +610,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "2.0.31" +version = "2.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" dependencies = [ "proc-macro2", "quote", @@ -644,9 +644,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unarray" @@ -656,9 +656,9 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "utf8parse" diff --git a/Cargo.toml b/Cargo.toml index c8342b5c..9c07f525 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,27 +3,27 @@ name = "lockstitch" version = "0.12.4" edition = "2021" authors = ["Coda Hale "] -license = "MIT" +license = "MIT OR Apache-2.0" description = "Lockstitch is an incremental, stateful cryptographic primitive for symmetric-key cryptographic operations in complex protocols. " homepage = "https://github.com/codahale/lockstitch" documentation = "https://docs.rs/lockstitch/" -keywords = ["crypto", "aegis-128l", "sha-256", "hazmat"] +keywords = ["crypto", "aes-128-ctr", "sha-256", "hazmat"] categories = ["cryptography", "no-std"] readme = "README.md" -include = ["src/**/*", "benches/**/*", "tests/**/*", "LICENSE", "README.md", "design.md", "perf.md"] +include = ["src/**/*", "benches/**/*", "tests/**/*", "LICENSE-*", "README.md", "design.md", "perf.md"] [dependencies] -aes = { version = "0.8.3", features = ["hazmat"], optional = true } -cmov = { git = "https://github.com/RustCrypto/utils", branch = "master", package = "cmov" } +aes = "0.8.3" +cmov = "0.3.0" +ctr = "0.9.2" rand_core = { version = "0.6.4", default-features = false, optional = true } -sha2 = { version = "0.10.8", default-features = false } +sha2 = "0.10.8" [features] -default = ["asm", "hedge", "std"] asm = ["sha2/asm"] +default = ["asm", "hedge", "std"] docs = [] hedge = ["rand_core"] -portable = ["aes"] std = [] [workspace] diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,202 @@ + + 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 b/LICENSE-MIT similarity index 96% rename from LICENSE rename to LICENSE-MIT index e1ccc5fc..8fe9686e 100644 --- a/LICENSE +++ b/LICENSE-MIT @@ -1,4 +1,4 @@ -Copyright (c) 2023 Coda Hale, Frank Denis +Copyright (c) 2021 Coda Hale Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated diff --git a/README.md b/README.md index 856116c7..8b7993b8 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,8 @@ Lockstitch is an incremental, stateful cryptographic primitive for symmetric-key cryptographic operations (e.g. hashing, encryption, message authentication codes, and authenticated encryption) in complex protocols. Inspired by TupleHash, STROBE, Noise Protocol's stateful objects, and Xoodyak's -Cyclist mode, Lockstitch uses the [AEGIS-128L][] authenticated cipher and SHA-256 to provide 100+ -Gb/sec performance on modern processors at a 128-bit security level. - -[AEGIS-128L]: https://www.ietf.org/archive/id/draft-irtf-cfrg-aegis-aead-04.html +Cyclist mode, Lockstitch uses AES-128-CTR and SHA-256 to provide ~1 GiB/sec performance on modern +processors at a 128-bit security level. ## ⚠️ WARNING: You should not use this. ⚠️ @@ -19,12 +17,11 @@ In addition, there is absolutely no guarantee of backwards compatibility. ## Design -A Lockstitch protocol is a stateful object which has five different operations: +A Lockstitch protocol is a stateful object which has four different operations: * `Mix`: Mixes a piece of data into the protocol's state, making all future outputs dependent on it. * `Derive`: Outputs bytes of pseudo-random data dependent on the protocol's prior state. * `Encrypt`/`Decrypt`: Encrypts and decrypts data using the protocol's state as the key. -* `Seal`/`Open`: Similar to `Encrypt`/`Decrypt` but uses a MAC to ensure authenticity. * `Ratchet`: Irreversibly modifies the protocol's state, preventing rollback. Using these operations, one can construct a wide variety of symmetric-key constructions. @@ -102,45 +99,22 @@ assert_eq!(aead_decrypt(b"a key", b"a nonce", b"some data", &bad_ciphertext), No ## Cargo Features * `asm`: Enables hand-coded assembly for SHA-256 for `x86` and `x86_64` and a vectorized - implementation for `aarch64`. Enabled by default. +implementation for `aarch64`. Enabled by default. +* `docs`: Enables the docs-only `perf` and `design` modules. * `hedge`: Enables hedged random value generation with `rand_core`. Enabled by default. -* `portable`: Uses the portable `aes` crate for AEGIS-128L at a steep performance penalty. * `std`: Enables features based on the Rust standard library. Enabled by default. ## Performance -Lockstitch's SHA-256 and AEGIS-128L implementations benefit significantly from the use of specific -CPU instructions. - ### `x86`/`x86_64` -On `x86`/`x86_64` CPUs, Lockstitch achieves its best performance with the `aes` and `ssse3` target -features enabled. - -To compile a binary with support for these features, create a `.cargo/config.toml` file with the -following: - -```toml -[build] -rustflags = ["-C", "target-feature=+aes,+ssse3"] -``` - -Or use the following `RUSTFLAGS` environment variable: - -```sh -export RUSTFLAGS="-C target-feature=+aes,+ssse3" -``` +Both the `aes` and `sha2` crates auto-detect CPU support at runtime and will use the most optimized +backend. ### `aarch64` -On `aarch64-darwin-apple` (i.e. macOS), the ARMv8-A cryptography instructions and NEON vector -instructions are enabled by default. On other targets (e.g. `aarch64-unknown-linux-gnu`), the `sha3` -and `aes` target features should be enabled. - -### Other - -For other platforms, the `portable` crate feature provides a very slow but fully portable AES -implementation. +In order to enable CPU support for AES and SHA2, enable the `asm` crate feature and include +`--cfg aes_armv8` in the `RUSTFLAGS` of your application. ## Additional Information @@ -149,8 +123,6 @@ For more information on performance, see [`perf.md`](perf.md). ## License -Copyright © 2023 Coda Hale, Frank Denis - -AEGIS-128L implementation adapted from [rust-aegis](https://github.com/jedisct1/rust-aegis/). +Copyright © 2023 Coda Hale -Distributed under the MIT License. +Distributed under the Apache License 2.0 or MIT License. diff --git a/design.md b/design.md index 28b7bb66..79bc48bb 100644 --- a/design.md +++ b/design.md @@ -2,9 +2,7 @@ Lockstitch provides a single cryptographic service for all symmetric-key operations and an incremental, stateful building block for complex schemes, constructions, and protocols, all built on -top of SHA-256 and [AEGIS-128L][], an authenticated cipher. - -[AEGIS-128L]: https://www.ietf.org/archive/id/draft-irtf-cfrg-aegis-aead-04.html +top of SHA-256 and AES-128-CTR. ## Preliminaries @@ -36,22 +34,22 @@ This encoding ensures that operations of variable-length inputs are always unamb To generate any output during an operation, the protocol first finalizes its current SHA-256 state into a 32-byte digest. That digest is split into a key and nonce and used to initialize an -AEGIS-128L instance. That AEGIS-128L instance is then used to generate 64 byte of PRF output. The +AES-128-CTR instance. That AES-128-CTR instance is then used to generate 64 byte of PRF output. The first 32 bytes are used as a chain key to update the protocol state, the second 32 bytes are split into an output key and nonce. The first byte of the output nonce is set to the operation code. -Finally, the output key and nonce are used to initialize an AEGIS-128L instance for output +Finally, the output key and nonce are used to initialize an AES-128-CTR instance for output generation: ```text function Chain(state, operation): - K₀ǁN₀ ← SHA256::Finalize(state) // Derive a key and nonce from the current state. - state ← SHA256::Init() // Reset the state. - prf ← AEGIS_128L::New(K₀, N₀) // Initialize an AEGIS-128L instance for PRF output. - K₁ ← AEGIS_128L::PRF(prf, 32) // Generate a chain key. - state ← Process(state, K₁, 0x07) // Update the protocol with the chain key and the Chain op code. - K₂ǁN₂ ← AEGIS_128L::PRF(prf, 32) // Generate an output key and nonce. - N₂[0] ← operation // Set the first byte of the output nonce to the op code. - output ← AEGIS_128L::New(K₂, N₂) // Create an output AEGIS-128L instance with the output key and nonce. + K₀ǁN₀ ← SHA256::Finalize(state) // Derive a key and nonce from the current state. + state ← SHA256::Init() // Reset the state. + prf ← AES_128_CTR::New(K₀, N₀) // Initialize an AES-128-CTR instance for PRF output. + K₁ ← AES_128_CTR::PRF(prf, 32) // Generate a chain key. + state ← Process(state, K₁, 0x07) // Update the protocol with the chain key and the Chain op code. + K₂ǁN₂ ← AES_128_CTR::PRF(prf, 32) // Generate an output key and nonce. + N₂[0] ← operation // Set the first byte of the output nonce to the op code. + output ← AES_128_CTR::New(K₂, N₂) // Create an output AES-128-CTR instance with the output key and nonce. return state, output ``` @@ -62,7 +60,7 @@ function Chain(state, operation): `Chain` uses an [HKDF][]-style _Extract-then-Expand_ key derivation function (KDF) with the protocol's prior inputs (i.e. the [encoded](#encoding-operations) operations) as the effective keying material, SHA-256 as the strong computational extractor for the keying material and -AEGIS-128L as the expanding PRF. +AES-128-CTR as the expanding PRF. [HKDF]: https://www.rfc-editor.org/rfc/rfc5869.html @@ -83,8 +81,7 @@ chain][kdf-chain], giving Lockstitch protocols the following security properties ## Operations -Lockstitch supports six operations: `Init`, `Mix`, `Derive`, `Encrypt`/`Decrypt`, `Seal`/`Open`, and -`Ratchet`. +Lockstitch supports five operations: `Init`, `Mix`, `Derive`, `Encrypt`/`Decrypt`, and `Ratchet`. ### `Init` @@ -135,14 +132,14 @@ behavior. ```text function Derive(state, n): - (state, output) ← Chain(state, 0x03) // Ratchet the protocol state and key an output AEGIS-128L instance. - prf ← AEGIS_128L::PRF(output, n) // Generate n bytes of AEGIS-128L PRF output. + (state, output) ← Chain(state, 0x03) // Ratchet the protocol state and key an output AES-128-CTR instance. + prf ← AES_128_CTR::PRF(output, n) // Generate n bytes of AES-128-CTR PRF output. state ← Process(state, LE64(n), 0x03) // Processes the output length with the Derive op code. return (state, prf) ``` A `Derive` operation's output is indistinguishable from random by an adversary who does not know the -protocol's state prior to the operation provided SHA-256 is collision-resistant and AEGIS-128L is +protocol's state prior to the operation provided SHA-256 is collision-resistant and AES-128-CTR is PRF secure. The protocol's state after the operation is dependent on both the fact that the operation was a `Derive` operation as well as the number of bytes produced. @@ -153,15 +150,14 @@ operation is complete, however, the protocols' states will be different. If a us ### `Encrypt`/`Decrypt` -`Encrypt` uses AEGIS-128L to encrypt a given plaintext with a key derived from the protocol's -current state and updates the protocol's state with the final 256-bit AEGIS-128L tag. +`Encrypt` uses AES-128_CTR to encrypt a given plaintext with a key derived from the protocol's +current state and updates the protocol's state with the ciphertext. ```text function Encrypt(state, plaintext): - (state, output) ← Chain(state, 0x04) // Ratchet the protocol state and key an output AEGIS-128L instance. - ciphertext ← AEGIS_128L::Encrypt(output, plaintext) // Encrypt the plaintext with AEGIS-128L. - (s_tag, l_tag) ← AEGIS_128L::Finalize(output) // Calculate the short and long AEGIS-128L tags. - state ← Process(state, l_tag, 0x04) // Process the long tag as input. + (state, output) ← Chain(state, 0x04) // Ratchet the protocol state and key an output AES-128-CTR instance. + state ← Process(state, plaintext, 0x04) // Process the ciphertext as input. + ciphertext ← AES_128_CTR::Encrypt(output, plaintext) // Encrypt the plaintext with AES-128-CTR. return (state, ciphertext) ``` @@ -169,34 +165,24 @@ function Encrypt(state, plaintext): ```text function Decrypt(state, ciphertext): - (state, output) ← Chain(state, 0x04) // Ratchet the protocol state and key an output AEGIS-128L instance. - plaintext ← AEGIS_128L::Decrypt(output, ciphertext) // Decrypt the plaintext with AEGIS-128L. - (s_tag, l_tag) ← AEGIS_128L::Finalize(output) // Calculate the short and long AEGIS-128L tags. - state ← Process(state, l_tag, 0x04) // Process the long tag as input. + (state, output) ← Chain(state, 0x04) // Ratchet the protocol state and key an output AES-128-CTR instance. + plaintext ← AES_128_CTR::Decrypt(output, ciphertext) // Decrypt the plaintext with AES-128-CTR. + state ← Process(state, plaintext, 0x04) // Process the ciphertext as input. return (state, plaintext) ``` -Three points bear mentioning about `Encrypt` and `Decrypt`. +Two points bear mentioning about `Encrypt` and `Decrypt`. First, both `Encrypt` and `Decrypt` use the same `Crypt` operation code to ensure protocols have the same state after both encrypting and decrypting data. -Second, despite not updating the protocol state with either the plaintext or ciphertext, the -inclusion of the long tag ensures the protocol's state is dependent on both because AEGIS-128L is -key committing (i.e. the probability of an attacker finding a different key, nonce, or plaintext -which produces the same authentication tag is negligible). - -**N.B.:** AEGIS-128L by itself is not fully committing, as [tag collisions can be found if -authenticated data is attacker-controlled](https://eprint.iacr.org/2023/1495.pdf). Lockstitch does -not pass authenticated data to AEGIS-128L, however, mooting this type of attack. - -Third, `Crypt` operations provide no authentication by themselves. An attacker can modify a +Second, `Crypt` operations provide no authentication by themselves. An attacker can modify a ciphertext and the `Decrypt` operation will return a plaintext which was never encrypted. Alone, they are EAV secure (i.e. a passive adversary will not be able to read plaintext without knowing the protocol's prior state) but not IND-CPA secure (i.e. an active adversary with an encryption oracle will be able to detect duplicate plaintexts) or IND-CCA secure (i.e. an active adversary can produce modified ciphertexts which successfully decrypt). For IND-CPA and IND-CCA security, -use [`Seal`/`Open`](#sealopen). +use an [AEAD construction](#authenticated-encryption-and-data-aead). As with `Derive`, `Encrypt`'s streaming support means an `Encrypt` operation with a shorter plaintext produces a keystream which is a prefix of one with a longer plaintext (e.g. @@ -205,44 +191,6 @@ bytes). Once the operation is complete, however, the protocols' states would be case requires ciphertexts to be dependent on their length, include the length in a `Mix` operation beforehand. -### `Seal`/`Open` - -The `Seal` operation uses AEGIS-128L to encrypt a given plaintext with a key derived from the -protocol's current state, updates the protocol's state with the final AEGIS-128L tag, and returns -the ciphertext along with the tag: - -```text -function Seal(state, plaintext): - (state, output) ← Chain(state, 0x05) // Ratchet the protocol state and key an output AEGIS-128L instance. - ciphertext ← AEGIS_128L::Encrypt(output, plaintext) // Encrypt the plaintext with AEGIS-128L. - (s_tag, l_tag) ← AEGIS_128L::Finalize(output) // Calculate the short and long AEGIS-128L tags. - state ← Process(state, l_tag, 0x05) // Process the long tag as input. - return (state, ciphertext, s_tag) // Return the ciphertext and the short tag. -``` - -This is essentially the same thing as the `Encrypt` operation but includes the short AEGIS-128L tag -in the ciphertext. Because the long tag is used to update the protocol's state (instead of just the -short tag), an attacker in possession of the protocol's initial state but not the plaintext will be -unable to compute the protocol's final state, as the long tag includes information which the short -tag does not. - -The `Open` operation decrypts the ciphertext and compares the counterfactual tag against the tag -included with the ciphertext: - -```text -function Open(state, ciphertext, tag): - (state, output) ← Chain(state, 0x05) // Ratchet the protocol state and key an output AEGIS-128L instance. - plaintext ← AEGIS_128L::Decrypt(output, ciphertext) // Decrypt the plaintext with AEGIS-128L. - (s_tag′, l_tag) ← AEGIS_128L::Finalize(output) // Calculate the counterfactual short and long AEGIS-128L tags. - state ← Process(state, l_tag, 0x05) // Process the long tag as input. - if tag ≠ s_tag′: // If the short tags are equal, the plaintext is authentic. - return ⟂ - else: - return (state, plaintext) -``` - -The resulting construction is CCA secure if AEGIS-128L is CCA secure. - ### `Ratchet` The `Ratchet` operation irreversibly modifies the protocol's state, preventing rollback: @@ -310,20 +258,21 @@ function Seal(key, nonce, ad, plaintext): state ← Mix(state, key) // Mix the key into the protocol. state ← Mix(state, nonce) // Mix the nonce into the protocol. state ← Mix(state, ad) // Mix the associated data into the protocol. - (state, ciphertext, tag) ← Seal(state, plaintext) // Seal the plaintext. + (state, ciphertext) ← Encrypt(state, plaintext) // Encrypt the plaintext. + tag ← Derive(state, 16) // Derive the tag. return ciphertext, tag // Return the ciphertext and tag. ``` The introduction of a nonce makes the scheme probabilistic (which is required for IND-CCA security). -The final `Seal` operation closes over all inputs--key, nonce, associated data, and plaintext--which -are also the values used to produce the ciphertext. Forging a tag here would imply that AEGIS-128L's -MAC construction is not sUF-CMA secure. +The final `Derive` operation closes over all inputs--key, nonce, associated data, and +plaintext--which are also the values used to produce the ciphertext. Forging a tag here would imply +that AES-128-CTR's MAC construction is not sUF-CMA secure. In addition, this construction is fully committing: finding a ciphertext and tag pair which -successfully decrypts under multiple keys would imply that AEGIS-128L is not key committing, and the -final tag serves as a commitment for the ciphertext. +successfully decrypts under multiple keys would imply that AES-128-CTR is not key committing, and +the final tag serves as a commitment for the ciphertext. -Decryption uses the `Open` operation to decrypt: +Decryption uses the `Decrypt` operation to decrypt: ```text function Open(key, nonce, ad, ciphertext, tag): @@ -331,12 +280,17 @@ function Open(key, nonce, ad, ciphertext, tag): state ← Mix(state, key) // Mix the key into the protocol. state ← Mix(state, nonce) // Mix the nonce into the protocol. state ← Mix(state, ad) // Mix the associated data into the protocol. - (state, plaintext) ← Open(state, ciphertext, tag) // Open the ciphertext. - return plaintext // Return the authenticated plaintext or ⟂. + (state, plaintext) ← Decrypt(state, ciphertext) // Open the ciphertext. + tag′ ← Derive(state, 16) // Derive the counterfactual tag. + if tag = tag′: + return plaintext // If both tags are equal, return the plaintext. + else: + return ⊥ // Otherwise, return an error. ``` Unlike a standard AEAD, this can be easily extended to allow for multiple, independent pieces of -associated data. +associated data. Also unlike many standard AEADs (e.g. AES-GCM and ChaCha20Poly1305), it is fully +context-committing. ## Complex Protocols @@ -356,7 +310,8 @@ function HPKE_Encrypt(receiver.pub, plaintext): state ← Mix(state, receiver.pub) // Mix the receiver's public key into the protocol. state ← Mix(state, ephemeral.pub) // Mix the ephemeral public key into the protocol. state ← Mix(state, ECDH(receiver.pub, ephemeral.priv)) // Mix the ephemeral ECDH shared secret into the protocol. - (state, ciphertext) ← Seal(state, plaintext) // Seal the plaintext. + (state, ciphertext) ← Encrypt(state, plaintext) // Encrypt the plaintext. + tag ← Derive(state, 16) // Derive a tag. return (ephemeral.pub, ciphertext) // Return the ephemeral public key and tag. ``` @@ -366,8 +321,12 @@ function HPKE_Decrypt(receiver, ephemeral.pub, ciphertext, tag): state ← Mix(state, receiver.pub) // Mix the receiver's public key into the protocol. state ← Mix(state, ephemeral.pub) // Mix the ephemeral public key into the protocol. state ← Mix(state, ECDH(ephemeral.pub, receiver.priv)) // Mix the ephemeral ECDH shared secret into the protocol. - (state, plaintext) ← Open(state, ciphertext) // Open the plaintext. - return plaintext // Return the authenticated plaintext or ⟂. + (state, plaintext) ← Decrypt(state, ciphertext) // Decrypt the plaintext. + tag′ ← Derive(state, 16) // Derive a counterfactual tag. + if tag = tag′: + return plaintext // If both tags are equal, return the plaintext. + else: + return ⊥ // Otherwise, return an error. ``` **N.B.:** This construction does not provide authentication in the public key setting. An adversary diff --git a/perf.md b/perf.md index 633d0971..98574ea8 100644 --- a/perf.md +++ b/perf.md @@ -1,101 +1,101 @@ # Performance -## `x86_64` (GCP `n2-standard-4`, Intel Ice Lake, `+aes,+ssse3`) +## `x86_64` (GCP `n2-standard-4`, Intel Ice Lake, rustc 1.73) ```text benchmarks fastest │ slowest │ median │ mean │ samples │ iters ├─ aead │ │ │ │ │ -│ ├─ 16 296.6 ns │ 61.82 µs │ 305 ns │ 307.9 ns │ 2463812 │ 2463812 -│ │ 51.44 MiB/s │ 252.7 KiB/s │ 50.01 MiB/s │ 49.54 MiB/s │ │ -│ ├─ 256 304.6 ns │ 24.57 µs │ 310.4 ns │ 312.9 ns │ 691533 │ 2766132 -│ │ 801.2 MiB/s │ 9.935 MiB/s │ 786.3 MiB/s │ 780 MiB/s │ │ -│ ├─ 1024 373.1 ns │ 33.54 µs │ 381.2 ns │ 383.9 ns │ 1054533 │ 2109066 -│ │ 2.555 GiB/s │ 29.11 MiB/s │ 2.501 GiB/s │ 2.483 GiB/s │ │ -│ ├─ 16384 1.707 µs │ 63.31 µs │ 1.718 µs │ 1.727 µs │ 519217 │ 519217 -│ │ 8.936 GiB/s │ 246.7 MiB/s │ 8.88 GiB/s │ 8.832 GiB/s │ │ -│ ╰─ 1048576 91.01 µs │ 321.1 µs │ 91.17 µs │ 91.9 µs │ 9146 │ 9146 -│ 10.72 GiB/s │ 3.04 GiB/s │ 10.71 GiB/s │ 10.62 GiB/s │ │ +│ ├─ 16 584.3 ns │ 77.57 µs │ 606.6 ns │ 613.9 ns │ 1376738 │ 1376738 +│ │ 26.11 MiB/s │ 201.4 KiB/s │ 25.15 MiB/s │ 24.85 MiB/s │ │ +│ ├─ 256 751.2 ns │ 73.55 µs │ 771.2 ns │ 781.2 ns │ 1087568 │ 1087568 +│ │ 324.9 MiB/s │ 3.319 MiB/s │ 316.5 MiB/s │ 312.4 MiB/s │ │ +│ ├─ 1024 1.414 µs │ 42.97 µs │ 1.462 µs │ 1.471 µs │ 612739 │ 612739 +│ │ 690.4 MiB/s │ 22.72 MiB/s │ 667.9 MiB/s │ 663.5 MiB/s │ │ +│ ├─ 16384 14.94 µs │ 58.07 µs │ 15.16 µs │ 15.23 µs │ 64710 │ 64710 +│ │ 1.021 GiB/s │ 269 MiB/s │ 1.006 GiB/s │ 1.001 GiB/s │ │ +│ ╰─ 1048576 926.8 µs │ 1.7 ms │ 950.7 µs │ 961.7 µs │ 1022 │ 1022 +│ 1.053 GiB/s │ 588.1 MiB/s │ 1.027 GiB/s │ 1.015 GiB/s │ │ ├─ hash │ │ │ │ │ -│ ├─ 16 142.3 ns │ 1.645 µs │ 144.6 ns │ 145.3 ns │ 370487 │ 5927792 -│ │ 107.1 MiB/s │ 9.272 MiB/s │ 105.4 MiB/s │ 105 MiB/s │ │ -│ ├─ 256 340 ns │ 14.05 µs │ 347.3 ns │ 349.6 ns │ 611243 │ 2444972 -│ │ 717.9 MiB/s │ 17.36 MiB/s │ 702.8 MiB/s │ 698.3 MiB/s │ │ -│ ├─ 1024 923.5 ns │ 71.3 µs │ 945 ns │ 950.8 ns │ 916094 │ 916094 -│ │ 1.032 GiB/s │ 13.69 MiB/s │ 1.009 GiB/s │ 1.002 GiB/s │ │ -│ ├─ 16384 12.44 µs │ 59.22 µs │ 12.67 µs │ 12.72 µs │ 77388 │ 77388 -│ │ 1.226 GiB/s │ 263.8 MiB/s │ 1.203 GiB/s │ 1.199 GiB/s │ │ -│ ╰─ 1048576 790.7 µs │ 991.8 µs │ 803.3 µs │ 804.5 µs │ 1217 │ 1217 -│ 1.234 GiB/s │ 1008 MiB/s │ 1.215 GiB/s │ 1.213 GiB/s │ │ +│ ├─ 16 249.3 ns │ 11.61 µs │ 259.5 ns │ 261.3 ns │ 814673 │ 3258692 +│ │ 61.2 MiB/s │ 1.313 MiB/s │ 58.8 MiB/s │ 58.37 MiB/s │ │ +│ ├─ 256 459.6 ns │ 35.77 µs │ 469.6 ns │ 473.6 ns │ 1618517 │ 1618517 +│ │ 531 MiB/s │ 6.824 MiB/s │ 519.7 MiB/s │ 515.4 MiB/s │ │ +│ ├─ 1024 1.015 µs │ 65.07 µs │ 1.045 µs │ 1.053 µs │ 835964 │ 835964 +│ │ 962 MiB/s │ 15 MiB/s │ 934.4 MiB/s │ 927.2 MiB/s │ │ +│ ├─ 16384 12.6 µs │ 82.78 µs │ 12.77 µs │ 12.86 µs │ 76537 │ 76537 +│ │ 1.21 GiB/s │ 188.7 MiB/s │ 1.193 GiB/s │ 1.186 GiB/s │ │ +│ ╰─ 1048576 800.3 µs │ 1.52 ms │ 809 µs │ 812.7 µs │ 1205 │ 1205 +│ 1.22 GiB/s │ 657.6 MiB/s │ 1.207 GiB/s │ 1.201 GiB/s │ │ ├─ prf │ │ │ │ │ -│ ├─ 16 139.1 ns │ 18.52 µs │ 142.1 ns │ 143.7 ns │ 1423054 │ 5692216 -│ │ 109.6 MiB/s │ 843.4 KiB/s │ 107.3 MiB/s │ 106.1 MiB/s │ │ -│ ├─ 256 159.6 ns │ 17.52 µs │ 163.7 ns │ 165.7 ns │ 1175881 │ 4703524 -│ │ 1.493 GiB/s │ 13.93 MiB/s │ 1.456 GiB/s │ 1.438 GiB/s │ │ -│ ├─ 1024 211.8 ns │ 7.144 µs │ 217.7 ns │ 219.5 ns │ 896064 │ 3584256 -│ │ 4.502 GiB/s │ 136.6 MiB/s │ 4.379 GiB/s │ 4.342 GiB/s │ │ -│ ├─ 16384 1.259 µs │ 62.59 µs │ 1.288 µs │ 1.308 µs │ 669538 │ 669538 -│ │ 12.11 GiB/s │ 249.6 MiB/s │ 11.84 GiB/s │ 11.66 GiB/s │ │ -│ ╰─ 1048576 71.59 µs │ 125.4 µs │ 71.85 µs │ 72.64 µs │ 11123 │ 11123 -│ 13.63 GiB/s │ 7.786 GiB/s │ 13.58 GiB/s │ 13.44 GiB/s │ │ +│ ├─ 16 235.6 ns │ 15.55 µs │ 244.1 ns │ 246.6 ns │ 861216 │ 3444864 +│ │ 64.75 MiB/s │ 1004 KiB/s │ 62.5 MiB/s │ 61.86 MiB/s │ │ +│ ├─ 256 266.8 ns │ 9.067 µs │ 278.7 ns │ 281.2 ns │ 726197 │ 2904788 +│ │ 915 MiB/s │ 26.92 MiB/s │ 875.8 MiB/s │ 868 MiB/s │ │ +│ ├─ 1024 375.8 ns │ 29.67 µs │ 388.1 ns │ 394 ns │ 1008828 │ 2017656 +│ │ 2.537 GiB/s │ 32.91 MiB/s │ 2.456 GiB/s │ 2.42 GiB/s │ │ +│ ├─ 16384 2.402 µs │ 58.98 µs │ 2.493 µs │ 2.514 µs │ 368430 │ 368430 +│ │ 6.35 GiB/s │ 264.8 MiB/s │ 6.119 GiB/s │ 6.068 GiB/s │ │ +│ ╰─ 1048576 150.3 µs │ 228 µs │ 154.2 µs │ 154.6 µs │ 5830 │ 5830 +│ 6.494 GiB/s │ 4.281 GiB/s │ 6.329 GiB/s │ 6.313 GiB/s │ │ ╰─ stream │ │ │ │ │ - ├─ 16 280.1 ns │ 8.218 µs │ 284.6 ns │ 286.6 ns │ 405049 │ 3240392 - │ 54.46 MiB/s │ 1.856 MiB/s │ 53.59 MiB/s │ 53.23 MiB/s │ │ - ├─ 256 300.9 ns │ 9.895 µs │ 305.4 ns │ 307.3 ns │ 366513 │ 2932104 - │ 811.2 MiB/s │ 24.67 MiB/s │ 799.2 MiB/s │ 794.3 MiB/s │ │ - ├─ 1024 369.3 ns │ 29.33 µs │ 380.8 ns │ 383.3 ns │ 1068199 │ 2136398 - │ 2.582 GiB/s │ 33.28 MiB/s │ 2.504 GiB/s │ 2.487 GiB/s │ │ - ├─ 16384 1.704 µs │ 22.73 µs │ 1.721 µs │ 1.729 µs │ 522165 │ 522165 - │ 8.953 GiB/s │ 687.1 MiB/s │ 8.861 GiB/s │ 8.821 GiB/s │ │ - ╰─ 1048576 91.02 µs │ 165 µs │ 91.18 µs │ 91.68 µs │ 9168 │ 9168 - 10.72 GiB/s │ 5.916 GiB/s │ 10.7 GiB/s │ 10.65 GiB/s │ │ + ├─ 16 289.6 ns │ 9.689 µs │ 299.1 ns │ 301.2 ns │ 375176 │ 3001408 + │ 52.67 MiB/s │ 1.574 MiB/s │ 51.01 MiB/s │ 50.65 MiB/s │ │ + ├─ 256 515.4 ns │ 23.56 µs │ 529.3 ns │ 533.4 ns │ 799196 │ 1598392 + │ 473.6 MiB/s │ 10.36 MiB/s │ 461.2 MiB/s │ 457.6 MiB/s │ │ + ├─ 1024 1.183 µs │ 84.15 µs │ 1.222 µs │ 1.232 µs │ 724322 │ 724322 + │ 825.1 MiB/s │ 11.6 MiB/s │ 799.1 MiB/s │ 792.3 MiB/s │ │ + ├─ 16384 14.73 µs │ 80.55 µs │ 14.95 µs │ 15.04 µs │ 65594 │ 65594 + │ 1.035 GiB/s │ 193.9 MiB/s │ 1.02 GiB/s │ 1.014 GiB/s │ │ + ╰─ 1048576 926.8 µs │ 1.361 ms │ 947.9 µs │ 955.3 µs │ 1029 │ 1029 + 1.053 GiB/s │ 734.6 MiB/s │ 1.03 GiB/s │ 1.022 GiB/s │ │ ``` -## `aarch64` (Apple M2 Air 2022) +## `aarch64` (Apple M2 Air 2022, rustc 1.73, `RUSTFLAGS="--cfg aes_armv8"`) ```text benchmarks fastest │ slowest │ median │ mean │ samples │ iters ├─ aead │ │ │ │ │ -│ ├─ 16 82.88 ns │ 45.16 µs │ 166.2 ns │ 167.6 ns │ 4751269 │ 4751269 -│ │ 184.1 MiB/s │ 345.9 KiB/s │ 91.8 MiB/s │ 91.03 MiB/s │ │ -│ ├─ 256 174 ns │ 1.762 µs │ 177.9 ns │ 179.8 ns │ 139981 │ 4479392 -│ │ 1.369 GiB/s │ 138.5 MiB/s │ 1.339 GiB/s │ 1.325 GiB/s │ │ -│ ├─ 1024 222.2 ns │ 1.9 µs │ 226.1 ns │ 230.9 ns │ 109019 │ 3488608 -│ │ 4.291 GiB/s │ 513.8 MiB/s │ 4.217 GiB/s │ 4.129 GiB/s │ │ -│ ├─ 16384 1.145 µs │ 15.45 µs │ 1.197 µs │ 1.212 µs │ 176079 │ 704316 -│ │ 13.32 GiB/s │ 1010 MiB/s │ 12.74 GiB/s │ 12.58 GiB/s │ │ -│ ╰─ 1048576 64.2 µs │ 115.6 µs │ 66.37 µs │ 66.65 µs │ 13701 │ 13701 -│ 15.2 GiB/s │ 8.442 GiB/s │ 14.71 GiB/s │ 14.65 GiB/s │ │ +│ ├─ 16 478.8 ns │ 7.515 µs │ 499.6 ns │ 505.6 ns │ 226181 │ 1809448 +│ │ 31.86 MiB/s │ 2.03 MiB/s │ 30.53 MiB/s │ 30.17 MiB/s │ │ +│ ├─ 256 577.7 ns │ 7.609 µs │ 598.5 ns │ 600.6 ns │ 192982 │ 1543856 +│ │ 422.5 MiB/s │ 32.08 MiB/s │ 407.8 MiB/s │ 406.4 MiB/s │ │ +│ ├─ 1024 937.1 ns │ 10.13 µs │ 978.8 ns │ 987.2 ns │ 240384 │ 961536 +│ │ 1.017 GiB/s │ 96.35 MiB/s │ 997.7 MiB/s │ 989.1 MiB/s │ │ +│ ├─ 16384 8.499 µs │ 49.79 µs │ 8.582 µs │ 8.66 µs │ 112799 │ 112799 +│ │ 1.795 GiB/s │ 313.8 MiB/s │ 1.777 GiB/s │ 1.761 GiB/s │ │ +│ ╰─ 1048576 520.7 µs │ 607.4 µs │ 521.2 µs │ 525.4 µs │ 1881 │ 1881 +│ 1.875 GiB/s │ 1.607 GiB/s │ 1.873 GiB/s │ 1.858 GiB/s │ │ ├─ hash │ │ │ │ │ -│ ├─ 16 90.04 ns │ 980 ns │ 91.99 ns │ 93.43 ns │ 114162 │ 7306368 -│ │ 169.4 MiB/s │ 15.56 MiB/s │ 165.8 MiB/s │ 163.3 MiB/s │ │ -│ ├─ 256 192.2 ns │ 1.986 µs │ 194.8 ns │ 196.6 ns │ 130743 │ 4183776 -│ │ 1.24 GiB/s │ 122.8 MiB/s │ 1.223 GiB/s │ 1.212 GiB/s │ │ -│ ├─ 1024 504.7 ns │ 6.817 µs │ 515.1 ns │ 521.2 ns │ 218928 │ 1751424 -│ │ 1.889 GiB/s │ 143.2 MiB/s │ 1.851 GiB/s │ 1.829 GiB/s │ │ -│ ├─ 16384 6.832 µs │ 60.16 µs │ 6.916 µs │ 6.958 µs │ 139761 │ 139761 -│ │ 2.233 GiB/s │ 259.6 MiB/s │ 2.206 GiB/s │ 2.192 GiB/s │ │ -│ ╰─ 1048576 434.9 µs │ 544.5 µs │ 434.9 µs │ 437.9 µs │ 2253 │ 2253 -│ 2.245 GiB/s │ 1.793 GiB/s │ 2.244 GiB/s │ 2.229 GiB/s │ │ +│ ├─ 16 224.8 ns │ 1.296 µs │ 234 ns │ 234.5 ns │ 112340 │ 3594880 +│ │ 67.84 MiB/s │ 11.76 MiB/s │ 65.2 MiB/s │ 65.04 MiB/s │ │ +│ ├─ 256 322.5 ns │ 3.4 µs │ 335.5 ns │ 336.7 ns │ 164077 │ 2625232 +│ │ 756.8 MiB/s │ 71.79 MiB/s │ 727.5 MiB/s │ 725 MiB/s │ │ +│ ├─ 1024 635 ns │ 5.442 µs │ 655.8 ns │ 659.3 ns │ 176315 │ 1410520 +│ │ 1.501 GiB/s │ 179.4 MiB/s │ 1.454 GiB/s │ 1.446 GiB/s │ │ +│ ├─ 16384 6.916 µs │ 47.16 µs │ 7.041 µs │ 7.061 µs │ 137798 │ 137798 +│ │ 2.206 GiB/s │ 331.2 MiB/s │ 2.167 GiB/s │ 2.16 GiB/s │ │ +│ ╰─ 1048576 435 µs │ 559.1 µs │ 435.1 µs │ 437.2 µs │ 2256 │ 2256 +│ 2.244 GiB/s │ 1.746 GiB/s │ 2.244 GiB/s │ 2.233 GiB/s │ │ ├─ prf │ │ │ │ │ -│ ├─ 16 85.48 ns │ 593.3 ns │ 87.44 ns │ 87.89 ns │ 119531 │ 7649984 -│ │ 178.4 MiB/s │ 25.71 MiB/s │ 174.5 MiB/s │ 173.6 MiB/s │ │ -│ ├─ 256 103.7 ns │ 1.812 µs │ 107.6 ns │ 108.1 ns │ 207343 │ 6634976 -│ │ 2.298 GiB/s │ 134.7 MiB/s │ 2.215 GiB/s │ 2.205 GiB/s │ │ -│ ├─ 1024 149.2 ns │ 2.071 µs │ 153.1 ns │ 154.1 ns │ 153444 │ 4910208 -│ │ 6.388 GiB/s │ 471.5 MiB/s │ 6.225 GiB/s │ 6.188 GiB/s │ │ -│ ├─ 16384 1.03 µs │ 11.64 µs │ 1.103 µs │ 1.11 µs │ 178333 │ 713332 -│ │ 14.8 GiB/s │ 1.31 GiB/s │ 13.82 GiB/s │ 13.74 GiB/s │ │ -│ ╰─ 1048576 61.79 µs │ 118.2 µs │ 63.91 µs │ 68.61 µs │ 12706 │ 12706 -│ 15.8 GiB/s │ 8.261 GiB/s │ 15.27 GiB/s │ 14.23 GiB/s │ │ +│ ├─ 16 205.3 ns │ 2.918 µs │ 220.9 ns │ 223.4 ns │ 234007 │ 3744112 +│ │ 74.3 MiB/s │ 5.227 MiB/s │ 69.04 MiB/s │ 68.28 MiB/s │ │ +│ ├─ 256 228.8 ns │ 3.411 µs │ 247 ns │ 247.3 ns │ 214431 │ 3430896 +│ │ 1.042 GiB/s │ 71.57 MiB/s │ 988.2 MiB/s │ 986.9 MiB/s │ │ +│ ├─ 1024 296.5 ns │ 2.791 µs │ 312.1 ns │ 313.5 ns │ 172170 │ 2754720 +│ │ 3.216 GiB/s │ 349.8 MiB/s │ 3.055 GiB/s │ 3.041 GiB/s │ │ +│ ├─ 16384 1.645 µs │ 19.45 µs │ 1.707 µs │ 1.728 µs │ 259413 │ 518826 +│ │ 9.273 GiB/s │ 803 MiB/s │ 8.933 GiB/s │ 8.829 GiB/s │ │ +│ ╰─ 1048576 91.58 µs │ 139.3 µs │ 94.79 µs │ 94.91 µs │ 9883 │ 9883 +│ 10.66 GiB/s │ 7.008 GiB/s │ 10.3 GiB/s │ 10.28 GiB/s │ │ ╰─ stream │ │ │ │ │ - ├─ 16 162.3 ns │ 2.121 µs │ 166.2 ns │ 167 ns │ 148676 │ 4757632 - │ 94.01 MiB/s │ 7.19 MiB/s │ 91.8 MiB/s │ 91.35 MiB/s │ │ - ├─ 256 176.6 ns │ 1.882 µs │ 180.5 ns │ 181.7 ns │ 138919 │ 4445408 - │ 1.349 GiB/s │ 129.6 MiB/s │ 1.32 GiB/s │ 1.311 GiB/s │ │ - ├─ 1024 220.9 ns │ 4.272 µs │ 233.9 ns │ 234.5 ns │ 218938 │ 3503008 - │ 4.317 GiB/s │ 228.5 MiB/s │ 4.076 GiB/s │ 4.066 GiB/s │ │ - ├─ 16384 1.134 µs │ 14.73 µs │ 1.197 µs │ 1.196 µs │ 178969 │ 715876 - │ 13.44 GiB/s │ 1.035 GiB/s │ 12.74 GiB/s │ 12.74 GiB/s │ │ - ╰─ 1048576 63.62 µs │ 116.8 µs │ 65.54 µs │ 65.87 µs │ 13891 │ 13891 - 15.34 GiB/s │ 8.355 GiB/s │ 14.89 GiB/s │ 14.82 GiB/s │ │ + ├─ 16 241.8 ns │ 3.004 µs │ 252.2 ns │ 253.5 ns │ 210147 │ 3362352 + │ 63.09 MiB/s │ 5.078 MiB/s │ 60.49 MiB/s │ 60.18 MiB/s │ │ + ├─ 256 343.3 ns │ 7.301 µs │ 364.2 ns │ 365 ns │ 301128 │ 2409024 + │ 710.9 MiB/s │ 33.43 MiB/s │ 670.3 MiB/s │ 668.7 MiB/s │ │ + ├─ 1024 707.9 ns │ 9.937 µs │ 739.2 ns │ 748.2 ns │ 312592 │ 1250368 + │ 1.347 GiB/s │ 98.27 MiB/s │ 1.29 GiB/s │ 1.274 GiB/s │ │ + ├─ 16384 8.249 µs │ 42.99 µs │ 8.374 µs │ 8.418 µs │ 116039 │ 116039 + │ 1.849 GiB/s │ 363.3 MiB/s │ 1.822 GiB/s │ 1.812 GiB/s │ │ + ╰─ 1048576 520.4 µs │ 650.5 µs │ 520.5 µs │ 528 µs │ 1872 │ 1872 + 1.876 GiB/s │ 1.501 GiB/s │ 1.875 GiB/s │ 1.849 GiB/s │ │ ``` diff --git a/src/aegis_128l.rs b/src/aegis_128l.rs deleted file mode 100644 index c9aef2d9..00000000 --- a/src/aegis_128l.rs +++ /dev/null @@ -1,600 +0,0 @@ -#[cfg(all(target_arch = "aarch64", not(feature = "portable")))] -use self::aarch64::*; - -#[cfg(feature = "portable")] -use self::portable::*; - -#[cfg(all(any(target_arch = "x86_64", target_arch = "x86"), not(feature = "portable")))] -use self::x86_64::*; - -#[cfg(all(target_arch = "aarch64", not(feature = "portable")))] -mod aarch64; - -#[cfg(feature = "portable")] -mod portable; - -#[cfg(all(any(target_arch = "x86_64", target_arch = "x86"), not(feature = "portable")))] -mod x86_64; - -/// The length of an AEGIS-128L block. -pub const BLOCK_LEN: usize = 32; - -/// The length of an AES block. -pub const AES_BLOCK_LEN: usize = 16; - -#[derive(Debug, Clone)] -pub struct Aegis128L { - blocks: [AesBlock; 8], - ad_len: u64, - mc_len: u64, -} - -impl Aegis128L { - pub fn new(key: &[u8; AES_BLOCK_LEN], nonce: &[u8; AES_BLOCK_LEN]) -> Self { - // Initialize constants. - let c0 = load(&[ - 0x00, 0x01, 0x01, 0x02, 0x03, 0x05, 0x08, 0x0d, 0x15, 0x22, 0x37, 0x59, 0x90, 0xe9, - 0x79, 0x62, - ]); - let c1 = load(&[ - 0xdb, 0x3d, 0x18, 0x55, 0x6d, 0xc2, 0x2f, 0xf1, 0x20, 0x11, 0x31, 0x42, 0x73, 0xb5, - 0x28, 0xdd, - ]); - - // Initialize key and nonce blocks. - let key = load(key); - let nonce = load(nonce); - - // Initialize cipher state. - let mut state = Aegis128L { - blocks: [ - xor(key, nonce), - c1, - c0, - c1, - xor(key, nonce), - xor(key, c0), - xor(key, c1), - xor(key, c0), - ], - ad_len: 0, - mc_len: 0, - }; - - // Update the state with the nonce and key 10 times. - for _ in 0..10 { - state.update(nonce, key); - } - - state - } - - #[cfg(test)] - pub fn ad(&mut self, ad: &[u8]) { - let mut xi = [0u8; BLOCK_LEN]; - - // Process whole blocks of associated data. - let mut chunks = ad.chunks_exact(BLOCK_LEN); - for chunk in chunks.by_ref() { - xi.copy_from_slice(chunk); - self.absorb(&xi); - } - - // Process the remainder of the associated data, if any. - let chunk = chunks.remainder(); - if !chunk.is_empty() { - xi.fill(0); - xi[..chunk.len()].copy_from_slice(chunk); - self.absorb(&xi); - } - - self.ad_len += ad.len() as u64; - } - - pub fn prf(&mut self, out: &mut [u8]) { - let mut ci = [0u8; BLOCK_LEN]; - - // Process whole blocks of output. - let mut chunks = out.chunks_exact_mut(BLOCK_LEN); - for chunk in chunks.by_ref() { - self.enc_zeroes(&mut ci); - chunk.copy_from_slice(&ci); - } - - // Process the remainder of the output, if any. - let chunk = chunks.into_remainder(); - if !chunk.is_empty() { - self.enc_zeroes(&mut ci); - chunk.copy_from_slice(&ci[..chunk.len()]); - } - - self.mc_len += out.len() as u64; - } - - pub fn encrypt(&mut self, in_out: &mut [u8]) { - let mut xi = [0u8; BLOCK_LEN]; - let mut ci = [0u8; BLOCK_LEN]; - - // Process whole blocks of plaintext. - let mut chunks = in_out.chunks_exact_mut(BLOCK_LEN); - for chunk in chunks.by_ref() { - xi.copy_from_slice(chunk); - self.enc(&mut ci, &xi); - chunk.copy_from_slice(&ci); - } - - // Process the remainder of the plaintext, if any. - let chunk = chunks.into_remainder(); - if !chunk.is_empty() { - xi.fill(0); - xi[..chunk.len()].copy_from_slice(chunk); - self.enc(&mut ci, &xi); - chunk.copy_from_slice(&ci[..chunk.len()]); - } - - self.mc_len += in_out.len() as u64; - } - - pub fn decrypt(&mut self, in_out: &mut [u8]) { - let mut ci = [0u8; BLOCK_LEN]; - let mut xi = [0u8; BLOCK_LEN]; - - // Process whole blocks of ciphertext. - let mut chunks = in_out.chunks_exact_mut(BLOCK_LEN); - for chunk in chunks.by_ref() { - ci.copy_from_slice(chunk); - self.dec(&mut xi, &ci); - chunk.copy_from_slice(&xi); - } - - // Process the remainder of the ciphertext, if any. - let cn = chunks.into_remainder(); - if !cn.is_empty() { - self.dec_partial(&mut xi, cn); - cn.copy_from_slice(&xi[..cn.len()]); - } - - self.mc_len += in_out.len() as u64; - } - - #[cfg(test)] - fn absorb(&mut self, ai: &[u8; BLOCK_LEN]) { - // Load the input blocks. - let (ai0, xi1) = load_2x(ai); - - // Update the cipher state with the two blocks. - self.update(ai0, xi1); - } - - fn enc_zeroes(&mut self, ci: &mut [u8; BLOCK_LEN]) { - // Generate two blocks of keystream. - let z0 = xor3(self.blocks[6], self.blocks[1], and(self.blocks[2], self.blocks[3])); - let z1 = xor3(self.blocks[2], self.blocks[5], and(self.blocks[6], self.blocks[7])); - - // Store the keystream blocks in the output. - store_2x(ci, z0, z1); - - // Update the cipher state as if two all-zero blocks were encrypted. - let xi = zero(); - self.update(xi, xi); - } - - fn enc(&mut self, ci: &mut [u8; BLOCK_LEN], xi: &[u8; BLOCK_LEN]) { - // Generate two blocks of keystream. - let z0 = xor3(self.blocks[6], self.blocks[1], and(self.blocks[2], self.blocks[3])); - let z1 = xor3(self.blocks[2], self.blocks[5], and(self.blocks[6], self.blocks[7])); - - // Load the plaintext blocks. - let (xi0, xi1) = load_2x(xi); - - // XOR the plaintext blocks with the keystream to produce ciphertext blocks. - let ci0 = xor(xi0, z0); - let ci1 = xor(xi1, z1); - - // Store ciphertext blocks in the output slice. - store_2x(ci, ci0, ci1); - - // Update the state with the plaintext blocks. - self.update(xi0, xi1); - } - - fn dec(&mut self, xi: &mut [u8; BLOCK_LEN], ci: &[u8; BLOCK_LEN]) { - // Generate two blocks of keystream. - let z0 = xor3(self.blocks[6], self.blocks[1], and(self.blocks[2], self.blocks[3])); - let z1 = xor3(self.blocks[2], self.blocks[5], and(self.blocks[6], self.blocks[7])); - - // Load the ciphertext blocks. - let (ci0, ci1) = load_2x(ci); - - // XOR the ciphertext blocks with the keystream to produce plaintext blocks. - let xi0 = xor(z0, ci0); - let xi1 = xor(z1, ci1); - - // Store plaintext blocks in the output slice. - store_2x(xi, xi0, xi1); - - // Update the state with the plaintext blocks. - self.update(xi0, xi1); - } - - fn dec_partial(&mut self, xn: &mut [u8; BLOCK_LEN], cn: &[u8]) { - // Pad the ciphertext with zeros to form two blocks. - let mut cn_padded = [0u8; BLOCK_LEN]; - cn_padded[..cn.len()].copy_from_slice(cn); - - // Generate two blocks of keystream. - let z0 = xor3(self.blocks[6], self.blocks[1], and(self.blocks[2], self.blocks[3])); - let z1 = xor3(self.blocks[2], self.blocks[5], and(self.blocks[6], self.blocks[7])); - - // Load the padded ciphertext blocks. - let (cn0, cn1) = load_2x(&cn_padded); - - // XOR the ciphertext blocks with the keystream to produce padded plaintext blocks. - let xn0 = xor(cn0, z0); - let xn1 = xor(cn1, z1); - - // Store plaintext blocks in the output slice and zero out the padding. - store_2x(xn, xn0, xn1); - xn[cn.len()..].fill(0); - - // Re-split the padding-less plaintext output, load it into blocks, and use it to update the - // state. - let (xn0, xn1) = load_2x(xn); - self.update(xn0, xn1); - } - - pub fn finalize(&mut self) -> ([u8; AES_BLOCK_LEN], [u8; BLOCK_LEN]) { - // Create a block from the associated data and message lengths, in bits, XOR it with the 3rd - // state block and update the state with that value. - let t = xor(load_64x2(self.ad_len * 8, self.mc_len * 8), self.blocks[2]); - for _ in 0..7 { - self.update(t, t); - } - - // Generate both short and long tags, re-using values where possible. - let mut short_tag = [0u8; AES_BLOCK_LEN]; - let mut long_tag = [0u8; BLOCK_LEN]; - let a = xor(xor3(self.blocks[0], self.blocks[1], self.blocks[2]), self.blocks[3]); - let b = xor3(self.blocks[4], self.blocks[5], self.blocks[6]); - store(&mut short_tag, xor(a, b)); - store_2x(&mut long_tag, a, xor(b, self.blocks[7])); - - (short_tag, long_tag) - } - - fn update(&mut self, m0: AesBlock, m1: AesBlock) { - // Make a temporary copy of the last state block. - let block7 = self.blocks[7]; - - // Perform the AES rounds in place. - self.blocks[7] = enc(self.blocks[6], self.blocks[7]); - self.blocks[6] = enc(self.blocks[5], self.blocks[6]); - self.blocks[5] = enc(self.blocks[4], self.blocks[5]); - self.blocks[4] = enc(self.blocks[3], self.blocks[4]); - self.blocks[3] = enc(self.blocks[2], self.blocks[3]); - self.blocks[2] = enc(self.blocks[1], self.blocks[2]); - self.blocks[1] = enc(self.blocks[0], self.blocks[1]); - self.blocks[0] = enc(block7, self.blocks[0]); - - // XOR blocks 0 and 4 with the two message blocks. - self.blocks[0] = xor(self.blocks[0], m0); - self.blocks[4] = xor(self.blocks[4], m1); - } -} - -/// Load two AES blocks from the given slice. -#[inline] -fn load_2x(bytes: &[u8; BLOCK_LEN]) -> (AesBlock, AesBlock) { - let (hi, lo) = bytes.split_at(AES_BLOCK_LEN); - (load(hi), load(lo)) -} - -/// Store two AES blocks in the given slice. -#[inline] -fn store_2x(bytes: &mut [u8; BLOCK_LEN], hi: AesBlock, lo: AesBlock) { - let (b_hi, b_lo) = bytes.split_at_mut(AES_BLOCK_LEN); - store(b_hi, hi); - store(b_lo, lo); -} - -#[cfg(all(test, feature = "std"))] -mod tests { - use super::*; - - use expect_test::expect; - use hex_literal::hex; - use proptest::collection::vec; - use proptest::prelude::*; - - fn encrypt(key: &[u8; 16], nonce: &[u8; 16], mc: &mut [u8], ad: &[u8]) -> ([u8; 16], [u8; 32]) { - let mut state = Aegis128L::new(key, nonce); - state.ad(ad); - state.encrypt(mc); - state.finalize() - } - - fn decrypt(key: &[u8; 16], nonce: &[u8; 16], mc: &mut [u8], ad: &[u8]) -> ([u8; 16], [u8; 32]) { - let mut state = Aegis128L::new(key, nonce); - state.ad(ad); - state.decrypt(mc); - state.finalize() - } - - #[test] - fn block_xor() { - let a = load(b"ayellowsubmarine"); - let b = load(b"tuneintotheocho!"); - let c = xor(a, b); - - let mut c_bytes = [0u8; 16]; - store(&mut c_bytes, c); - - expect!["150c0b090501031c010a080e11010144"].assert_eq(&hex::encode(c_bytes)); - } - - #[test] - fn block_xor3() { - let a = load(b"ayellowsubmarine"); - let b = load(b"tuneintotheocho!"); - let c = load(b"mambonumbereight"); - let d = xor3(a, b, c); - - let mut d_bytes = [0u8; 16]; - store(&mut d_bytes, d); - - expect!["786d666b6a6f7671636f7a6b78666930"].assert_eq(&hex::encode(d_bytes)); - } - - #[test] - fn block_and() { - let a = load(b"ayellowsubmarine"); - let b = load(b"tuneintotheocho!"); - let c = and(a, b); - - let mut c_bytes = [0u8; 16]; - store(&mut c_bytes, c); - - expect!["60716464686e74637460656162686e21"].assert_eq(&hex::encode(c_bytes)); - } - - // from https://www.ietf.org/archive/id/draft-irtf-cfrg-aegis-aead-04.html - - #[test] - fn aes_round_test_vector() { - let a = load(&hex!("000102030405060708090a0b0c0d0e0f")); - let b = load(&hex!("101112131415161718191a1b1c1d1e1f")); - let out = enc(a, b); - let mut c = [0u8; 16]; - store(&mut c, out); - - expect!["7a7b4e5638782546a8c0477a3b813f43"].assert_eq(&hex::encode(c)); - } - - #[test] - fn update_test_vector() { - let mut state = Aegis128L { - blocks: [ - load(&hex!("9b7e60b24cc873ea894ecc07911049a3")), - load(&hex!("330be08f35300faa2ebf9a7b0d274658")), - load(&hex!("7bbd5bd2b049f7b9b515cf26fbe7756c")), - load(&hex!("c35a00f55ea86c3886ec5e928f87db18")), - load(&hex!("9ebccafce87cab446396c4334592c91f")), - load(&hex!("58d83e31f256371e60fc6bb257114601")), - load(&hex!("1639b56ea322c88568a176585bc915de")), - load(&hex!("640818ffb57dc0fbc2e72ae93457e39a")), - ], - ad_len: 0, - mc_len: 0, - }; - - let d1 = load(&hex!("033e6975b94816879e42917650955aa0")); - let d2 = load(&hex!("033e6975b94816879e42917650955aa0")); - - state.update(d1, d2); - - let mut blocks = [[0u8; 16]; 8]; - store(&mut blocks[0], state.blocks[0]); - store(&mut blocks[1], state.blocks[1]); - store(&mut blocks[2], state.blocks[2]); - store(&mut blocks[3], state.blocks[3]); - store(&mut blocks[4], state.blocks[4]); - store(&mut blocks[5], state.blocks[5]); - store(&mut blocks[6], state.blocks[6]); - store(&mut blocks[7], state.blocks[7]); - - expect!["596ab773e4433ca0127c73f60536769d"].assert_eq(&hex::encode(blocks[0])); - expect!["790394041a3d26ab697bde865014652d"].assert_eq(&hex::encode(blocks[1])); - expect!["38cf49e4b65248acd533041b64dd0611"].assert_eq(&hex::encode(blocks[2])); - expect!["16d8e58748f437bfff1797f780337cee"].assert_eq(&hex::encode(blocks[3])); - expect!["69761320f7dd738b281cc9f335ac2f5a"].assert_eq(&hex::encode(blocks[4])); - expect!["a21746bb193a569e331e1aa985d0d729"].assert_eq(&hex::encode(blocks[5])); - expect!["09d714e6fcf9177a8ed1cde7e3d259a6"].assert_eq(&hex::encode(blocks[6])); - expect!["61279ba73167f0ab76f0a11bf203bdff"].assert_eq(&hex::encode(blocks[7])); - } - - #[test] - fn test_vector_1() { - let key = hex!("10010000000000000000000000000000"); - let nonce = hex!("10000200000000000000000000000000"); - let ad = hex!(""); - let (ct, (short_tag, long_tag)) = { - let mut msg = hex!("00000000000000000000000000000000"); - let tags = encrypt(&key, &nonce, &mut msg, &ad); - (msg, tags) - }; - - expect!["c1c0e58bd913006feba00f4b3cc3594e"].assert_eq(&hex::encode(ct)); - expect!["abe0ece80c24868a226a35d16bdae37a"].assert_eq(&hex::encode(short_tag)); - expect!["25835bfbb21632176cf03840687cb968cace4617af1bd0f7d064c639a5c79ee4"] - .assert_eq(&hex::encode(long_tag)); - } - - #[test] - fn test_vector_2() { - let key = hex!("10010000000000000000000000000000"); - let nonce = hex!("10000200000000000000000000000000"); - let ad = hex!(""); - let (ct, (short_tag, long_tag)) = { - let mut msg = [0u8; 0]; - let tags = encrypt(&key, &nonce, &mut msg, &ad); - (msg, tags) - }; - - assert_eq!([0u8; 0], ct); - expect!["c2b879a67def9d74e6c14f708bbcc9b4"].assert_eq(&hex::encode(short_tag)); - expect!["1360dc9db8ae42455f6e5b6a9d488ea4f2184c4e12120249335c4ee84bafe25d"] - .assert_eq(&hex::encode(long_tag)); - } - - #[test] - fn test_vector_3() { - let key = hex!("10010000000000000000000000000000"); - let nonce = hex!("10000200000000000000000000000000"); - let ad = hex!("0001020304050607"); - let (ct, (short_tag, long_tag)) = { - let mut msg = hex!( - "000102030405060708090a0b0c0d0e0f" - "101112131415161718191a1b1c1d1e1f" - ); - let tags = encrypt(&key, &nonce, &mut msg, &ad); - (msg, tags) - }; - - assert_eq!( - hex!( - "79d94593d8c2119d7e8fd9b8fc77845c" - "5c077a05b2528b6ac54b563aed8efe84" - ), - ct - ); - expect!["cc6f3372f6aa1bb82388d695c3962d9a"].assert_eq(&hex::encode(short_tag)); - expect!["022cb796fe7e0ae1197525ff67e309484cfbab6528ddef89f17d74ef8ecd82b3"] - .assert_eq(&hex::encode(long_tag)); - } - - #[test] - fn test_vector_4() { - let key = hex!("10010000000000000000000000000000"); - let nonce = hex!("10000200000000000000000000000000"); - let ad = hex!("0001020304050607"); - let (ct, (short_tag, long_tag)) = { - let mut msg = hex!("000102030405060708090a0b0c0d"); - let tags = encrypt(&key, &nonce, &mut msg, &ad); - (msg, tags) - }; - - expect!["79d94593d8c2119d7e8fd9b8fc77"].assert_eq(&hex::encode(ct)); - expect!["5c04b3dba849b2701effbe32c7f0fab7"].assert_eq(&hex::encode(short_tag)); - expect!["86f1b80bfb463aba711d15405d094baf4a55a15dbfec81a76f35ed0b9c8b04ac"] - .assert_eq(&hex::encode(long_tag)); - } - - #[test] - fn test_vector_5() { - let key = hex!("10010000000000000000000000000000"); - let nonce = hex!("10000200000000000000000000000000"); - let ad = hex!( - "000102030405060708090a0b0c0d0e0f" - "101112131415161718191a1b1c1d1e1f" - "20212223242526272829" - ); - let (ct, (short_tag, long_tag)) = { - let mut msg = hex!( - "101112131415161718191a1b1c1d1e1f" - "202122232425262728292a2b2c2d2e2f" - "3031323334353637" - ); - let tags = encrypt(&key, &nonce, &mut msg, &ad); - (msg, tags) - }; - - assert_eq!( - hex!( - "b31052ad1cca4e291abcf2df3502e6bd" - "b1bfd6db36798be3607b1f94d34478aa" - "7ede7f7a990fec10" - ), - ct - ); - expect!["7542a745733014f9474417b337399507"].assert_eq(&hex::encode(short_tag)); - expect!["b91e2947a33da8bee89b6794e647baf0fc835ff574aca3fc27c33be0db2aff98"] - .assert_eq(&hex::encode(long_tag)); - } - - #[test] - fn test_vector_6() { - let key = hex!("10000200000000000000000000000000"); - let nonce = hex!("10010000000000000000000000000000"); - let ad = hex!("0001020304050607"); - let mut ct = hex!("79d94593d8c2119d7e8fd9b8fc77"); - let (short_tag, long_tag) = decrypt(&key, &nonce, &mut ct, &ad); - - assert_ne!("5c04b3dba849b2701effbe32c7f0fab7", hex::encode(short_tag)); - assert_ne!( - "86f1b80bfb463aba711d15405d094baf4a55a15dbfec81a76f35ed0b9c8b04ac", - hex::encode(long_tag) - ); - } - - #[test] - fn test_vector_7() { - let key = hex!("10010000000000000000000000000000"); - let nonce = hex!("10000200000000000000000000000000"); - let ad = hex!("0001020304050607"); - let mut ct = hex!("79d94593d8c2119d7e8fd9b8fc78"); - let (short_tag, long_tag) = decrypt(&key, &nonce, &mut ct, &ad); - - assert_ne!("5c04b3dba849b2701effbe32c7f0fab7", hex::encode(short_tag)); - assert_ne!( - "86f1b80bfb463aba711d15405d094baf4a55a15dbfec81a76f35ed0b9c8b04ac", - hex::encode(long_tag) - ); - } - - #[test] - fn test_vector_8() { - let key = hex!("10010000000000000000000000000000"); - let nonce = hex!("10000200000000000000000000000000"); - let ad = hex!("0001020304050608"); - let mut ct = hex!("79d94593d8c2119d7e8fd9b8fc77"); - let (short_tag, long_tag) = decrypt(&key, &nonce, &mut ct, &ad); - - assert_ne!("5c04b3dba849b2701effbe32c7f0fab7", hex::encode(short_tag)); - assert_ne!( - "86f1b80bfb463aba711d15405d094baf4a55a15dbfec81a76f35ed0b9c8b04ac", - hex::encode(long_tag) - ); - } - - #[test] - fn test_vector_9() { - let key = hex!("10010000000000000000000000000000"); - let nonce = hex!("10000200000000000000000000000000"); - let ad = hex!("0001020304050607"); - let mut ct = hex!("79d94593d8c2119d7e8fd9b8fc77"); - let (short_tag, long_tag) = decrypt(&key, &nonce, &mut ct, &ad); - - assert_ne!("6c04b3dba849b2701effbe32c7f0fab8", hex::encode(short_tag)); - assert_ne!( - "86f1b80bfb463aba711d15405d094baf4a55a15dbfec81a76f35ed0b9c8b04ad", - hex::encode(long_tag) - ); - } - - proptest! { - #[test] - fn round_trip( - k: [u8; 16], - n: [u8; 16], - ad in vec(any::(), 0..200), - msg in vec(any::(), 0..200)) { - - let mut ct = msg.clone(); - let tag_e = encrypt(&k, &n, &mut ct, &ad); - let tag_d = decrypt(&k, &n, &mut ct, &ad); - - prop_assert_eq!(msg, ct, "invalid plaintext"); - prop_assert_eq!(tag_e, tag_d, "invalid tag"); - } - } -} diff --git a/src/aegis_128l/aarch64.rs b/src/aegis_128l/aarch64.rs deleted file mode 100644 index 1778f389..00000000 --- a/src/aegis_128l/aarch64.rs +++ /dev/null @@ -1,74 +0,0 @@ -pub use core::arch::aarch64::*; -pub use core::arch::asm; - -/// An AES block. -pub use self::uint8x16_t as AesBlock; - -/// Create an all-zero AES block. -#[inline] -pub fn zero() -> AesBlock { - unsafe { vmovq_n_u8(0) } -} - -/// Load an AES block from the given slice. -#[inline] -pub fn load(bytes: &[u8]) -> AesBlock { - unsafe { vld1q_u8(bytes.as_ptr()) } -} - -/// Load an AES block from the two given u64 values as big-endian integers. -#[inline] -pub fn load_64x2(a: u64, b: u64) -> AesBlock { - unsafe { vreinterpretq_u8_u64(vsetq_lane_u64(b, vmovq_n_u64(a), 1)) } -} - -/// Store an AES block in the given slice. -#[inline] -pub fn store(bytes: &mut [u8], block: AesBlock) { - unsafe { vst1q_u8(bytes.as_mut_ptr(), block) }; -} - -/// Bitwise XOR the given AES blocks. -#[inline] -pub fn xor(a: AesBlock, b: AesBlock) -> AesBlock { - unsafe { veorq_u8(a, b) } -} - -/// Bitwise XOR the given AES blocks. -#[inline] -pub fn xor3(a: AesBlock, b: AesBlock, c: AesBlock) -> AesBlock { - // TODO replace with veor3q_u8 intrinsic when that's stable - #[target_feature(enable = "sha3")] - unsafe fn veor3q_u8(mut a: AesBlock, b: AesBlock, c: AesBlock) -> AesBlock { - asm!( - "EOR3 {a:v}.16B, {a:v}.16B, {b:v}.16B, {c:v}.16B", - a = inlateout(vreg) a, b = in(vreg) b, c = in(vreg) c, - options(pure, nomem, nostack, preserves_flags) - ); - a - } - unsafe { veor3q_u8(a, b, c) } -} - -/// Bitwise AND the given AES blocks. -#[inline] -pub fn and(a: AesBlock, b: AesBlock) -> AesBlock { - unsafe { vandq_u8(a, b) } -} - -/// Perform one AES round on the given state using the given round key. -#[inline] -pub fn enc(state: AesBlock, round_key: AesBlock) -> AesBlock { - // TODO replace with vaeseq_u8 and vaesmcq_u8 instrinsics when that's stable - #[target_feature(enable = "aes")] - unsafe fn vaeseq_u8_and_vaesmcq_u8(mut state: AesBlock) -> AesBlock { - asm!( - "AESE {state:v}.16B, {zero:v}.16B", - "AESMC {state:v}.16B, {state:v}.16B", - state = inlateout(vreg) state, zero = in(vreg) 0, - options(pure, nomem, nostack, preserves_flags) - ); - state - } - unsafe { veorq_u8(vaeseq_u8_and_vaesmcq_u8(state), round_key) } -} diff --git a/src/aegis_128l/portable.rs b/src/aegis_128l/portable.rs deleted file mode 100644 index 6ea836e0..00000000 --- a/src/aegis_128l/portable.rs +++ /dev/null @@ -1,67 +0,0 @@ -/// An AES block. -pub use aes::Block as AesBlock; - -/// Create an all-zero AES block. -#[inline] -pub fn zero() -> AesBlock { - [0u8; 16].into() -} - -/// Load an AES block from the given slice. -#[inline] -pub fn load(bytes: &[u8]) -> AesBlock { - *AesBlock::from_slice(bytes) -} - -/// Load an AES block from the two given u64 values as big-endian integers. -#[inline] -pub fn load_64x2(a: u64, b: u64) -> AesBlock { - let mut buf = [0u8; core::mem::size_of::() * 2]; - let (a_block, b_block) = buf.split_at_mut(core::mem::size_of::()); - a_block.copy_from_slice(&a.to_le_bytes()); - b_block.copy_from_slice(&b.to_le_bytes()); - load(&buf) -} - -/// Store an AES block in the given slice. -#[inline] -pub fn store(bytes: &mut [u8], block: AesBlock) { - bytes.copy_from_slice(&block); -} - -/// Bitwise XOR the given AES blocks. -#[inline] -pub fn xor(a: AesBlock, b: AesBlock) -> AesBlock { - let mut out = AesBlock::default(); - for ((z, x), y) in out.iter_mut().zip(a).zip(b) { - *z = x ^ y; - } - out -} - -/// Bitwise XOR the given AES blocks. -#[inline] -pub fn xor3(a: AesBlock, b: AesBlock, c: AesBlock) -> AesBlock { - let mut out = AesBlock::default(); - for (((z, r), x), y) in out.iter_mut().zip(a).zip(b).zip(c) { - *z = r ^ x ^ y; - } - out -} - -/// Bitwise AND the given AES blocks. -#[inline] -pub fn and(a: AesBlock, b: AesBlock) -> AesBlock { - let mut out = AesBlock::default(); - for ((z, x), y) in out.iter_mut().zip(a).zip(b) { - *z = x & y; - } - out -} - -/// Perform one AES round on the given state using the given round key. -#[inline] -pub fn enc(mut state: AesBlock, round_key: AesBlock) -> AesBlock { - aes::hazmat::cipher_round(&mut state, &round_key); - state -} diff --git a/src/aegis_128l/x86_64.rs b/src/aegis_128l/x86_64.rs deleted file mode 100644 index 90faeb00..00000000 --- a/src/aegis_128l/x86_64.rs +++ /dev/null @@ -1,56 +0,0 @@ -#[cfg(target_arch = "x86")] -use core::arch::x86::{self as x86, *}; - -#[cfg(target_arch = "x86_64")] -use core::arch::x86_64::{self as x86, *}; - -/// An AES block. -pub use x86::__m128i as AesBlock; - -/// Create an all-zero AES block. -#[inline] -pub fn zero() -> AesBlock { - unsafe { _mm_setzero_si128() } -} - -/// Load an AES block from the given slice. -#[inline] -pub fn load(bytes: &[u8]) -> AesBlock { - unsafe { _mm_loadu_si128(bytes.as_ptr() as *const __m128i) } -} - -/// Load an AES block from the two given u64 values as big-endian integers. -#[inline] -pub fn load_64x2(a: u64, b: u64) -> AesBlock { - unsafe { _mm_set_epi64x(b.try_into().unwrap(), a.try_into().unwrap()) } -} - -/// Store an AES block in the given slice. -#[inline] -pub fn store(bytes: &mut [u8], block: AesBlock) { - unsafe { _mm_storeu_si128(bytes.as_mut_ptr() as *mut __m128i, block) }; -} - -/// Bitwise XOR the given AES blocks. -#[inline] -pub fn xor(a: AesBlock, b: AesBlock) -> AesBlock { - unsafe { _mm_xor_si128(a, b) } -} - -/// Bitwise XOR the given AES blocks. -#[inline] -pub fn xor3(a: AesBlock, b: AesBlock, c: AesBlock) -> AesBlock { - unsafe { _mm_xor_si128(a, _mm_xor_si128(b, c)) } -} - -/// Bitwise AND the given AES blocks. -#[inline] -pub fn and(a: AesBlock, b: AesBlock) -> AesBlock { - unsafe { _mm_and_si128(a, b) } -} - -/// Perform one AES round on the given state using the given round key. -#[inline] -pub fn enc(state: AesBlock, round_key: AesBlock) -> AesBlock { - unsafe { _mm_aesenc_si128(state, round_key) } -} diff --git a/src/lib.rs b/src/lib.rs index 920409c3..d64e474e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,16 +14,13 @@ clippy::semicolon_if_nothing_returned )] -use crate::aegis_128l::Aegis128L; - +use core::fmt::Debug; #[cfg(feature = "std")] use std::io::{self, Read, Write}; use cmov::CmovEq; #[cfg(feature = "hedge")] use rand_core::{CryptoRng, RngCore}; -use sha2::digest::FixedOutputReset; -use sha2::{Digest, Sha256}; #[cfg(feature = "docs")] #[doc = include_str!("../design.md")] @@ -33,11 +30,17 @@ pub mod design {} #[doc = include_str!("../perf.md")] pub mod perf {} -mod aegis_128l; +use aes::cipher::{KeyIvInit, StreamCipher}; +use sha2::digest::{Digest, FixedOutputReset}; +use sha2::Sha256; + mod integration_tests; +/// AES-128-CTR using a 128-bit Big Endian counter. +type Aes128Ctr = ctr::Ctr128BE; + /// The length of an authentication tag in bytes. -pub const TAG_LEN: usize = aegis_128l::AES_BLOCK_LEN; +pub const TAG_LEN: usize = 16; /// A stateful object providing fine-grained symmetric-key cryptographic services like hashing, /// message authentication codes, pseudo-random functions, authenticated encryption, and more. @@ -113,11 +116,12 @@ impl Protocol { /// Derive output from the protocol's current state and fill the given slice with it. #[inline] pub fn derive(&mut self, out: &mut [u8]) { - // Chain the protocol's key and generate an output key. - let mut output = self.chain(Operation::Derive); + // Chain the protocol's key and generate an output key and nonce. + let mut ctr = self.chain(Operation::Derive); // Fill the buffer with PRF output. - output.prf(out); + out.fill(0); + ctr.apply_keystream(out); // Update the state with the output length and the operation code. self.process(&(out.len() as u64).to_le_bytes(), Operation::Derive); @@ -134,33 +138,33 @@ impl Protocol { /// Encrypt the given slice in place. #[inline] pub fn encrypt(&mut self, in_out: &mut [u8]) { - // Chain the protocol's key and generate an output key. - let mut output = self.chain(Operation::Crypt); + // Chain the protocol's key and generate an output key and nonce. + let mut ctr = self.chain(Operation::Crypt); // Encrypt the plaintext. - output.encrypt(in_out); + ctr.apply_keystream(in_out); - // Calculate the 256-bit tag. - let (_, l_tag) = output.finalize(); + // Hash the ciphertext. + self.state.update(&in_out); - // Update the state with the tag and the operation code. - self.process(&l_tag, Operation::Crypt); + // Update the state with the ciphertext and the operation code. + self.process(&(in_out.len().to_le_bytes()), Operation::Crypt); } /// Decrypt the given slice in place. #[inline] pub fn decrypt(&mut self, in_out: &mut [u8]) { - // Chain the protocol's key and generate an output key. - let mut output = self.chain(Operation::Crypt); + // Chain the protocol's key and generate an output key and nonce. + let mut ctr = self.chain(Operation::Crypt); - // Decrypt the plaintext. - output.decrypt(in_out); + // Hash the ciphertext. + self.state.update(&in_out); - // Calculate the 256-bit tag. - let (_, l_tag) = output.finalize(); + // Decrypt the ciphertext. + ctr.apply_keystream(in_out); - // Update the state with the tag and the operation code. - self.process(&l_tag, Operation::Crypt); + // Update the state with the ciphertext and the operation code. + self.process(&(in_out.len().to_le_bytes()), Operation::Crypt); } /// Seals the given mutable slice in place. @@ -169,22 +173,13 @@ impl Protocol { #[inline] pub fn seal(&mut self, in_out: &mut [u8]) { // Split the buffer into plaintext and tag. - let (in_out, tag_out) = in_out.split_at_mut(in_out.len() - TAG_LEN); - - // Chain the protocol's key and generate an output key. - let mut output = self.chain(Operation::AuthCrypt); + let (in_out, tag) = in_out.split_at_mut(in_out.len() - TAG_LEN); // Encrypt the plaintext. - output.encrypt(in_out); + self.encrypt(in_out); - // Calculate the short and long tags. - let (s_tag, l_tag) = output.finalize(); - - // Append the short tag to the ciphertext. - tag_out.copy_from_slice(&s_tag); - - // Update the state with the long tag and the operation code. - self.process(&l_tag, Operation::AuthCrypt); + // Derive a tag. + self.derive(tag); } /// Opens the given mutable slice in place. Returns the plaintext slice of `in_out` if the input @@ -193,24 +188,16 @@ impl Protocol { #[must_use] pub fn open<'a>(&mut self, in_out: &'a mut [u8]) -> Option<&'a [u8]> { // Split the buffer into ciphertext and tag. - let (in_out, s_tag) = in_out.split_at_mut(in_out.len() - TAG_LEN); - - // Chain the protocol's key and generate an output key. - let mut output = self.chain(Operation::AuthCrypt); + let (in_out, tag) = in_out.split_at_mut(in_out.len() - TAG_LEN); // Decrypt the plaintext. - output.decrypt(in_out); + self.decrypt(in_out); - // Calculate the counterfactual short and long tags. - let (s_tag_p, l_tag) = output.finalize(); + // Derive a counterfactual tag. + let tag_p = self.derive_array::(); - // Update the state with the long tag and the operation code. - self.process(&l_tag, Operation::AuthCrypt); - - // Check the tag against the counterfactual short tag in constant time. - let mut eq = 0u8; - s_tag.cmoveq(&s_tag_p, 1, &mut eq); - if eq == 1 { + // Check the tag against the counterfactual tag in constant time. + if ct_eq(tag, &tag_p) { // If the tag is verified, then the ciphertext is authentic. Return the slice of the // input which contains the plaintext. Some(in_out) @@ -226,7 +213,7 @@ impl Protocol { /// Modifies the protocol's state irreversibly, preventing rollback. pub fn ratchet(&mut self) { // Chain the protocol's key, ignoring the PRF output. - let _ = self.chain(Operation::Ratchet); + self.chain(Operation::Ratchet); // Update the state with the operation code and zero length. self.end_op(Operation::Ratchet, 0); @@ -265,37 +252,39 @@ impl Protocol { unreachable!("unable to hedge a valid value in 10,000 tries"); } - /// Replace the protocol's state with derived output and return an operation-specific AEGIS-128L - /// instance. + /// Replace the protocol's state with derived output and initialize the cipher context. #[inline] - #[must_use] - fn chain(&mut self, operation: Operation) -> Aegis128L { + fn chain(&mut self, operation: Operation) -> Aes128Ctr { // Finalize the current state and reset it to an uninitialized state. let hash = self.state.finalize_fixed_reset(); - // Split the hash into a key and nonce and initialize an AEGIS-128L instance for PRF output. + // Split the hash into a key and nonce and initialize an AES-128-CTR instance for PRF + // output. let (prf_key, prf_nonce) = hash.split_at(16); - let mut prf = Aegis128L::new( - &prf_key.try_into().expect("should be 16 bytes"), - &prf_nonce.try_into().expect("should be 16 bytes"), + let mut prf = Aes128Ctr::new( + &<[u8; 16]>::try_from(prf_key).expect("should be 16 bytes").into(), + &<[u8; 16]>::try_from(prf_nonce).expect("should be 16 bytes").into(), ); - // Use the AEGIS-128L instance to generate a chain key and an output key and nonce. + // Generate 64 bytes of PRF output. let mut prf_out = [0u8; 64]; - prf.prf(&mut prf_out); + prf.apply_keystream(&mut prf_out); + + // Split the PRF output into a 32-byte chain key, a 16-byte output key, and a 16-byte output + // nonce. let (chain_key, output_key) = prf_out.split_at_mut(32); let (output_key, output_nonce) = output_key.split_at_mut(16); - // Initialize the current state with the chain key. - self.process(chain_key, Operation::Chain); - // Set the first byte of the output nonce to the operation code. output_nonce[0] = operation as u8; - // Return a AEGIS-128L instance keyed with the output key and nonce. - Aegis128L::new( - &output_key.try_into().expect("should be 16 bytes"), - &output_nonce.try_into().expect("should be 16 bytes"), + // Initialize the current state with the chain key. + self.process(chain_key, Operation::Chain); + + // Initialize an AES-128-CTR instance for output. + Aes128Ctr::new( + &<[u8; 16]>::try_from(output_key).expect("should be 16 bytes").into(), + &<[u8; 16]>::try_from(output_nonce).expect("should be 16 bytes").into(), ) } @@ -330,6 +319,16 @@ impl Protocol { } } +/// Compare two slices for equality in constant time. +pub fn ct_eq(a: &[u8], b: &[u8]) -> bool { + // TODO replace with slice cmovne when cmov >0.3.0 drops + let mut res = 1; + for (x, y) in a.iter().zip(b.iter()) { + x.cmovne(y, 0, &mut res); + } + res != 0 +} + /// A primitive operation in a protocol with a unique 1-byte code. #[derive(Debug, Clone, Copy)] enum Operation { @@ -337,9 +336,8 @@ enum Operation { Mix = 0x02, Derive = 0x03, Crypt = 0x04, - AuthCrypt = 0x05, - Ratchet = 0x06, - Chain = 0x07, + Ratchet = 0x05, + Chain = 0x06, } #[cfg(all(test, feature = "std"))] @@ -356,11 +354,11 @@ mod tests { protocol.mix(b"one"); protocol.mix(b"two"); - expect!["3f6d24ea37711c9e"].assert_eq(&hex::encode(protocol.derive_array::<8>())); + expect!["bf104433d3418198"].assert_eq(&hex::encode(protocol.derive_array::<8>())); let mut plaintext = b"this is an example".to_vec(); protocol.encrypt(&mut plaintext); - expect!["534f4064af0c07bf6bd8e93e8d39b38c3bc0"].assert_eq(&hex::encode(plaintext)); + expect!["7befee7f98fd117bf14ddcf1297e42a38afc"].assert_eq(&hex::encode(plaintext)); protocol.ratchet(); @@ -369,10 +367,10 @@ mod tests { sealed[..plaintext.len()].copy_from_slice(plaintext); protocol.seal(&mut sealed); - expect!["e7cc92b86d79f182b58b778492ad3169d090eddf089710e19b2edeea75da5e3d9628"] + expect!["00d6be82b649b75a53fbadc51b8e6e20de9c997937a726fb4300b640dc407d23d55d"] .assert_eq(&hex::encode(sealed)); - expect!["395ffb61c78bd8c0"].assert_eq(&hex::encode(protocol.derive_array::<8>())); + expect!["8d68e30bc94cc882"].assert_eq(&hex::encode(protocol.derive_array::<8>())); } #[test] diff --git a/xtask/src/xtask.rs b/xtask/src/xtask.rs index 41bc5e33..76805dcd 100644 --- a/xtask/src/xtask.rs +++ b/xtask/src/xtask.rs @@ -79,7 +79,7 @@ fn ci(sh: &Shell) -> Result<()> { fn bench(sh: &Shell, args: Vec) -> Result<()> { let args = args.join(" "); cmd!(sh, "cargo bench {args}") - .env("RUSTFLAGS", "-C target-cpu=native") + .env("RUSTFLAGS", "--cfg aes_armv8") .env("DIVAN_BYTES_FORMAT", "binary") .env("DIVAN_TIMER", "tsc") .env("DIVAN_MIN_TIME", "1") @@ -95,7 +95,7 @@ fn cloud_create(sh: &Shell) -> Result<()> { } fn cloud_setup(sh: &Shell) -> Result<()> { - cmd!(sh, "gcloud compute ssh lockstitch-benchmark --zone=us-central1-a --command 'sudo apt-get install build-essential git -y'").run()?; + cmd!(sh, "gcloud compute ssh lockstitch-benchmark --zone=us-central1-a --command 'sudo apt-get install build-essential git pkg-config libssl-dev -y'").run()?; cmd!(sh, "gcloud compute ssh lockstitch-benchmark --zone=us-central1-a --command 'curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y'").run()?; cmd!(sh, "gcloud compute ssh lockstitch-benchmark --zone=us-central1-a --command 'git clone https://github.com/codahale/lockstitch'").run()?; @@ -103,7 +103,7 @@ fn cloud_setup(sh: &Shell) -> Result<()> { } fn cloud_test(sh: &Shell, branch: &str) -> Result<()> { - let cmd = format!("source ~/.cargo/env && cd lockstitch && git pull --ff-only && git checkout {branch} && RUSTFLAGS='-C target-feature=+aes,+ssse3' cargo test"); + let cmd = format!("source ~/.cargo/env && cd lockstitch && git fetch && git reset --hard origin/{branch} && cargo test"); cmd!(sh, "gcloud compute ssh lockstitch-benchmark --zone=us-central1-a --command {cmd}") .run()?; @@ -111,7 +111,7 @@ fn cloud_test(sh: &Shell, branch: &str) -> Result<()> { } fn cloud_bench(sh: &Shell, branch: &str) -> Result<()> { - let cmd = format!("source ~/.cargo/env && cd lockstitch && git pull --ff-only && git checkout {branch} && RUSTFLAGS='-C target-feature=+aes,+ssse3' DIVAN_BYTES_FORMAT=binary DIVAN_TIMER=tsc DIVAN_MIN_TIME=1 cargo bench"); + let cmd = format!("source ~/.cargo/env && cd lockstitch && git fetch && git reset --hard origin/{branch} && cargo xtask bench"); cmd!(sh, "gcloud compute ssh lockstitch-benchmark --zone=us-central1-a --command {cmd}") .run()?;