From ea4eca8c8cd45875aa98db18a8bef5e0f9065d4c Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Thu, 2 Sep 2021 09:51:46 +0200 Subject: [PATCH 01/40] Balloon implementation --- .github/workflows/balloon.yml | 56 ++++++ Cargo.lock | 70 +++++-- Cargo.toml | 1 + README.md | 4 + balloon/CHANGELOG.md | 9 + balloon/Cargo.toml | 34 ++++ balloon/LICENSE-APACHE | 201 ++++++++++++++++++++ balloon/LICENSE-MIT | 25 +++ balloon/README.md | 56 ++++++ balloon/src/error.rs | 53 ++++++ balloon/src/lib.rs | 346 ++++++++++++++++++++++++++++++++++ balloon/src/params.rs | 106 +++++++++++ 12 files changed, 950 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/balloon.yml create mode 100644 balloon/CHANGELOG.md create mode 100644 balloon/Cargo.toml create mode 100644 balloon/LICENSE-APACHE create mode 100644 balloon/LICENSE-MIT create mode 100644 balloon/README.md create mode 100644 balloon/src/error.rs create mode 100644 balloon/src/lib.rs create mode 100644 balloon/src/params.rs diff --git a/.github/workflows/balloon.yml b/.github/workflows/balloon.yml new file mode 100644 index 00000000..dbc4932a --- /dev/null +++ b/.github/workflows/balloon.yml @@ -0,0 +1,56 @@ +name: balloon + +on: + pull_request: + paths: + - "balloon/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: balloon + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.51.0 # MSRV + - stable + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + override: true + - run: cargo build --target ${{ matrix.target }} --release --no-default-features + - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features password-hash + - run: cargo build --target ${{ matrix.target }} --release + + test: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.51.0 # MSRV + - stable + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + override: true + - run: cargo test --release --all-features diff --git a/Cargo.lock b/Cargo.lock index 96688d49..f67a3378 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,6 +20,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "balloon-hash" +version = "0.1.0" +dependencies = [ + "digest 0.9.0", + "hex", + "password-hash", + "rayon", + "sha2 0.9.8", +] + [[package]] name = "base64ct" version = "1.0.1" @@ -33,7 +44,7 @@ dependencies = [ "blowfish", "hex-literal", "pbkdf2", - "sha2", + "sha2 0.10.0", "zeroize", ] @@ -43,7 +54,16 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a58bdf5134c5beae6fc382002c4d88950bad1feea20f8f7165494b6b43b049de" dependencies = [ - "digest", + "digest 0.10.0", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", ] [[package]] @@ -149,13 +169,22 @@ dependencies = [ "generic-array", ] +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "digest" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8549e6bfdecd113b7e221fe60b433087f6957387a20f8118ebca9b12af19143d" dependencies = [ - "block-buffer", + "block-buffer 0.10.0", "crypto-common", "generic-array", "subtle", @@ -197,6 +226,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hex-literal" version = "0.3.4" @@ -209,7 +244,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddca131f3e7f2ce2df364b57949a9d47915cfbd35e46cfee355ccebbf794d6a2" dependencies = [ - "digest", + "digest 0.10.0", ] [[package]] @@ -264,13 +299,13 @@ dependencies = [ name = "pbkdf2" version = "0.10.0" dependencies = [ - "digest", + "digest 0.10.0", "hex-literal", "hmac", "password-hash", "rayon", "sha-1", - "sha2", + "sha2 0.10.0", "streebog", ] @@ -368,7 +403,7 @@ dependencies = [ "password-hash", "pbkdf2", "salsa20", - "sha2", + "sha2 0.10.0", ] [[package]] @@ -379,7 +414,7 @@ checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.0", ] [[package]] @@ -387,10 +422,23 @@ name = "sha-crypt" version = "0.3.2" dependencies = [ "rand", - "sha2", + "sha2 0.10.0", "subtle", ] +[[package]] +name = "sha2" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + [[package]] name = "sha2" version = "0.10.0" @@ -399,7 +447,7 @@ checksum = "900d964dd36bb15bcf2f2b35694c072feab74969a54f2bbeec7a2d725d2bdcb6" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.0", ] [[package]] @@ -408,7 +456,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f2a93b52a311873ee038192d8a95dc3bad1d638ac926c2afee0ea9887ecfaf0" dependencies = [ - "digest", + "digest 0.10.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ae8c916d..e3f4a849 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ resolver = "2" members = [ "argon2", + "balloon", "bcrypt-pbkdf", "pbkdf2", "scrypt", diff --git a/README.md b/README.md index 7bcffe4f..5ae41e02 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Collection of password hashing algorithms, otherwise known as password-based key | Algorithm | Crate | Crates.io | Documentation | MSRV | |-----------|-------|:----------:|:-------------:|:----:| | [Argon2] | [`argon2`] | [![crates.io](https://img.shields.io/crates/v/argon2.svg)](https://crates.io/crates/argon2) | [![Documentation](https://docs.rs/argon2/badge.svg)](https://docs.rs/argon2) | ![MSRV 1.51][msrv-1.51] | +| [Balloon] | [`balloon`] | [![crates.io](https://img.shields.io/crates/v/balloon-hash.svg)](https://crates.io/crates/balloon-hash) | [![Documentation](https://docs.rs/balloon-hash/badge.svg)](https://docs.rs/balloon-hash) | ![MSRV 1.56][msrv-1.56] | | [bcrypt-pbkdf] | [`bcrypt-pbkdf`] |[![crates.io](https://img.shields.io/crates/v/bcrypt-pbkdf.svg)](https://crates.io/crates/bcrypt-pbkdf) | [![Documentation](https://docs.rs/bcrypt-pbkdf/badge.svg)](https://docs.rs/bcrypt-pbkdf) | ![MSRV 1.51][msrv-1.51] | | [PBKDF2] | [`pbkdf2`] | [![crates.io](https://img.shields.io/crates/v/pbkdf2.svg)](https://crates.io/crates/pbkdf2) | [![Documentation](https://docs.rs/pbkdf2/badge.svg)](https://docs.rs/pbkdf2) | ![MSRV 1.51][msrv-1.51] | | [scrypt] | [`scrypt`] | [![crates.io](https://img.shields.io/crates/v/scrypt.svg)](https://crates.io/crates/scrypt) | [![Documentation](https://docs.rs/scrypt/badge.svg)](https://docs.rs/scrypt) | ![MSRV 1.51][msrv-1.51] | @@ -41,10 +42,12 @@ Unless you explicitly state otherwise, any contribution intentionally submitted [deps-image]: https://deps.rs/repo/github/RustCrypto/password-hashes/status.svg [deps-link]: https://deps.rs/repo/github/RustCrypto/password-hashes [msrv-1.51]: https://img.shields.io/badge/rustc-1.51.0+-blue.svg +[msrv-1.56]: https://img.shields.io/badge/rustc-1.56.0+-blue.svg [//]: # (crates) [`argon2`]: ./argon2 +[`balloon`]: ./balloon [`bcrypt-pbkdf`]: ./bcrypt-pbkdf [`pbkdf2`]: ./pbkdf2 [`scrypt`]: ./scrypt @@ -53,6 +56,7 @@ Unless you explicitly state otherwise, any contribution intentionally submitted [//]: # (general links) [Argon2]: https://en.wikipedia.org/wiki/Argon2 +[Balloon]: https://en.wikipedia.org/wiki/Balloon_hashing [bcrypt-pbkdf]: https://flak.tedunangst.com/post/bcrypt-pbkdf [PBKDF2]: https://en.wikipedia.org/wiki/PBKDF2 [scrypt]: https://en.wikipedia.org/wiki/Scrypt diff --git a/balloon/CHANGELOG.md b/balloon/CHANGELOG.md new file mode 100644 index 00000000..425b5f82 --- /dev/null +++ b/balloon/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## 0.1.0 (2021-09-01) +- Initial release diff --git a/balloon/Cargo.toml b/balloon/Cargo.toml new file mode 100644 index 00000000..ebd1b87c --- /dev/null +++ b/balloon/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "balloon-hash" +version = "0.1.0" # Also update html_root_url in lib.rs when bumping this +description = "Pure Rust implementation of the Balloon password hashing function" +authors = ["RustCrypto Developers"] +license = "MIT OR Apache-2.0" +documentation = "https://docs.rs/balloon-hash" +repository = "https://github.com/RustCrypto/password-hashes/tree/master/balloon" +keywords = ["crypto", "password", "hashing"] +categories = ["cryptography", "no-std"] +edition = "2018" +readme = "README.md" + +[dependencies] +digest = { version = "0.9", default-features = false } + +# optional dependencies +password-hash = { version = "0.3", default-features = false, optional = true } +rayon = { version = "1", optional = true } + +[dev-dependencies] +hex = "0.4" +sha2 = "0.9" + +[features] +default = ["alloc", "password-hash", "rand"] +alloc = [] +parallel = ["rayon", "std"] +rand = ["password-hash/rand_core"] +std = ["alloc", "password-hash/std"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/balloon/LICENSE-APACHE b/balloon/LICENSE-APACHE new file mode 100644 index 00000000..78173fa2 --- /dev/null +++ b/balloon/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/balloon/LICENSE-MIT b/balloon/LICENSE-MIT new file mode 100644 index 00000000..c869ada5 --- /dev/null +++ b/balloon/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2021 The RustCrypto Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/balloon/README.md b/balloon/README.md new file mode 100644 index 00000000..73189028 --- /dev/null +++ b/balloon/README.md @@ -0,0 +1,56 @@ +# RustCrypto: Balloon + +[![crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +![Apache2/MIT licensed][license-image] +![Rust Version][rustc-image] +[![Project Chat][chat-image]][chat-link] +[![Build Status][build-image]][build-link] + +Pure Rust implementation of the [Balloon] password hashing function. + +[Documentation][docs-link] + +## Minimum Supported Rust Version + +Rust **1.51** or higher. + +Minimum supported Rust version can be changed in the future, but it will be +done with a minor version bump. + +## SemVer Policy + +- All on-by-default features of this library are covered by SemVer +- MSRV is considered exempt from SemVer as noted above + +## License + +Licensed under either of: + + * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) + * [MIT license](http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[//]: # (badges) + +[crate-image]: https://img.shields.io/crates/v/balloon-hash.svg +[crate-link]: https://crates.io/crates/balloon-hash +[docs-image]: https://docs.rs/balloon-hash/badge.svg +[docs-link]: https://docs.rs/balloon-hash/ +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.51+-blue.svg +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260046-password-hashes +[build-image]: https://github.com/RustCrypto/password-hashes/workflows/balloon/badge.svg?branch=master&event=push +[build-link]: https://github.com/RustCrypto/password-hashes/actions?query=workflow%3Aballoon + +[//]: # (general links) + +[Balloon]: https://en.wikipedia.org/wiki/Balloon_hashing diff --git a/balloon/src/error.rs b/balloon/src/error.rs new file mode 100644 index 00000000..642186e2 --- /dev/null +++ b/balloon/src/error.rs @@ -0,0 +1,53 @@ +//! Error type + +use core::fmt; + +#[cfg(feature = "password-hash")] +use password_hash::errors::InvalidValue; + +/// Result with balloon's [`Error`] type. +pub type Result = core::result::Result; + +/// Error type. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Error { + /// Memory cost is too small. + MemoryTooLittle, + /// Output is too short. + OutputTooShort, + /// Not enough threads. + ThreadsTooFew, + /// Too many threads. + ThreadsTooMany, + /// Time cost is too small. + TimeTooSmall, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Error::MemoryTooLittle => "memory cost is too small", + Error::OutputTooShort => "output is too short", + Error::ThreadsTooFew => "not enough threads", + Error::ThreadsTooMany => "too many threads", + Error::TimeTooSmall => "time cost is too small", + }) + } +} + +#[cfg(feature = "password-hash")] +#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))] +impl From for password_hash::Error { + fn from(err: Error) -> password_hash::Error { + match err { + Error::MemoryTooLittle => InvalidValue::TooShort.param_error(), + Error::OutputTooShort => password_hash::Error::OutputTooShort, + Error::ThreadsTooFew => InvalidValue::TooShort.param_error(), + Error::ThreadsTooMany => InvalidValue::TooLong.param_error(), + Error::TimeTooSmall => InvalidValue::TooShort.param_error(), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Error {} diff --git a/balloon/src/lib.rs b/balloon/src/lib.rs new file mode 100644 index 00000000..a8045ebf --- /dev/null +++ b/balloon/src/lib.rs @@ -0,0 +1,346 @@ +//! Pure Rust implementation of the [Balloon] password hashing function as +//! specified in [this paper](https://eprint.iacr.org/2016/027.pdf). +//! +//! # Usage (simple with default params) +//! +//! Note: this example requires the `rand_core` crate with the `std` feature +//! enabled for `rand_core::OsRng` (embedded platforms can substitute their +//! own RNG) +//! +//! Add the following to your crate's `Cargo.toml` to import it: +//! +//! ```toml +//! [dependencies] +//! balloon-hash = "0.1" +//! rand_core = { version = "0.6", features = ["std"] } +//! sha2 = "0.9" +//! ``` +//! +//! The following example demonstrates the high-level password hashing API: +//! +//! ``` +//! # fn main() -> Result<(), Box> { +//! # #[cfg(all(feature = "password-hash", feature = "std"))] +//! # { +//! use balloon_hash::{ +//! password_hash::{ +//! rand_core::OsRng, +//! PasswordHash, PasswordHasher, PasswordVerifier, SaltString +//! }, +//! Balloon +//! }; +//! use sha2::Sha256; +//! +//! let password = b"hunter42"; // Bad password; don't actually use! +//! let salt = SaltString::generate(&mut OsRng); +//! +//! // Balloon with default params +//! let balloon = Balloon::::default(); +//! +//! // Hash password to PHC string ($balloon$v=1$...) +//! let password_hash = balloon.hash_password(password, &salt)?.to_string(); +//! +//! // Verify password against PHC string +//! let parsed_hash = PasswordHash::new(&password_hash)?; +//! assert!(balloon.verify_password(password, &parsed_hash).is_ok()); +//! # } +//! # Ok(()) +//! # } +//! ``` +//! +//! [Balloon]: https://en.wikipedia.org/wiki/Balloon_hashing + +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", + html_root_url = "https://docs.rs/balloon-hash/0.1.0" +)] +#![warn(rust_2018_idioms, missing_docs)] + +#[cfg(feature = "alloc")] +extern crate alloc; + +mod error; +mod params; + +pub use crate::{ + error::{Error, Result}, + params::Params, +}; +use core::convert::{TryFrom, TryInto}; +use core::marker::PhantomData; +use digest::generic_array::GenericArray; +use digest::Digest; +#[cfg(feature = "password-hash")] +#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))] +pub use password_hash::{self, PasswordHash, PasswordHasher, PasswordVerifier}; +#[cfg(all(feature = "alloc", feature = "password-hash"))] +use password_hash::{Decimal, Ident, ParamsString, Salt}; + +/// Balloon context. +/// +/// This is the primary type of this crate's API, and contains the following: +/// +/// - Default set of [`Params`] to be used +/// - (Optional) Secret key a.k.a. "pepper" to be used +#[derive(Clone, Default)] +pub struct Balloon<'key, D: Digest> { + /// Storing which hash function is used + pub digest: PhantomData, + /// Algorithm parameters + pub params: Params, + /// Key array + pub secret: Option<&'key [u8]>, +} + +impl<'key, D: Digest> Balloon<'key, D> { + /// Create a new Balloon context. + pub fn new(params: Params, secret: Option<&'key [u8]>) -> Self { + Self { + digest: PhantomData, + params, + secret, + } + } + + /// Hash a password and associated parameters. + #[cfg(feature = "alloc")] + #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] + pub fn hash(&self, pwd: &[u8], salt: &[u8]) -> Result> { + if self.params.p_cost.get() == 1 { + let mut memory = alloc::vec![GenericArray::default(); usize::try_from(self.params.s_cost.get()).unwrap()]; + self.hash_with_memory(pwd, salt, &mut memory) + } else { + #[cfg(not(feature = "parallel"))] + return Err(Error::ThreadsTooMany); + #[cfg(feature = "parallel")] + { + use core::num::NonZeroU32; + use rayon::iter::{IntoParallelIterator, ParallelIterator}; + + let mut params = self.params; + params.p_cost = NonZeroU32::new(1).unwrap(); + + (1..=self.params.p_cost.get()) + .into_par_iter() + .map_with((params, self.secret), |(params, secret), index| { + const U32_SIZE: usize = core::mem::size_of::(); + + // expand salt if necessary + let mut salt = if salt.len() < U32_SIZE { + let mut new_salt = [0; U32_SIZE]; + new_salt[..salt.len()].copy_from_slice(salt); + new_salt.to_vec() + } else { + salt.to_vec() + }; + + // add thread index to salt + let byte = salt[..U32_SIZE].try_into().unwrap(); + salt[..U32_SIZE] + .copy_from_slice(&(u32::from_le_bytes(byte) + index).to_le_bytes()); + Self::new(*params, *secret).hash(pwd, &salt) + }) + .try_reduce(GenericArray::default, |a, b| { + Ok(a.into_iter().zip(b).map(|(a, b)| a ^ b).collect()) + }) + } + } + } + + /// Hash a password and associated parameters. + /// + /// This method takes an explicit `memory_blocks` parameter which allows + /// the caller to provide the backing storage for the algorithm's state: + /// + /// - Users with the `alloc` feature enabled can use [`Balloon::hash`] + /// to have it allocated for them. + /// - `no_std` users on "heapless" targets can use an array of the [`GenericArray`] type + /// to stack allocate this buffer. + pub fn hash_with_memory( + &self, + pwd: &[u8], + salt: &[u8], + memory_blocks: &mut [GenericArray], + ) -> Result> { + if self.params.p_cost.get() > 1 { + return Err(Error::ThreadsTooMany); + } + + let s_cost = usize::try_from(self.params.s_cost.get()).unwrap(); + let t_cost = usize::try_from(self.params.t_cost.get()).unwrap(); + + let mut digest = D::new(); + + // This is a direct translation of the `Balloon` from chapter 3.1. + // int delta = 3 // Number of dependencies per block + const DELTA: u32 = 3; + // int cnt = 0 // A counter (used in security proof) + let mut cnt: u32 = 0; + // block_t buf[s_cost]): // The main buffer + let memory_blocks = memory_blocks + .get_mut(..s_cost) + .ok_or(Error::MemoryTooLittle)?; + + // Step 1. Expand input into buffer. + // buf[0] = hash(cnt++, passwd, salt) + digest.update(cnt.to_le_bytes()); + cnt += 1; + digest.update(pwd); + digest.update(salt); + + if let Some(secret) = self.secret { + digest.update(secret); + } + + memory_blocks[0] = digest.finalize_reset(); + + // for m from 1 to s_cost-1: + for m in 1..s_cost { + // buf[m] = hash(cnt++, buf[m-1]) + digest.update(&cnt.to_le_bytes()); + cnt += 1; + digest.update(&memory_blocks[m - 1]); + memory_blocks[m] = digest.finalize_reset(); + } + + // Step 2. Mix buffer contents. + // for t from 0 to t_cost-1: + for t in 0..t_cost { + // for m from 0 to s_cost-1: + for m in 0..s_cost { + // Step 2a. Hash last and current blocks. + // block_t prev = buf[(m-1) mod s_cost] + let prev = if m == 0 { s_cost - 1 } else { m - 1 }; + let prev = &memory_blocks[prev]; + + // buf[m] = hash(cnt++, prev, buf[m]) + digest.update(&cnt.to_le_bytes()); + cnt += 1; + digest.update(prev); + digest.update(&memory_blocks[m]); + memory_blocks[m] = digest.finalize_reset(); + + // Step 2b. Hash in pseudorandomly chosen blocks. + // for i from 0 to delta-1: + for i in 0..DELTA { + // block_t idx_block = ints_to_block(t, m, i) + // int other = to_int(hash(cnt++, salt, idx_block)) mod s_cost + digest.update(&cnt.to_le_bytes()); + cnt += 1; + digest.update(salt); + digest.update(&u32::try_from(t).unwrap().to_le_bytes()); + digest.update(&u32::try_from(m).unwrap().to_le_bytes()); + digest.update(&i.to_le_bytes()); + let other = + u32::from_le_bytes(digest.finalize_reset()[..4].try_into().unwrap()) + % self.params.s_cost.get(); + let other = usize::try_from(other).unwrap(); + + // buf[m] = hash(cnt++, buf[m], buf[other]) + digest.update(&cnt.to_le_bytes()); + cnt += 1; + digest.update(&memory_blocks[m]); + digest.update(&memory_blocks[other]); + memory_blocks[m] = digest.finalize_reset(); + } + } + } + + // Step 3. Extract output from buffer. + // return buf[s_cost-1] + Ok(memory_blocks[s_cost - 1].clone()) + } +} + +#[cfg(all(feature = "alloc", feature = "password-hash"))] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))] +impl PasswordHasher for Balloon<'_, D> { + type Params = Params; + + fn hash_password<'a, S>( + &self, + password: &[u8], + salt: &'a S, + ) -> password_hash::Result> + where + S: AsRef + ?Sized, + { + let salt = Salt::try_from(salt.as_ref())?; + let mut salt_arr = [0u8; 64]; + let salt_bytes = salt.b64_decode(&mut salt_arr)?; + + let output = password_hash::Output::new(&self.hash(password, salt_bytes)?)?; + + Ok(PasswordHash { + algorithm: Ident::new("balloon"), + version: Some(1), + params: ParamsString::try_from(&self.params)?, + salt: Some(salt), + hash: Some(output), + }) + } + + fn hash_password_customized<'a>( + &self, + password: &[u8], + alg_id: Option>, + version: Option, + params: Params, + salt: impl Into>, + ) -> password_hash::Result> { + if let Some(alg_id) = alg_id { + if alg_id.as_str() != "balloon" { + return Err(password_hash::Error::Algorithm); + } + } + + if let Some(version) = version { + if version != 1 { + return Err(password_hash::Error::Version); + } + } + + let salt = salt.into(); + + Self::new(params, self.secret).hash_password(password, salt.as_str()) + } +} + +#[cfg(all(test, feature = "alloc", feature = "password-hash"))] +#[test] +fn hash_simple_retains_configured_params() { + use sha2::Sha256; + + /// Example password only: don't use this as a real password!!! + const EXAMPLE_PASSWORD: &[u8] = b"hunter42"; + + /// Example salt value. Don't use a static salt value!!! + const EXAMPLE_SALT: &str = "examplesalt"; + + // Non-default but valid parameters + let t_cost = 4; + let s_cost = 2048; + let p_cost = 2; + + let params = Params::new(s_cost, t_cost, p_cost).unwrap(); + let hasher = Balloon::::new(params, None); + let hash = hasher + .hash_password(EXAMPLE_PASSWORD, EXAMPLE_SALT) + .unwrap(); + + assert_eq!(hash.version.unwrap(), 1); + + for &(param, value) in &[("t", t_cost), ("s", s_cost), ("p", p_cost)] { + assert_eq!( + hash.params + .get(param) + .and_then(|p| p.decimal().ok()) + .unwrap(), + value + ); + } +} diff --git a/balloon/src/params.rs b/balloon/src/params.rs new file mode 100644 index 00000000..7e988863 --- /dev/null +++ b/balloon/src/params.rs @@ -0,0 +1,106 @@ +//! Argon2 password hash parameters. + +use crate::{Error, Result}; +use core::convert::TryFrom; +use core::num::NonZeroU32; +#[cfg(feature = "password-hash")] +use password_hash::{errors::InvalidValue, ParamsString, PasswordHash}; + +/// Argon2 password hash parameters. +/// +/// These are parameters which can be encoded into a PHC hash string. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Params { + /// Space cost, expressed in of blocks. + pub s_cost: NonZeroU32, + /// Time cost, expressed in number of rounds. + pub t_cost: NonZeroU32, + /// Degree of parallelism, expressed in number of threads. + pub p_cost: NonZeroU32, +} + +impl Params { + /// Default memory cost. + pub const DEFAULT_S_COST: u32 = 1024; + + /// Default number of iterations (i.e. "time"). + pub const DEFAULT_T_COST: u32 = 1; + + /// Default degree of parallelism. + pub const DEFAULT_P_COST: u32 = 1; + + /// Create new parameters. + pub fn new(s_cost: u32, t_cost: u32, p_cost: u32) -> Result { + Ok(Self { + s_cost: NonZeroU32::new(s_cost).ok_or(Error::MemoryTooLittle)?, + t_cost: NonZeroU32::new(t_cost).ok_or(Error::TimeTooSmall)?, + p_cost: NonZeroU32::new(p_cost).ok_or(Error::ThreadsTooFew)?, + }) + } +} + +impl Default for Params { + fn default() -> Self { + Self::new( + Self::DEFAULT_S_COST, + Self::DEFAULT_T_COST, + Self::DEFAULT_P_COST, + ) + .unwrap() + } +} + +#[cfg(feature = "password-hash")] +#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))] +impl<'a> TryFrom<&'a PasswordHash<'a>> for Params { + type Error = password_hash::Error; + + fn try_from(hash: &'a PasswordHash<'a>) -> password_hash::Result { + let mut params = Self::default(); + + for (ident, value) in hash.params.iter() { + match ident.as_str() { + "s" => { + params.s_cost = NonZeroU32::new(value.decimal()?) + .ok_or_else(|| InvalidValue::TooShort.param_error())?; + } + "t" => { + params.t_cost = NonZeroU32::new(value.decimal()?) + .ok_or_else(|| InvalidValue::TooShort.param_error())?; + } + "p" => { + params.p_cost = NonZeroU32::new(value.decimal()?) + .ok_or_else(|| InvalidValue::TooShort.param_error())?; + } + _ => return Err(password_hash::Error::ParamNameInvalid), + } + } + + Ok(params) + } +} + +#[cfg(feature = "password-hash")] +#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))] +impl<'a> TryFrom for ParamsString { + type Error = password_hash::Error; + + fn try_from(params: Params) -> password_hash::Result { + ParamsString::try_from(¶ms) + } +} + +#[cfg(feature = "password-hash")] +#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))] +impl<'a> TryFrom<&Params> for ParamsString { + type Error = password_hash::Error; + + fn try_from(params: &Params) -> password_hash::Result { + let mut output = ParamsString::new(); + output.add_decimal("s", params.s_cost.get())?; + output.add_decimal("t", params.t_cost.get())?; + output.add_decimal("p", params.p_cost.get())?; + + Ok(output) + } +} From 44c841f8f659b4fc85c3fbd5e5de1f92d8b68c06 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Thu, 2 Sep 2021 11:03:18 +0200 Subject: [PATCH 02/40] Small improvements --- Cargo.lock | 7 ------- balloon/Cargo.toml | 1 - balloon/src/lib.rs | 12 ++++++------ 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f67a3378..46af64e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,7 +25,6 @@ name = "balloon-hash" version = "0.1.0" dependencies = [ "digest 0.9.0", - "hex", "password-hash", "rayon", "sha2 0.9.8", @@ -226,12 +225,6 @@ dependencies = [ "libc", ] -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - [[package]] name = "hex-literal" version = "0.3.4" diff --git a/balloon/Cargo.toml b/balloon/Cargo.toml index ebd1b87c..6fbadd3c 100644 --- a/balloon/Cargo.toml +++ b/balloon/Cargo.toml @@ -19,7 +19,6 @@ password-hash = { version = "0.3", default-features = false, optional = true } rayon = { version = "1", optional = true } [dev-dependencies] -hex = "0.4" sha2 = "0.9" [features] diff --git a/balloon/src/lib.rs b/balloon/src/lib.rs index a8045ebf..f5a7feca 100644 --- a/balloon/src/lib.rs +++ b/balloon/src/lib.rs @@ -140,7 +140,7 @@ impl<'key, D: Digest> Balloon<'key, D> { // add thread index to salt let byte = salt[..U32_SIZE].try_into().unwrap(); salt[..U32_SIZE] - .copy_from_slice(&(u32::from_le_bytes(byte) + index).to_le_bytes()); + .copy_from_slice(&(u32::from_le_bytes(byte).wrapping_add(index)).to_le_bytes()); Self::new(*params, *secret).hash(pwd, &salt) }) .try_reduce(GenericArray::default, |a, b| { @@ -187,7 +187,7 @@ impl<'key, D: Digest> Balloon<'key, D> { // Step 1. Expand input into buffer. // buf[0] = hash(cnt++, passwd, salt) digest.update(cnt.to_le_bytes()); - cnt += 1; + cnt = cnt.wrapping_add(1); digest.update(pwd); digest.update(salt); @@ -201,7 +201,7 @@ impl<'key, D: Digest> Balloon<'key, D> { for m in 1..s_cost { // buf[m] = hash(cnt++, buf[m-1]) digest.update(&cnt.to_le_bytes()); - cnt += 1; + cnt = cnt.wrapping_add(1); digest.update(&memory_blocks[m - 1]); memory_blocks[m] = digest.finalize_reset(); } @@ -218,7 +218,7 @@ impl<'key, D: Digest> Balloon<'key, D> { // buf[m] = hash(cnt++, prev, buf[m]) digest.update(&cnt.to_le_bytes()); - cnt += 1; + cnt = cnt.wrapping_add(1); digest.update(prev); digest.update(&memory_blocks[m]); memory_blocks[m] = digest.finalize_reset(); @@ -229,7 +229,7 @@ impl<'key, D: Digest> Balloon<'key, D> { // block_t idx_block = ints_to_block(t, m, i) // int other = to_int(hash(cnt++, salt, idx_block)) mod s_cost digest.update(&cnt.to_le_bytes()); - cnt += 1; + cnt = cnt.wrapping_add(1); digest.update(salt); digest.update(&u32::try_from(t).unwrap().to_le_bytes()); digest.update(&u32::try_from(m).unwrap().to_le_bytes()); @@ -241,7 +241,7 @@ impl<'key, D: Digest> Balloon<'key, D> { // buf[m] = hash(cnt++, buf[m], buf[other]) digest.update(&cnt.to_le_bytes()); - cnt += 1; + cnt = cnt.wrapping_add(1); digest.update(&memory_blocks[m]); digest.update(&memory_blocks[other]); memory_blocks[m] = digest.finalize_reset(); From 9537dd096ab5cb56b4b343705bf95339acbe4804 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Thu, 2 Sep 2021 11:07:12 +0200 Subject: [PATCH 03/40] Fix CI --- balloon/src/lib.rs | 5 +++-- balloon/src/params.rs | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/balloon/src/lib.rs b/balloon/src/lib.rs index f5a7feca..1a9aad5e 100644 --- a/balloon/src/lib.rs +++ b/balloon/src/lib.rs @@ -139,8 +139,9 @@ impl<'key, D: Digest> Balloon<'key, D> { // add thread index to salt let byte = salt[..U32_SIZE].try_into().unwrap(); - salt[..U32_SIZE] - .copy_from_slice(&(u32::from_le_bytes(byte).wrapping_add(index)).to_le_bytes()); + salt[..U32_SIZE].copy_from_slice( + &(u32::from_le_bytes(byte).wrapping_add(index)).to_le_bytes(), + ); Self::new(*params, *secret).hash(pwd, &salt) }) .try_reduce(GenericArray::default, |a, b| { diff --git a/balloon/src/params.rs b/balloon/src/params.rs index 7e988863..979b0ac2 100644 --- a/balloon/src/params.rs +++ b/balloon/src/params.rs @@ -1,10 +1,12 @@ //! Argon2 password hash parameters. use crate::{Error, Result}; -use core::convert::TryFrom; use core::num::NonZeroU32; #[cfg(feature = "password-hash")] -use password_hash::{errors::InvalidValue, ParamsString, PasswordHash}; +use { + core::convert::TryFrom, + password_hash::{errors::InvalidValue, ParamsString, PasswordHash}, +}; /// Argon2 password hash parameters. /// From 907b2825cfc4a403a652ec66ab024858297b4323 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Thu, 2 Sep 2021 12:18:18 +0200 Subject: [PATCH 04/40] Small improvements --- balloon/src/error.rs | 4 ---- balloon/src/lib.rs | 8 +++++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/balloon/src/error.rs b/balloon/src/error.rs index 642186e2..d3eedb52 100644 --- a/balloon/src/error.rs +++ b/balloon/src/error.rs @@ -13,8 +13,6 @@ pub type Result = core::result::Result; pub enum Error { /// Memory cost is too small. MemoryTooLittle, - /// Output is too short. - OutputTooShort, /// Not enough threads. ThreadsTooFew, /// Too many threads. @@ -27,7 +25,6 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { Error::MemoryTooLittle => "memory cost is too small", - Error::OutputTooShort => "output is too short", Error::ThreadsTooFew => "not enough threads", Error::ThreadsTooMany => "too many threads", Error::TimeTooSmall => "time cost is too small", @@ -41,7 +38,6 @@ impl From for password_hash::Error { fn from(err: Error) -> password_hash::Error { match err { Error::MemoryTooLittle => InvalidValue::TooShort.param_error(), - Error::OutputTooShort => password_hash::Error::OutputTooShort, Error::ThreadsTooFew => InvalidValue::TooShort.param_error(), Error::ThreadsTooMany => InvalidValue::TooLong.param_error(), Error::TimeTooSmall => InvalidValue::TooShort.param_error(), diff --git a/balloon/src/lib.rs b/balloon/src/lib.rs index 1a9aad5e..c5dc1d71 100644 --- a/balloon/src/lib.rs +++ b/balloon/src/lib.rs @@ -235,9 +235,11 @@ impl<'key, D: Digest> Balloon<'key, D> { digest.update(&u32::try_from(t).unwrap().to_le_bytes()); digest.update(&u32::try_from(m).unwrap().to_le_bytes()); digest.update(&i.to_le_bytes()); - let other = - u32::from_le_bytes(digest.finalize_reset()[..4].try_into().unwrap()) - % self.params.s_cost.get(); + let other = u32::from_le_bytes( + digest.finalize_reset()[..core::mem::size_of::()] + .try_into() + .unwrap(), + ) % self.params.s_cost.get(); let other = usize::try_from(other).unwrap(); // buf[m] = hash(cnt++, buf[m], buf[other]) From 0fc6acc4e46c0b9d7cb27b0dd53cd6b35dfb3e20 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Wed, 15 Sep 2021 14:49:13 +0200 Subject: [PATCH 05/40] Improvements --- balloon/src/lib.rs | 111 ++++++++++++++++++++++++--------------------- 1 file changed, 60 insertions(+), 51 deletions(-) diff --git a/balloon/src/lib.rs b/balloon/src/lib.rs index c5dc1d71..ac55bb45 100644 --- a/balloon/src/lib.rs +++ b/balloon/src/lib.rs @@ -111,38 +111,28 @@ impl<'key, D: Digest> Balloon<'key, D> { pub fn hash(&self, pwd: &[u8], salt: &[u8]) -> Result> { if self.params.p_cost.get() == 1 { let mut memory = alloc::vec![GenericArray::default(); usize::try_from(self.params.s_cost.get()).unwrap()]; - self.hash_with_memory(pwd, salt, &mut memory) + self.hash_internal(pwd, salt, &mut memory, None) } else { #[cfg(not(feature = "parallel"))] return Err(Error::ThreadsTooMany); #[cfg(feature = "parallel")] { - use core::num::NonZeroU32; use rayon::iter::{IntoParallelIterator, ParallelIterator}; - let mut params = self.params; - params.p_cost = NonZeroU32::new(1).unwrap(); - - (1..=self.params.p_cost.get()) + (1..=u64::from(self.params.p_cost.get())) .into_par_iter() - .map_with((params, self.secret), |(params, secret), index| { - const U32_SIZE: usize = core::mem::size_of::(); - - // expand salt if necessary - let mut salt = if salt.len() < U32_SIZE { - let mut new_salt = [0; U32_SIZE]; - new_salt[..salt.len()].copy_from_slice(salt); - new_salt.to_vec() - } else { - salt.to_vec() - }; - - // add thread index to salt - let byte = salt[..U32_SIZE].try_into().unwrap(); - salt[..U32_SIZE].copy_from_slice( - &(u32::from_le_bytes(byte).wrapping_add(index)).to_le_bytes(), - ); - Self::new(*params, *secret).hash(pwd, &salt) + .map_with((self.params, self.secret), |(params, secret), index| { + let mut memory = alloc::vec![ + GenericArray::default(); usize::try_from(params.s_cost.get()).unwrap() + ]; + // `PhantomData` doesn't implement `Sync` unless `D` does, so we build a + // new `Balloon`, which is free + Self::new(*params, *secret).hash_internal( + pwd, + salt, + &mut memory, + Some(index), + ) }) .try_reduce(GenericArray::default, |a, b| { Ok(a.into_iter().zip(b).map(|(a, b)| a ^ b).collect()) @@ -159,7 +149,7 @@ impl<'key, D: Digest> Balloon<'key, D> { /// - Users with the `alloc` feature enabled can use [`Balloon::hash`] /// to have it allocated for them. /// - `no_std` users on "heapless" targets can use an array of the [`GenericArray`] type - /// to stack allocate this buffer. + /// to stack allocate this buffer. It needs a minimum size of `s_cost`. pub fn hash_with_memory( &self, pwd: &[u8], @@ -170,59 +160,73 @@ impl<'key, D: Digest> Balloon<'key, D> { return Err(Error::ThreadsTooMany); } + self.hash_internal(pwd, salt, memory_blocks, None) + } + + fn hash_internal( + &self, + pwd: &[u8], + salt: &[u8], + memory_blocks: &mut [GenericArray], + thread_id: Option, + ) -> Result> { + // we will use `s_cost` to index arrays regularly let s_cost = usize::try_from(self.params.s_cost.get()).unwrap(); - let t_cost = usize::try_from(self.params.t_cost.get()).unwrap(); let mut digest = D::new(); // This is a direct translation of the `Balloon` from chapter 3.1. // int delta = 3 // Number of dependencies per block - const DELTA: u32 = 3; + const DELTA: u64 = 3; // int cnt = 0 // A counter (used in security proof) - let mut cnt: u32 = 0; + let mut cnt: u64 = 0; // block_t buf[s_cost]): // The main buffer - let memory_blocks = memory_blocks + let buf = memory_blocks .get_mut(..s_cost) .ok_or(Error::MemoryTooLittle)?; // Step 1. Expand input into buffer. // buf[0] = hash(cnt++, passwd, salt) digest.update(cnt.to_le_bytes()); - cnt = cnt.wrapping_add(1); + cnt += 1; digest.update(pwd); digest.update(salt); + if let Some(thread_id) = thread_id { + digest.update(thread_id.to_le_bytes()); + } + if let Some(secret) = self.secret { digest.update(secret); } - memory_blocks[0] = digest.finalize_reset(); + buf[0] = digest.finalize_reset(); // for m from 1 to s_cost-1: for m in 1..s_cost { // buf[m] = hash(cnt++, buf[m-1]) digest.update(&cnt.to_le_bytes()); - cnt = cnt.wrapping_add(1); - digest.update(&memory_blocks[m - 1]); - memory_blocks[m] = digest.finalize_reset(); + cnt += 1; + digest.update(&buf[m - 1]); + buf[m] = digest.finalize_reset(); } // Step 2. Mix buffer contents. // for t from 0 to t_cost-1: - for t in 0..t_cost { + for t in 0..u64::from(self.params.t_cost.get()) { // for m from 0 to s_cost-1: for m in 0..s_cost { // Step 2a. Hash last and current blocks. // block_t prev = buf[(m-1) mod s_cost] let prev = if m == 0 { s_cost - 1 } else { m - 1 }; - let prev = &memory_blocks[prev]; + let prev = &buf[prev]; // buf[m] = hash(cnt++, prev, buf[m]) digest.update(&cnt.to_le_bytes()); - cnt = cnt.wrapping_add(1); + cnt += 1; digest.update(prev); - digest.update(&memory_blocks[m]); - memory_blocks[m] = digest.finalize_reset(); + digest.update(&buf[m]); + buf[m] = digest.finalize_reset(); // Step 2b. Hash in pseudorandomly chosen blocks. // for i from 0 to delta-1: @@ -230,31 +234,36 @@ impl<'key, D: Digest> Balloon<'key, D> { // block_t idx_block = ints_to_block(t, m, i) // int other = to_int(hash(cnt++, salt, idx_block)) mod s_cost digest.update(&cnt.to_le_bytes()); - cnt = cnt.wrapping_add(1); + cnt += 1; digest.update(salt); - digest.update(&u32::try_from(t).unwrap().to_le_bytes()); - digest.update(&u32::try_from(m).unwrap().to_le_bytes()); + + if let Some(thread_id) = thread_id { + digest.update(thread_id.to_le_bytes()); + } + + digest.update(&t.to_le_bytes()); + digest.update(&u64::try_from(m).unwrap().to_le_bytes()); digest.update(&i.to_le_bytes()); - let other = u32::from_le_bytes( - digest.finalize_reset()[..core::mem::size_of::()] + let other = u64::from_le_bytes( + digest.finalize_reset()[..core::mem::size_of::()] .try_into() .unwrap(), - ) % self.params.s_cost.get(); + ) % u64::from(self.params.s_cost.get()); let other = usize::try_from(other).unwrap(); // buf[m] = hash(cnt++, buf[m], buf[other]) digest.update(&cnt.to_le_bytes()); - cnt = cnt.wrapping_add(1); - digest.update(&memory_blocks[m]); - digest.update(&memory_blocks[other]); - memory_blocks[m] = digest.finalize_reset(); + cnt += 1; + digest.update(&buf[m]); + digest.update(&buf[other]); + buf[m] = digest.finalize_reset(); } } } // Step 3. Extract output from buffer. // return buf[s_cost-1] - Ok(memory_blocks[s_cost - 1].clone()) + Ok(buf[s_cost - 1].clone()) } } @@ -313,7 +322,7 @@ impl PasswordHasher for Balloon<'_, D> { } } -#[cfg(all(test, feature = "alloc", feature = "password-hash"))] +#[cfg(all(test, feature = "parallel", feature = "password-hash"))] #[test] fn hash_simple_retains_configured_params() { use sha2::Sha256; From 2bc365758e78cb55f7d16a293a7ec0749389628b Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Wed, 15 Sep 2021 15:21:13 +0200 Subject: [PATCH 06/40] Adjusted defaults --- balloon/src/params.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/balloon/src/params.rs b/balloon/src/params.rs index 979b0ac2..8f886b60 100644 --- a/balloon/src/params.rs +++ b/balloon/src/params.rs @@ -26,7 +26,7 @@ impl Params { pub const DEFAULT_S_COST: u32 = 1024; /// Default number of iterations (i.e. "time"). - pub const DEFAULT_T_COST: u32 = 1; + pub const DEFAULT_T_COST: u32 = 3; /// Default degree of parallelism. pub const DEFAULT_P_COST: u32 = 1; From 688c07ae9cec186caee77c5487a1f238bcb6f0c3 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Tue, 21 Sep 2021 13:34:56 +0200 Subject: [PATCH 07/40] Calculate `other` properly with crypto-bigint --- Cargo.lock | 11 +++++++++++ balloon/Cargo.toml | 1 + balloon/src/lib.rs | 48 +++++++++++++++++++++++++++++++++++++++------- 3 files changed, 53 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 46af64e7..a6e502ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,6 +24,7 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" name = "balloon-hash" version = "0.1.0" dependencies = [ + "crypto-bigint", "digest 0.9.0", "password-hash", "rayon", @@ -159,6 +160,16 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "crypto-bigint" +version = "0.2.9" +source = "git+https://github.com/daxpedda/crypto-bigint?branch=nonzero-div-rem#d37468d38f7c20463be000fcf263a05cc4a746fd" +dependencies = [ + "generic-array", + "rand_core", + "subtle", +] + [[package]] name = "crypto-common" version = "0.1.0" diff --git a/balloon/Cargo.toml b/balloon/Cargo.toml index 6fbadd3c..6ea4f6da 100644 --- a/balloon/Cargo.toml +++ b/balloon/Cargo.toml @@ -13,6 +13,7 @@ readme = "README.md" [dependencies] digest = { version = "0.9", default-features = false } +crypto-bigint = { version = "0.2", git = "https://github.com/daxpedda/crypto-bigint", branch = "nonzero-div-rem", features = ["generic-array"] } # optional dependencies password-hash = { version = "0.3", default-features = false, optional = true } diff --git a/balloon/src/lib.rs b/balloon/src/lib.rs index ac55bb45..0b81153d 100644 --- a/balloon/src/lib.rs +++ b/balloon/src/lib.rs @@ -71,6 +71,9 @@ pub use crate::{ }; use core::convert::{TryFrom, TryInto}; use core::marker::PhantomData; +use core::mem; +use crypto_bigint::{ArrayDecoding, ArrayEncoding, NonZero}; +use digest::generic_array::typenum::Unsigned; use digest::generic_array::GenericArray; use digest::Digest; #[cfg(feature = "password-hash")] @@ -78,6 +81,7 @@ use digest::Digest; pub use password_hash::{self, PasswordHash, PasswordHasher, PasswordVerifier}; #[cfg(all(feature = "alloc", feature = "password-hash"))] use password_hash::{Decimal, Ident, ParamsString, Salt}; +use std::ops::Rem; /// Balloon context. /// @@ -86,7 +90,15 @@ use password_hash::{Decimal, Ident, ParamsString, Salt}; /// - Default set of [`Params`] to be used /// - (Optional) Secret key a.k.a. "pepper" to be used #[derive(Clone, Default)] -pub struct Balloon<'key, D: Digest> { +pub struct Balloon<'key, D: Digest> +where + GenericArray: ArrayDecoding, + as ArrayDecoding>::Output: + Rem as ArrayDecoding>::Output>>, + < as ArrayDecoding>::Output as Rem< + NonZero< as ArrayDecoding>::Output>, + >>::Output: ArrayEncoding, +{ /// Storing which hash function is used pub digest: PhantomData, /// Algorithm parameters @@ -95,7 +107,15 @@ pub struct Balloon<'key, D: Digest> { pub secret: Option<&'key [u8]>, } -impl<'key, D: Digest> Balloon<'key, D> { +impl<'key, D: Digest> Balloon<'key, D> +where + GenericArray: ArrayDecoding, + as ArrayDecoding>::Output: + Rem as ArrayDecoding>::Output>>, + < as ArrayDecoding>::Output as Rem< + NonZero< as ArrayDecoding>::Output>, + >>::Output: ArrayEncoding, +{ /// Create a new Balloon context. pub fn new(params: Params, secret: Option<&'key [u8]>) -> Self { Self { @@ -244,12 +264,18 @@ impl<'key, D: Digest> Balloon<'key, D> { digest.update(&t.to_le_bytes()); digest.update(&u64::try_from(m).unwrap().to_le_bytes()); digest.update(&i.to_le_bytes()); - let other = u64::from_le_bytes( - digest.finalize_reset()[..core::mem::size_of::()] + let s_cost = { + let mut s_cost = GenericArray::::default(); + s_cost[D::OutputSize::USIZE - mem::size_of::()..] + .copy_from_slice(&self.params.s_cost.get().to_le_bytes()); + NonZero::new(s_cost.into_bigint_le()).unwrap() + }; + let other = digest.finalize_reset().into_bigint_le() % s_cost; + let other = usize::from_le_bytes( + other.to_le_byte_array()[D::OutputSize::USIZE - mem::size_of::()..] .try_into() .unwrap(), - ) % u64::from(self.params.s_cost.get()); - let other = usize::try_from(other).unwrap(); + ); // buf[m] = hash(cnt++, buf[m], buf[other]) digest.update(&cnt.to_le_bytes()); @@ -270,7 +296,15 @@ impl<'key, D: Digest> Balloon<'key, D> { #[cfg(all(feature = "alloc", feature = "password-hash"))] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] #[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))] -impl PasswordHasher for Balloon<'_, D> { +impl PasswordHasher for Balloon<'_, D> +where + GenericArray: ArrayDecoding, + as ArrayDecoding>::Output: + Rem as ArrayDecoding>::Output>>, + < as ArrayDecoding>::Output as Rem< + NonZero< as ArrayDecoding>::Output>, + >>::Output: ArrayEncoding, +{ type Params = Params; fn hash_password<'a, S>( From f504e16c50cf064369311c398f65e7cf60fadf44 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Tue, 21 Sep 2021 13:39:37 +0200 Subject: [PATCH 08/40] Implement `ints_to_block` --- balloon/src/lib.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/balloon/src/lib.rs b/balloon/src/lib.rs index 0b81153d..216d91eb 100644 --- a/balloon/src/lib.rs +++ b/balloon/src/lib.rs @@ -252,6 +252,11 @@ where // for i from 0 to delta-1: for i in 0..DELTA { // block_t idx_block = ints_to_block(t, m, i) + digest.update(&t.to_le_bytes()); + digest.update(&u64::try_from(m).unwrap().to_le_bytes()); + digest.update(&i.to_le_bytes()); + let idx_block = digest.finalize_reset(); + // int other = to_int(hash(cnt++, salt, idx_block)) mod s_cost digest.update(&cnt.to_le_bytes()); cnt += 1; @@ -261,9 +266,7 @@ where digest.update(thread_id.to_le_bytes()); } - digest.update(&t.to_le_bytes()); - digest.update(&u64::try_from(m).unwrap().to_le_bytes()); - digest.update(&i.to_le_bytes()); + digest.update(idx_block); let s_cost = { let mut s_cost = GenericArray::::default(); s_cost[D::OutputSize::USIZE - mem::size_of::()..] From 59f5172340b1f92bc9cf22f63f3a591ca08b69d6 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Tue, 21 Sep 2021 14:01:14 +0200 Subject: [PATCH 09/40] Fix no_std --- balloon/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/balloon/src/lib.rs b/balloon/src/lib.rs index 216d91eb..86f75532 100644 --- a/balloon/src/lib.rs +++ b/balloon/src/lib.rs @@ -72,6 +72,7 @@ pub use crate::{ use core::convert::{TryFrom, TryInto}; use core::marker::PhantomData; use core::mem; +use core::ops::Rem; use crypto_bigint::{ArrayDecoding, ArrayEncoding, NonZero}; use digest::generic_array::typenum::Unsigned; use digest::generic_array::GenericArray; @@ -81,7 +82,6 @@ use digest::Digest; pub use password_hash::{self, PasswordHash, PasswordHasher, PasswordVerifier}; #[cfg(all(feature = "alloc", feature = "password-hash"))] use password_hash::{Decimal, Ident, ParamsString, Salt}; -use std::ops::Rem; /// Balloon context. /// From 0869c784046f4e75008b09e6ba22e082d3e88a01 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Tue, 21 Sep 2021 14:19:43 +0200 Subject: [PATCH 10/40] Fix indexing --- balloon/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/balloon/src/lib.rs b/balloon/src/lib.rs index 86f75532..93921990 100644 --- a/balloon/src/lib.rs +++ b/balloon/src/lib.rs @@ -269,13 +269,13 @@ where digest.update(idx_block); let s_cost = { let mut s_cost = GenericArray::::default(); - s_cost[D::OutputSize::USIZE - mem::size_of::()..] + s_cost[..mem::size_of::()] .copy_from_slice(&self.params.s_cost.get().to_le_bytes()); NonZero::new(s_cost.into_bigint_le()).unwrap() }; let other = digest.finalize_reset().into_bigint_le() % s_cost; let other = usize::from_le_bytes( - other.to_le_byte_array()[D::OutputSize::USIZE - mem::size_of::()..] + other.to_le_byte_array()[..mem::size_of::()] .try_into() .unwrap(), ); From 711098e1fed0ca32786157ff1a569a1a4b916744 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Tue, 21 Sep 2021 14:20:12 +0200 Subject: [PATCH 11/40] Switch crypto-bigint to master --- Cargo.lock | 2 +- balloon/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a6e502ae..6cf9bb65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -163,7 +163,7 @@ dependencies = [ [[package]] name = "crypto-bigint" version = "0.2.9" -source = "git+https://github.com/daxpedda/crypto-bigint?branch=nonzero-div-rem#d37468d38f7c20463be000fcf263a05cc4a746fd" +source = "git+https://github.com/RustCrypto/crypto-bigint#193a2ae82a536e85f01b123436bdfe4f7892bb7a" dependencies = [ "generic-array", "rand_core", diff --git a/balloon/Cargo.toml b/balloon/Cargo.toml index 6ea4f6da..213aa3a0 100644 --- a/balloon/Cargo.toml +++ b/balloon/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" [dependencies] digest = { version = "0.9", default-features = false } -crypto-bigint = { version = "0.2", git = "https://github.com/daxpedda/crypto-bigint", branch = "nonzero-div-rem", features = ["generic-array"] } +crypto-bigint = { version = "0.2", git = "https://github.com/RustCrypto/crypto-bigint", features = ["generic-array"] } # optional dependencies password-hash = { version = "0.3", default-features = false, optional = true } From 744e1fd2986e9d86301f49b32da8687792e13755 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Tue, 21 Sep 2021 14:24:17 +0200 Subject: [PATCH 12/40] Fix Clippy --- balloon/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/balloon/src/lib.rs b/balloon/src/lib.rs index 93921990..78782732 100644 --- a/balloon/src/lib.rs +++ b/balloon/src/lib.rs @@ -74,7 +74,6 @@ use core::marker::PhantomData; use core::mem; use core::ops::Rem; use crypto_bigint::{ArrayDecoding, ArrayEncoding, NonZero}; -use digest::generic_array::typenum::Unsigned; use digest::generic_array::GenericArray; use digest::Digest; #[cfg(feature = "password-hash")] From 2a23622bcb85d7722c1b2d2677f8d0229d236c8a Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Tue, 21 Sep 2021 14:55:43 +0200 Subject: [PATCH 13/40] Change secret handling --- balloon/src/lib.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/balloon/src/lib.rs b/balloon/src/lib.rs index 78782732..7baf78c3 100644 --- a/balloon/src/lib.rs +++ b/balloon/src/lib.rs @@ -211,14 +211,14 @@ where digest.update(pwd); digest.update(salt); - if let Some(thread_id) = thread_id { - digest.update(thread_id.to_le_bytes()); - } - if let Some(secret) = self.secret { digest.update(secret); } + if let Some(thread_id) = thread_id { + digest.update(thread_id.to_le_bytes()); + } + buf[0] = digest.finalize_reset(); // for m from 1 to s_cost-1: @@ -261,6 +261,10 @@ where cnt += 1; digest.update(salt); + if let Some(secret) = self.secret { + digest.update(secret); + } + if let Some(thread_id) = thread_id { digest.update(thread_id.to_le_bytes()); } From d8bc056eb19dd37837e2184c4d00acb2d5212781 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Tue, 21 Sep 2021 15:05:33 +0200 Subject: [PATCH 14/40] Small improvements --- balloon/src/lib.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/balloon/src/lib.rs b/balloon/src/lib.rs index 7baf78c3..4e383cb1 100644 --- a/balloon/src/lib.rs +++ b/balloon/src/lib.rs @@ -237,8 +237,11 @@ where for m in 0..s_cost { // Step 2a. Hash last and current blocks. // block_t prev = buf[(m-1) mod s_cost] - let prev = if m == 0 { s_cost - 1 } else { m - 1 }; - let prev = &buf[prev]; + let prev = if m == 0 { + buf.last().unwrap() + } else { + &buf[m - 1] + }; // buf[m] = hash(cnt++, prev, buf[m]) digest.update(&cnt.to_le_bytes()); @@ -295,7 +298,7 @@ where // Step 3. Extract output from buffer. // return buf[s_cost-1] - Ok(buf[s_cost - 1].clone()) + Ok(buf.last().unwrap().clone()) } } From 8e65bef97c9013fa890b388dcb96463466f3cdcc Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Tue, 21 Sep 2021 18:35:15 +0200 Subject: [PATCH 15/40] Use Python implementation for testing --- Cargo.lock | 122 +++++++++++++++++++++++++++++++++++++++++++++ balloon/Cargo.toml | 1 + balloon/src/lib.rs | 34 ++++++++++++- 3 files changed, 156 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 6cf9bb65..049acd71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,7 @@ version = "0.1.0" dependencies = [ "crypto-bigint", "digest 0.9.0", + "nachonavarro-balloon", "password-hash", "rayon", "sha2 0.9.8", @@ -48,6 +49,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "blake2" version = "0.10.0" @@ -251,6 +258,15 @@ dependencies = [ "digest 0.10.0", ] +[[package]] +name = "instant" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d" +dependencies = [ + "cfg-if", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -263,6 +279,15 @@ version = "0.2.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f98a04dce437184842841303488f70d0188c5f51437d2a834dc097eafa909a01" +[[package]] +name = "lock_api" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +dependencies = [ + "scopeguard", +] + [[package]] name = "memoffset" version = "0.6.5" @@ -272,6 +297,14 @@ dependencies = [ "autocfg", ] +[[package]] +name = "nachonavarro-balloon" +version = "0.0.0" +source = "git+https://github.com/daxpedda/nachonavarro-balloon#3ee06fa415fa76b91723bb1df681c25ab55568e4" +dependencies = [ + "pyo3", +] + [[package]] name = "num_cpus" version = "1.13.0" @@ -282,12 +315,43 @@ dependencies = [ "libc", ] +[[package]] +name = "once_cell" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" + [[package]] name = "opaque-debug" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + [[package]] name = "password-hash" version = "0.3.2" @@ -319,6 +383,27 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" +[[package]] +name = "pyo3" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35100f9347670a566a67aa623369293703322bb9db77d99d7df7313b575ae0c8" +dependencies = [ + "cfg-if", + "libc", + "parking_lot", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-build-config" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d12961738cacbd7f91b7c43bc25cfeeaa2698ad07a04b3be0aa88b950865738f" +dependencies = [ + "once_cell", +] + [[package]] name = "rand" version = "0.8.4" @@ -384,6 +469,15 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + [[package]] name = "salsa20" version = "0.9.0" @@ -454,6 +548,12 @@ dependencies = [ "digest 0.10.0", ] +[[package]] +name = "smallvec" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" + [[package]] name = "streebog" version = "0.10.0" @@ -487,6 +587,28 @@ version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "zeroize" version = "1.4.3" diff --git a/balloon/Cargo.toml b/balloon/Cargo.toml index 213aa3a0..55fd8cc4 100644 --- a/balloon/Cargo.toml +++ b/balloon/Cargo.toml @@ -20,6 +20,7 @@ password-hash = { version = "0.3", default-features = false, optional = true } rayon = { version = "1", optional = true } [dev-dependencies] +nachonavarro-balloon = { git = "https://github.com/daxpedda/nachonavarro-balloon" } sha2 = "0.9" [features] diff --git a/balloon/src/lib.rs b/balloon/src/lib.rs index 4e383cb1..a557029b 100644 --- a/balloon/src/lib.rs +++ b/balloon/src/lib.rs @@ -365,7 +365,7 @@ where } } -#[cfg(all(test, feature = "parallel", feature = "password-hash"))] +#[cfg(all(feature = "parallel", feature = "password-hash"))] #[test] fn hash_simple_retains_configured_params() { use sha2::Sha256; @@ -399,3 +399,35 @@ fn hash_simple_retains_configured_params() { ); } } + +#[cfg(feature = "alloc")] +#[test] +fn nachonavarro() { + let balloon = Balloon::::new(Params::default(), None); + + const TEST_VECTOR: [u8; 32] = [ + 113, 96, 67, 223, 247, 119, 180, 74, 167, 184, 141, 203, 171, 18, 192, 120, 171, 236, 250, + 201, 210, 137, 197, 181, 25, 89, 103, 170, 99, 68, 13, 251, + ]; + + assert_eq!( + TEST_VECTOR, + balloon + .hash(b"hunter42", b"examplesalt") + .unwrap() + .as_slice() + ); + + assert_eq!( + TEST_VECTOR, + nachonavarro_balloon::balloon( + "hunter42", + "examplesalt", + balloon.params.s_cost.get(), + balloon.params.t_cost.get(), + 3 + ) + .unwrap() + .as_slice() + ); +} From bde7709d377d8f381e95376ba77aa19bcbf7bc64 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Tue, 21 Sep 2021 18:41:29 +0200 Subject: [PATCH 16/40] Re-introduce `default-features = false` for crypto-bigint --- Cargo.lock | 3 +-- balloon/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 049acd71..1e0d2691 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -170,10 +170,9 @@ dependencies = [ [[package]] name = "crypto-bigint" version = "0.2.9" -source = "git+https://github.com/RustCrypto/crypto-bigint#193a2ae82a536e85f01b123436bdfe4f7892bb7a" +source = "git+https://github.com/RustCrypto/crypto-bigint#b8c8832153aad97782638cb7027af54b0cca9de6" dependencies = [ "generic-array", - "rand_core", "subtle", ] diff --git a/balloon/Cargo.toml b/balloon/Cargo.toml index 55fd8cc4..406066e4 100644 --- a/balloon/Cargo.toml +++ b/balloon/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" [dependencies] digest = { version = "0.9", default-features = false } -crypto-bigint = { version = "0.2", git = "https://github.com/RustCrypto/crypto-bigint", features = ["generic-array"] } +crypto-bigint = { version = "0.2", git = "https://github.com/RustCrypto/crypto-bigint", default-features = false, features = ["generic-array"] } # optional dependencies password-hash = { version = "0.3", default-features = false, optional = true } From a9f48a8091b4d0a3cfd15012a71b941e86991b1e Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Tue, 21 Sep 2021 18:44:27 +0200 Subject: [PATCH 17/40] Fix build --- balloon/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/balloon/src/lib.rs b/balloon/src/lib.rs index a557029b..4646b565 100644 --- a/balloon/src/lib.rs +++ b/balloon/src/lib.rs @@ -277,9 +277,9 @@ where let mut s_cost = GenericArray::::default(); s_cost[..mem::size_of::()] .copy_from_slice(&self.params.s_cost.get().to_le_bytes()); - NonZero::new(s_cost.into_bigint_le()).unwrap() + NonZero::new(s_cost.into_uint_le()).unwrap() }; - let other = digest.finalize_reset().into_bigint_le() % s_cost; + let other = digest.finalize_reset().into_uint_le() % s_cost; let other = usize::from_le_bytes( other.to_le_byte_array()[..mem::size_of::()] .try_into() From 116070f687ec8c2a76f4a20d284895a966e60474 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Tue, 21 Sep 2021 21:28:02 +0200 Subject: [PATCH 18/40] Update `crypto-bigint`, remove constraints --- Cargo.lock | 2 +- balloon/src/lib.rs | 16 ---------------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1e0d2691..d44fd6c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -170,7 +170,7 @@ dependencies = [ [[package]] name = "crypto-bigint" version = "0.2.9" -source = "git+https://github.com/RustCrypto/crypto-bigint#b8c8832153aad97782638cb7027af54b0cca9de6" +source = "git+https://github.com/RustCrypto/crypto-bigint#66795aed2bf33dce565cb51e6169c66cf39010f0" dependencies = [ "generic-array", "subtle", diff --git a/balloon/src/lib.rs b/balloon/src/lib.rs index 4646b565..34786649 100644 --- a/balloon/src/lib.rs +++ b/balloon/src/lib.rs @@ -72,7 +72,6 @@ pub use crate::{ use core::convert::{TryFrom, TryInto}; use core::marker::PhantomData; use core::mem; -use core::ops::Rem; use crypto_bigint::{ArrayDecoding, ArrayEncoding, NonZero}; use digest::generic_array::GenericArray; use digest::Digest; @@ -92,11 +91,6 @@ use password_hash::{Decimal, Ident, ParamsString, Salt}; pub struct Balloon<'key, D: Digest> where GenericArray: ArrayDecoding, - as ArrayDecoding>::Output: - Rem as ArrayDecoding>::Output>>, - < as ArrayDecoding>::Output as Rem< - NonZero< as ArrayDecoding>::Output>, - >>::Output: ArrayEncoding, { /// Storing which hash function is used pub digest: PhantomData, @@ -109,11 +103,6 @@ where impl<'key, D: Digest> Balloon<'key, D> where GenericArray: ArrayDecoding, - as ArrayDecoding>::Output: - Rem as ArrayDecoding>::Output>>, - < as ArrayDecoding>::Output as Rem< - NonZero< as ArrayDecoding>::Output>, - >>::Output: ArrayEncoding, { /// Create a new Balloon context. pub fn new(params: Params, secret: Option<&'key [u8]>) -> Self { @@ -308,11 +297,6 @@ where impl PasswordHasher for Balloon<'_, D> where GenericArray: ArrayDecoding, - as ArrayDecoding>::Output: - Rem as ArrayDecoding>::Output>>, - < as ArrayDecoding>::Output as Rem< - NonZero< as ArrayDecoding>::Output>, - >>::Output: ArrayEncoding, { type Params = Params; From b23f5ca823900c4f5981dc5533195d16462c2707 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Tue, 21 Sep 2021 21:28:20 +0200 Subject: [PATCH 19/40] Add test vectors --- balloon/src/lib.rs | 32 ------------ balloon/tests/compatibility.rs | 95 ++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 32 deletions(-) create mode 100644 balloon/tests/compatibility.rs diff --git a/balloon/src/lib.rs b/balloon/src/lib.rs index 34786649..c51e39c7 100644 --- a/balloon/src/lib.rs +++ b/balloon/src/lib.rs @@ -383,35 +383,3 @@ fn hash_simple_retains_configured_params() { ); } } - -#[cfg(feature = "alloc")] -#[test] -fn nachonavarro() { - let balloon = Balloon::::new(Params::default(), None); - - const TEST_VECTOR: [u8; 32] = [ - 113, 96, 67, 223, 247, 119, 180, 74, 167, 184, 141, 203, 171, 18, 192, 120, 171, 236, 250, - 201, 210, 137, 197, 181, 25, 89, 103, 170, 99, 68, 13, 251, - ]; - - assert_eq!( - TEST_VECTOR, - balloon - .hash(b"hunter42", b"examplesalt") - .unwrap() - .as_slice() - ); - - assert_eq!( - TEST_VECTOR, - nachonavarro_balloon::balloon( - "hunter42", - "examplesalt", - balloon.params.s_cost.get(), - balloon.params.t_cost.get(), - 3 - ) - .unwrap() - .as_slice() - ); -} diff --git a/balloon/tests/compatibility.rs b/balloon/tests/compatibility.rs new file mode 100644 index 00000000..4953c69e --- /dev/null +++ b/balloon/tests/compatibility.rs @@ -0,0 +1,95 @@ +#![cfg(feature = "alloc")] + +use balloon_hash::{Balloon, Params}; + +struct TestVector { + password: &'static [u8], + salt: &'static [u8], + s_cost: u32, + t_cost: u32, + output: [u8; 32], +} + +const TEST_VECTORS: &[TestVector] = &[ + TestVector { + password: b"hunter42", + salt: b"examplesalt", + s_cost: 1024, + t_cost: 3, + output: [ + 113, 96, 67, 223, 247, 119, 180, 74, 167, 184, 141, 203, 171, 18, 192, 120, 171, 236, + 250, 201, 210, 137, 197, 181, 25, 89, 103, 170, 99, 68, 13, 251, + ], + }, + TestVector { + password: b"", + salt: b"salt", + s_cost: 3, + t_cost: 3, + output: [ + 95, 2, 248, 32, 111, 156, 210, 18, 72, 92, 107, 223, 133, 82, 123, 105, 137, 86, 112, + 26, 208, 133, 33, 6, 249, 75, 148, 238, 148, 87, 115, 120, + ], + }, + TestVector { + password: b"password", + salt: b"", + s_cost: 3, + t_cost: 3, + output: [ + 32, 170, 153, 215, 254, 63, 77, 244, 189, 152, 198, 85, 197, 72, 14, 201, 139, 20, 49, + 7, 163, 49, 253, 73, 29, 237, 168, 133, 196, 214, 166, 204, + ], + }, + TestVector { + password: b"\0", + salt: b"\0", + s_cost: 3, + t_cost: 3, + output: [ + 79, 199, 227, 2, 255, 162, 154, 224, 234, 195, 17, 102, 206, 231, 165, 82, 209, 215, + 17, 53, 244, 224, 218, 102, 72, 111, 182, 138, 116, 155, 115, 164, + ], + }, + TestVector { + password: b"password", + salt: b"salt", + s_cost: 1, + t_cost: 1, + output: [ + 238, 253, 164, 168, 167, 91, 70, 31, 163, 137, 193, 220, 250, 243, 233, 223, 172, 188, + 38, 248, 31, 34, 230, 242, 128, 209, 92, 193, 140, 65, 117, 69, + ], + }, +]; + +#[test] +fn test() { + for test_vector in TEST_VECTORS { + let balloon = Balloon::::new( + Params::new(test_vector.s_cost, test_vector.t_cost, 1).unwrap(), + None, + ); + + assert_eq!( + test_vector.output, + balloon + .hash(test_vector.password, test_vector.salt) + .unwrap() + .as_slice() + ); + + assert_eq!( + test_vector.output, + nachonavarro_balloon::balloon( + core::str::from_utf8(test_vector.password).unwrap(), + core::str::from_utf8(test_vector.salt).unwrap(), + test_vector.s_cost, + test_vector.t_cost, + 3 + ) + .unwrap() + .as_slice() + ); + } +} From 82252b60c65f2e3f007cd537c67bd1d948846a08 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Tue, 21 Sep 2021 23:37:50 +0200 Subject: [PATCH 20/40] Update crypto-bigint to 0.2.10 --- Cargo.lock | 5 +++-- balloon/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d44fd6c1..0eaa16c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -169,8 +169,9 @@ dependencies = [ [[package]] name = "crypto-bigint" -version = "0.2.9" -source = "git+https://github.com/RustCrypto/crypto-bigint#66795aed2bf33dce565cb51e6169c66cf39010f0" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d12477e115c0d570c12a2dfd859f80b55b60ddb5075df210d3af06d133a69f45" dependencies = [ "generic-array", "subtle", diff --git a/balloon/Cargo.toml b/balloon/Cargo.toml index 406066e4..e3a7c94c 100644 --- a/balloon/Cargo.toml +++ b/balloon/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" [dependencies] digest = { version = "0.9", default-features = false } -crypto-bigint = { version = "0.2", git = "https://github.com/RustCrypto/crypto-bigint", default-features = false, features = ["generic-array"] } +crypto-bigint = { version = "0.2.10", default-features = false, features = ["generic-array"] } # optional dependencies password-hash = { version = "0.3", default-features = false, optional = true } From e0f72e03b5006f73b5a2d18ec129cdd6ab159b29 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Tue, 19 Oct 2021 13:19:17 +0200 Subject: [PATCH 21/40] Parrallel implementation without rayon --- balloon/src/error.rs | 4 -- balloon/src/lib.rs | 89 +++++++++++++++++++++++++------------------- 2 files changed, 51 insertions(+), 42 deletions(-) diff --git a/balloon/src/error.rs b/balloon/src/error.rs index d3eedb52..e7452ef8 100644 --- a/balloon/src/error.rs +++ b/balloon/src/error.rs @@ -15,8 +15,6 @@ pub enum Error { MemoryTooLittle, /// Not enough threads. ThreadsTooFew, - /// Too many threads. - ThreadsTooMany, /// Time cost is too small. TimeTooSmall, } @@ -26,7 +24,6 @@ impl fmt::Display for Error { f.write_str(match self { Error::MemoryTooLittle => "memory cost is too small", Error::ThreadsTooFew => "not enough threads", - Error::ThreadsTooMany => "too many threads", Error::TimeTooSmall => "time cost is too small", }) } @@ -39,7 +36,6 @@ impl From for password_hash::Error { match err { Error::MemoryTooLittle => InvalidValue::TooShort.param_error(), Error::ThreadsTooFew => InvalidValue::TooShort.param_error(), - Error::ThreadsTooMany => InvalidValue::TooLong.param_error(), Error::TimeTooSmall => InvalidValue::TooShort.param_error(), } } diff --git a/balloon/src/lib.rs b/balloon/src/lib.rs index c51e39c7..96720b44 100644 --- a/balloon/src/lib.rs +++ b/balloon/src/lib.rs @@ -117,36 +117,8 @@ where #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] pub fn hash(&self, pwd: &[u8], salt: &[u8]) -> Result> { - if self.params.p_cost.get() == 1 { - let mut memory = alloc::vec![GenericArray::default(); usize::try_from(self.params.s_cost.get()).unwrap()]; - self.hash_internal(pwd, salt, &mut memory, None) - } else { - #[cfg(not(feature = "parallel"))] - return Err(Error::ThreadsTooMany); - #[cfg(feature = "parallel")] - { - use rayon::iter::{IntoParallelIterator, ParallelIterator}; - - (1..=u64::from(self.params.p_cost.get())) - .into_par_iter() - .map_with((self.params, self.secret), |(params, secret), index| { - let mut memory = alloc::vec![ - GenericArray::default(); usize::try_from(params.s_cost.get()).unwrap() - ]; - // `PhantomData` doesn't implement `Sync` unless `D` does, so we build a - // new `Balloon`, which is free - Self::new(*params, *secret).hash_internal( - pwd, - salt, - &mut memory, - Some(index), - ) - }) - .try_reduce(GenericArray::default, |a, b| { - Ok(a.into_iter().zip(b).map(|(a, b)| a ^ b).collect()) - }) - } - } + let mut memory = alloc::vec![GenericArray::default(); (self.params.s_cost.get() * self.params.p_cost.get()) as usize]; + self.hash_with_memory(pwd, salt, &mut memory) } /// Hash a password and associated parameters. @@ -157,18 +129,59 @@ where /// - Users with the `alloc` feature enabled can use [`Balloon::hash`] /// to have it allocated for them. /// - `no_std` users on "heapless" targets can use an array of the [`GenericArray`] type - /// to stack allocate this buffer. It needs a minimum size of `s_cost`. + /// to stack allocate this buffer. It needs a minimum size of `s_cost * p_cost`. pub fn hash_with_memory( &self, pwd: &[u8], salt: &[u8], memory_blocks: &mut [GenericArray], ) -> Result> { - if self.params.p_cost.get() > 1 { - return Err(Error::ThreadsTooMany); - } + if self.params.p_cost.get() == 1 { + self.hash_internal(pwd, salt, memory_blocks, None) + } else { + if memory_blocks.len() < (self.params.s_cost.get() * self.params.p_cost.get()) as usize + { + return Err(Error::MemoryTooLittle); + } - self.hash_internal(pwd, salt, memory_blocks, None) + #[cfg(not(feature = "parallel"))] + { + let mut result = GenericArray::default(); + + for (thread, memory) in (1..=u64::from(self.params.p_cost.get())) + .zip(memory_blocks.chunks_exact_mut(self.params.s_cost.get() as usize)) + { + let hash = self.hash_internal(pwd, salt, memory, Some(thread))?; + result = result.into_iter().zip(hash).map(|(a, b)| a ^ b).collect(); + } + + Ok(result) + } + #[cfg(feature = "parallel")] + { + use rayon::iter::{ParallelBridge, ParallelIterator}; + + (1..=u64::from(self.params.p_cost.get())) + .zip(memory_blocks.chunks_exact_mut(self.params.s_cost.get() as usize)) + .par_bridge() + .map_with( + (self.params, self.secret), + |(params, secret), (thread, memory)| { + // `PhantomData` doesn't implement `Sync` unless `D` does, so we build a + // new `Balloon`, which is free + Self::new(*params, *secret).hash_internal( + pwd, + salt, + memory, + Some(thread), + ) + }, + ) + .try_reduce(GenericArray::default, |a, b| { + Ok(a.into_iter().zip(b).map(|(a, b)| a ^ b).collect()) + }) + } + } } fn hash_internal( @@ -179,7 +192,7 @@ where thread_id: Option, ) -> Result> { // we will use `s_cost` to index arrays regularly - let s_cost = usize::try_from(self.params.s_cost.get()).unwrap(); + let s_cost = self.params.s_cost.get() as usize; let mut digest = D::new(); @@ -244,7 +257,7 @@ where for i in 0..DELTA { // block_t idx_block = ints_to_block(t, m, i) digest.update(&t.to_le_bytes()); - digest.update(&u64::try_from(m).unwrap().to_le_bytes()); + digest.update(&(m as u64).to_le_bytes()); digest.update(&i.to_le_bytes()); let idx_block = digest.finalize_reset(); @@ -349,7 +362,7 @@ where } } -#[cfg(all(feature = "parallel", feature = "password-hash"))] +#[cfg(feature = "password-hash")] #[test] fn hash_simple_retains_configured_params() { use sha2::Sha256; From 658800f6f8b0bcb4dafb0c0b731a165b72a380d5 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Tue, 19 Oct 2021 13:23:02 +0200 Subject: [PATCH 22/40] Fix Clippy --- balloon/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/balloon/src/lib.rs b/balloon/src/lib.rs index 96720b44..5442db2e 100644 --- a/balloon/src/lib.rs +++ b/balloon/src/lib.rs @@ -69,7 +69,7 @@ pub use crate::{ error::{Error, Result}, params::Params, }; -use core::convert::{TryFrom, TryInto}; +use core::convert::TryInto; use core::marker::PhantomData; use core::mem; use crypto_bigint::{ArrayDecoding, ArrayEncoding, NonZero}; @@ -321,6 +321,8 @@ where where S: AsRef + ?Sized, { + use core::convert::TryFrom; + let salt = Salt::try_from(salt.as_ref())?; let mut salt_arr = [0u8; 64]; let salt_bytes = salt.b64_decode(&mut salt_arr)?; From 4177cc1bb3e2fd8af0048695bffebdd909829dc6 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Tue, 19 Oct 2021 13:32:13 +0200 Subject: [PATCH 23/40] Fix memory requirement --- balloon/src/lib.rs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/balloon/src/lib.rs b/balloon/src/lib.rs index 5442db2e..ea6fda25 100644 --- a/balloon/src/lib.rs +++ b/balloon/src/lib.rs @@ -117,6 +117,9 @@ where #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] pub fn hash(&self, pwd: &[u8], salt: &[u8]) -> Result> { + #[cfg(not(feature = "parallel"))] + let mut memory = alloc::vec![GenericArray::default(); self.params.s_cost.get() as usize]; + #[cfg(feature = "parallel")] let mut memory = alloc::vec![GenericArray::default(); (self.params.s_cost.get() * self.params.p_cost.get()) as usize]; self.hash_with_memory(pwd, salt, &mut memory) } @@ -129,7 +132,8 @@ where /// - Users with the `alloc` feature enabled can use [`Balloon::hash`] /// to have it allocated for them. /// - `no_std` users on "heapless" targets can use an array of the [`GenericArray`] type - /// to stack allocate this buffer. It needs a minimum size of `s_cost * p_cost`. + /// to stack allocate this buffer. It needs a minimum size of `s_cost` or `s_cost * p_cost` + /// with the `parallel` feature enabled. pub fn hash_with_memory( &self, pwd: &[u8], @@ -139,19 +143,12 @@ where if self.params.p_cost.get() == 1 { self.hash_internal(pwd, salt, memory_blocks, None) } else { - if memory_blocks.len() < (self.params.s_cost.get() * self.params.p_cost.get()) as usize - { - return Err(Error::MemoryTooLittle); - } - #[cfg(not(feature = "parallel"))] { let mut result = GenericArray::default(); - for (thread, memory) in (1..=u64::from(self.params.p_cost.get())) - .zip(memory_blocks.chunks_exact_mut(self.params.s_cost.get() as usize)) - { - let hash = self.hash_internal(pwd, salt, memory, Some(thread))?; + for thread in 1..=u64::from(self.params.p_cost.get()) { + let hash = self.hash_internal(pwd, salt, memory_blocks, Some(thread))?; result = result.into_iter().zip(hash).map(|(a, b)| a ^ b).collect(); } @@ -161,6 +158,12 @@ where { use rayon::iter::{ParallelBridge, ParallelIterator}; + if memory_blocks.len() + < (self.params.s_cost.get() * self.params.p_cost.get()) as usize + { + return Err(Error::MemoryTooLittle); + } + (1..=u64::from(self.params.p_cost.get())) .zip(memory_blocks.chunks_exact_mut(self.params.s_cost.get() as usize)) .par_bridge() From c4814eb1e26456459f4bbb7f792226ec026864ab Mon Sep 17 00:00:00 2001 From: daxpedda Date: Thu, 4 Nov 2021 14:37:41 +0100 Subject: [PATCH 24/40] Update dependency location --- Cargo.lock | 10 +++++----- balloon/Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0eaa16c2..cf27bc24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -300,7 +300,7 @@ dependencies = [ [[package]] name = "nachonavarro-balloon" version = "0.0.0" -source = "git+https://github.com/daxpedda/nachonavarro-balloon#3ee06fa415fa76b91723bb1df681c25ab55568e4" +source = "git+https://github.com/khonsulabs/nachonavarro-balloon#e4de7a46b20a0141aa1ac97f169c7cee55659d4e" dependencies = [ "pyo3", ] @@ -385,9 +385,9 @@ checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" [[package]] name = "pyo3" -version = "0.14.5" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35100f9347670a566a67aa623369293703322bb9db77d99d7df7313b575ae0c8" +checksum = "64664505ce285a59b8b7e940fbe54ad65b1758a0810eddc5bc26df6f6ec8c557" dependencies = [ "cfg-if", "libc", @@ -397,9 +397,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.14.5" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d12961738cacbd7f91b7c43bc25cfeeaa2698ad07a04b3be0aa88b950865738f" +checksum = "5f1e4a72de84cdcd69f62211b62f62753d0c11b7b5715f3467f3754dab22a7ca" dependencies = [ "once_cell", ] diff --git a/balloon/Cargo.toml b/balloon/Cargo.toml index e3a7c94c..b9e1d3b9 100644 --- a/balloon/Cargo.toml +++ b/balloon/Cargo.toml @@ -20,7 +20,7 @@ password-hash = { version = "0.3", default-features = false, optional = true } rayon = { version = "1", optional = true } [dev-dependencies] -nachonavarro-balloon = { git = "https://github.com/daxpedda/nachonavarro-balloon" } +nachonavarro-balloon = { git = "https://github.com/khonsulabs/nachonavarro-balloon" } sha2 = "0.9" [features] From 8c95d961166874bd25561877a916de657232b39b Mon Sep 17 00:00:00 2001 From: daxpedda Date: Thu, 4 Nov 2021 19:34:16 +0100 Subject: [PATCH 25/40] Move nachonavarro-balloon to password-hashes --- .gitmodules | 3 + Cargo.lock | 126 ++++++++++++++++++++++++++++++--- balloon/Cargo.toml | 2 +- balloon/balloon-hashing | 1 + balloon/tests/compatibility.rs | 27 +++++++ 5 files changed, 149 insertions(+), 10 deletions(-) create mode 100644 .gitmodules create mode 160000 balloon/balloon-hashing diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..e99c3ce5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "balloon/balloon-hashing"] + path = balloon/balloon-hashing + url = https://github.com/nachonavarro/balloon-hashing diff --git a/Cargo.lock b/Cargo.lock index cf27bc24..1de42257 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,8 +26,8 @@ version = "0.1.0" dependencies = [ "crypto-bigint", "digest 0.9.0", - "nachonavarro-balloon", "password-hash", + "pyo3", "rayon", "sha2 0.9.8", ] @@ -258,6 +258,29 @@ dependencies = [ "digest 0.10.0", ] +[[package]] +name = "indoc" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47741a8bc60fb26eb8d6e0238bbb26d8575ff623fdc97b1a2c00c050b9684ed8" +dependencies = [ + "indoc-impl", + "proc-macro-hack", +] + +[[package]] +name = "indoc-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce046d161f000fffde5f432a0d034d0341dc152643b2598ed5bfce44c4f3a8f0" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", + "unindent", +] + [[package]] name = "instant" version = "0.1.10" @@ -297,14 +320,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "nachonavarro-balloon" -version = "0.0.0" -source = "git+https://github.com/khonsulabs/nachonavarro-balloon#e4de7a46b20a0141aa1ac97f169c7cee55659d4e" -dependencies = [ - "pyo3", -] - [[package]] name = "num_cpus" version = "1.13.0" @@ -363,6 +378,25 @@ dependencies = [ "subtle", ] +[[package]] +name = "paste" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" +dependencies = [ + "paste-impl", + "proc-macro-hack", +] + +[[package]] +name = "paste-impl" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" +dependencies = [ + "proc-macro-hack", +] + [[package]] name = "pbkdf2" version = "0.10.0" @@ -383,6 +417,21 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro2" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" +dependencies = [ + "unicode-xid", +] + [[package]] name = "pyo3" version = "0.15.0" @@ -390,9 +439,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64664505ce285a59b8b7e940fbe54ad65b1758a0810eddc5bc26df6f6ec8c557" dependencies = [ "cfg-if", + "indoc", "libc", "parking_lot", + "paste", "pyo3-build-config", + "pyo3-macros", + "unindent", ] [[package]] @@ -404,6 +457,38 @@ dependencies = [ "once_cell", ] +[[package]] +name = "pyo3-macros" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "244f21d0a3887a9c02018b94e3b78d693dc7eca5c56839b7796a499cc364deb4" +dependencies = [ + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d3d18ac41d05199bb82645d56e39f8c8b4909a0137c6f2640f03685b29f672" +dependencies = [ + "proc-macro2", + "pyo3-build-config", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +dependencies = [ + "proc-macro2", +] + [[package]] name = "rand" version = "0.8.4" @@ -569,12 +654,35 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +[[package]] +name = "syn" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "typenum" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "unindent" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f14ee04d9415b52b3aeab06258a3f07093182b88ba0f9b8d203f211a7a7d41c7" + [[package]] name = "version_check" version = "0.9.3" diff --git a/balloon/Cargo.toml b/balloon/Cargo.toml index b9e1d3b9..817702de 100644 --- a/balloon/Cargo.toml +++ b/balloon/Cargo.toml @@ -20,7 +20,7 @@ password-hash = { version = "0.3", default-features = false, optional = true } rayon = { version = "1", optional = true } [dev-dependencies] -nachonavarro-balloon = { git = "https://github.com/khonsulabs/nachonavarro-balloon" } +pyo3 = "0.15" sha2 = "0.9" [features] diff --git a/balloon/balloon-hashing b/balloon/balloon-hashing new file mode 160000 index 00000000..7a895239 --- /dev/null +++ b/balloon/balloon-hashing @@ -0,0 +1 @@ +Subproject commit 7a895239c6b37dd8a98725081dc1c8e26ffca3aa diff --git a/balloon/tests/compatibility.rs b/balloon/tests/compatibility.rs index 4953c69e..f1e4b468 100644 --- a/balloon/tests/compatibility.rs +++ b/balloon/tests/compatibility.rs @@ -93,3 +93,30 @@ fn test() { ); } } + +mod nachonavarro_balloon { + use pyo3::{types::PyModule, PyResult, Python}; + + pub fn balloon( + password: &str, + salt: &str, + space_cost: u32, + time_cost: u32, + delta: u32, + ) -> PyResult> { + pyo3::prepare_freethreaded_python(); + + Python::with_gil(|py| { + let balloon = PyModule::from_code( + py, + include_str!("../balloon-hashing/balloon.py"), + "balloon", + "balloon", + )?; + balloon + .getattr("balloon")? + .call1((password, salt, space_cost, time_cost, delta))? + .extract() + }) + } +} From 364680bbcbff03d25b567c6c4c03dbde123aa247 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Thu, 4 Nov 2021 19:40:36 +0100 Subject: [PATCH 26/40] Add dependabot gitsubmodule updates --- .github/dependabot.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index db0413ce..f7d81458 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,4 +4,8 @@ updates: directory: "/" schedule: interval: daily +- package-ecosystem: gitsubmodule + directory: "/" + schedule: + interval: daily open-pull-requests-limit: 10 \ No newline at end of file From d1f45d02263330c3ea993bcaecf0c51bb70025f5 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Thu, 4 Nov 2021 19:43:03 +0100 Subject: [PATCH 27/40] Fix CI --- .github/workflows/balloon.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/balloon.yml b/.github/workflows/balloon.yml index dbc4932a..8ae70641 100644 --- a/.github/workflows/balloon.yml +++ b/.github/workflows/balloon.yml @@ -29,6 +29,8 @@ jobs: - wasm32-unknown-unknown steps: - uses: actions/checkout@v1 + with: + submodules: true - uses: actions-rs/toolchain@v1 with: profile: minimal @@ -48,6 +50,8 @@ jobs: - stable steps: - uses: actions/checkout@v1 + with: + submodules: true - uses: actions-rs/toolchain@v1 with: profile: minimal From 2a25d9180b137b33cad9896cb5646d004649670c Mon Sep 17 00:00:00 2001 From: daxpedda Date: Fri, 5 Nov 2021 05:56:10 +0100 Subject: [PATCH 28/40] Remove dependency on nachonavarro's balloon-hashing library --- .github/dependabot.yml | 4 - .github/workflows/balloon.yml | 4 - .gitmodules | 3 - Cargo.lock | 230 ------------------ balloon/Cargo.toml | 1 - balloon/balloon-hashing | 1 - .../{compatibility.rs => test-vectors.rs} | 40 --- 7 files changed, 283 deletions(-) delete mode 100644 .gitmodules delete mode 160000 balloon/balloon-hashing rename balloon/tests/{compatibility.rs => test-vectors.rs} (67%) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f7d81458..db0413ce 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,8 +4,4 @@ updates: directory: "/" schedule: interval: daily -- package-ecosystem: gitsubmodule - directory: "/" - schedule: - interval: daily open-pull-requests-limit: 10 \ No newline at end of file diff --git a/.github/workflows/balloon.yml b/.github/workflows/balloon.yml index 8ae70641..dbc4932a 100644 --- a/.github/workflows/balloon.yml +++ b/.github/workflows/balloon.yml @@ -29,8 +29,6 @@ jobs: - wasm32-unknown-unknown steps: - uses: actions/checkout@v1 - with: - submodules: true - uses: actions-rs/toolchain@v1 with: profile: minimal @@ -50,8 +48,6 @@ jobs: - stable steps: - uses: actions/checkout@v1 - with: - submodules: true - uses: actions-rs/toolchain@v1 with: profile: minimal diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e99c3ce5..00000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "balloon/balloon-hashing"] - path = balloon/balloon-hashing - url = https://github.com/nachonavarro/balloon-hashing diff --git a/Cargo.lock b/Cargo.lock index 1de42257..1b008030 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,7 +27,6 @@ dependencies = [ "crypto-bigint", "digest 0.9.0", "password-hash", - "pyo3", "rayon", "sha2 0.9.8", ] @@ -49,12 +48,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "blake2" version = "0.10.0" @@ -258,38 +251,6 @@ dependencies = [ "digest 0.10.0", ] -[[package]] -name = "indoc" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47741a8bc60fb26eb8d6e0238bbb26d8575ff623fdc97b1a2c00c050b9684ed8" -dependencies = [ - "indoc-impl", - "proc-macro-hack", -] - -[[package]] -name = "indoc-impl" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce046d161f000fffde5f432a0d034d0341dc152643b2598ed5bfce44c4f3a8f0" -dependencies = [ - "proc-macro-hack", - "proc-macro2", - "quote", - "syn", - "unindent", -] - -[[package]] -name = "instant" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d" -dependencies = [ - "cfg-if", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -302,15 +263,6 @@ version = "0.2.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f98a04dce437184842841303488f70d0188c5f51437d2a834dc097eafa909a01" -[[package]] -name = "lock_api" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" -dependencies = [ - "scopeguard", -] - [[package]] name = "memoffset" version = "0.6.5" @@ -330,43 +282,12 @@ dependencies = [ "libc", ] -[[package]] -name = "once_cell" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" - [[package]] name = "opaque-debug" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall", - "smallvec", - "winapi", -] - [[package]] name = "password-hash" version = "0.3.2" @@ -378,25 +299,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "paste" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" -dependencies = [ - "paste-impl", - "proc-macro-hack", -] - -[[package]] -name = "paste-impl" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" -dependencies = [ - "proc-macro-hack", -] - [[package]] name = "pbkdf2" version = "0.10.0" @@ -417,78 +319,6 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" -[[package]] -name = "proc-macro-hack" -version = "0.5.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - -[[package]] -name = "proc-macro2" -version = "1.0.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "pyo3" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64664505ce285a59b8b7e940fbe54ad65b1758a0810eddc5bc26df6f6ec8c557" -dependencies = [ - "cfg-if", - "indoc", - "libc", - "parking_lot", - "paste", - "pyo3-build-config", - "pyo3-macros", - "unindent", -] - -[[package]] -name = "pyo3-build-config" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f1e4a72de84cdcd69f62211b62f62753d0c11b7b5715f3467f3754dab22a7ca" -dependencies = [ - "once_cell", -] - -[[package]] -name = "pyo3-macros" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "244f21d0a3887a9c02018b94e3b78d693dc7eca5c56839b7796a499cc364deb4" -dependencies = [ - "pyo3-macros-backend", - "quote", - "syn", -] - -[[package]] -name = "pyo3-macros-backend" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d3d18ac41d05199bb82645d56e39f8c8b4909a0137c6f2640f03685b29f672" -dependencies = [ - "proc-macro2", - "pyo3-build-config", - "quote", - "syn", -] - -[[package]] -name = "quote" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" -dependencies = [ - "proc-macro2", -] - [[package]] name = "rand" version = "0.8.4" @@ -554,15 +384,6 @@ dependencies = [ "num_cpus", ] -[[package]] -name = "redox_syscall" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" -dependencies = [ - "bitflags", -] - [[package]] name = "salsa20" version = "0.9.0" @@ -633,12 +454,6 @@ dependencies = [ "digest 0.10.0", ] -[[package]] -name = "smallvec" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" - [[package]] name = "streebog" version = "0.10.0" @@ -654,35 +469,12 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" -[[package]] -name = "syn" -version = "1.0.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - [[package]] name = "typenum" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" -[[package]] -name = "unicode-xid" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" - -[[package]] -name = "unindent" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f14ee04d9415b52b3aeab06258a3f07093182b88ba0f9b8d203f211a7a7d41c7" - [[package]] name = "version_check" version = "0.9.3" @@ -695,28 +487,6 @@ version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "zeroize" version = "1.4.3" diff --git a/balloon/Cargo.toml b/balloon/Cargo.toml index 817702de..eef6efdc 100644 --- a/balloon/Cargo.toml +++ b/balloon/Cargo.toml @@ -20,7 +20,6 @@ password-hash = { version = "0.3", default-features = false, optional = true } rayon = { version = "1", optional = true } [dev-dependencies] -pyo3 = "0.15" sha2 = "0.9" [features] diff --git a/balloon/balloon-hashing b/balloon/balloon-hashing deleted file mode 160000 index 7a895239..00000000 --- a/balloon/balloon-hashing +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7a895239c6b37dd8a98725081dc1c8e26ffca3aa diff --git a/balloon/tests/compatibility.rs b/balloon/tests/test-vectors.rs similarity index 67% rename from balloon/tests/compatibility.rs rename to balloon/tests/test-vectors.rs index f1e4b468..d778e587 100644 --- a/balloon/tests/compatibility.rs +++ b/balloon/tests/test-vectors.rs @@ -78,45 +78,5 @@ fn test() { .unwrap() .as_slice() ); - - assert_eq!( - test_vector.output, - nachonavarro_balloon::balloon( - core::str::from_utf8(test_vector.password).unwrap(), - core::str::from_utf8(test_vector.salt).unwrap(), - test_vector.s_cost, - test_vector.t_cost, - 3 - ) - .unwrap() - .as_slice() - ); - } -} - -mod nachonavarro_balloon { - use pyo3::{types::PyModule, PyResult, Python}; - - pub fn balloon( - password: &str, - salt: &str, - space_cost: u32, - time_cost: u32, - delta: u32, - ) -> PyResult> { - pyo3::prepare_freethreaded_python(); - - Python::with_gil(|py| { - let balloon = PyModule::from_code( - py, - include_str!("../balloon-hashing/balloon.py"), - "balloon", - "balloon", - )?; - balloon - .getattr("balloon")? - .call1((password, salt, space_cost, time_cost, delta))? - .extract() - }) } } From a14fb35813107040a7a36d6b54632e5109f104d7 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Fri, 5 Nov 2021 05:59:45 +0100 Subject: [PATCH 29/40] Add comment to origin of the test vectors --- balloon/tests/test-vectors.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/balloon/tests/test-vectors.rs b/balloon/tests/test-vectors.rs index d778e587..a08fa682 100644 --- a/balloon/tests/test-vectors.rs +++ b/balloon/tests/test-vectors.rs @@ -10,6 +10,7 @@ struct TestVector { output: [u8; 32], } +/// Created and tested here: . const TEST_VECTORS: &[TestVector] = &[ TestVector { password: b"hunter42", From 30ac720189ea81538bdafbfa3bf210590bc97d55 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Sat, 6 Nov 2021 01:03:11 +0100 Subject: [PATCH 30/40] Convert test vectors to hex to simplify compatibility --- Cargo.lock | 1 + balloon/Cargo.toml | 1 + balloon/tests/test-vectors.rs | 26 ++++++-------------------- 3 files changed, 8 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1b008030..70267847 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,7 @@ version = "0.1.0" dependencies = [ "crypto-bigint", "digest 0.9.0", + "hex-literal", "password-hash", "rayon", "sha2 0.9.8", diff --git a/balloon/Cargo.toml b/balloon/Cargo.toml index eef6efdc..d31fad29 100644 --- a/balloon/Cargo.toml +++ b/balloon/Cargo.toml @@ -20,6 +20,7 @@ password-hash = { version = "0.3", default-features = false, optional = true } rayon = { version = "1", optional = true } [dev-dependencies] +hex-literal = "0.3" sha2 = "0.9" [features] diff --git a/balloon/tests/test-vectors.rs b/balloon/tests/test-vectors.rs index a08fa682..31b43123 100644 --- a/balloon/tests/test-vectors.rs +++ b/balloon/tests/test-vectors.rs @@ -1,6 +1,7 @@ #![cfg(feature = "alloc")] use balloon_hash::{Balloon, Params}; +use hex_literal::hex; struct TestVector { password: &'static [u8], @@ -17,50 +18,35 @@ const TEST_VECTORS: &[TestVector] = &[ salt: b"examplesalt", s_cost: 1024, t_cost: 3, - output: [ - 113, 96, 67, 223, 247, 119, 180, 74, 167, 184, 141, 203, 171, 18, 192, 120, 171, 236, - 250, 201, 210, 137, 197, 181, 25, 89, 103, 170, 99, 68, 13, 251, - ], + output: hex!("716043dff777b44aa7b88dcbab12c078abecfac9d289c5b5195967aa63440dfb"), }, TestVector { password: b"", salt: b"salt", s_cost: 3, t_cost: 3, - output: [ - 95, 2, 248, 32, 111, 156, 210, 18, 72, 92, 107, 223, 133, 82, 123, 105, 137, 86, 112, - 26, 208, 133, 33, 6, 249, 75, 148, 238, 148, 87, 115, 120, - ], + output: hex!("5f02f8206f9cd212485c6bdf85527b698956701ad0852106f94b94ee94577378"), }, TestVector { password: b"password", salt: b"", s_cost: 3, t_cost: 3, - output: [ - 32, 170, 153, 215, 254, 63, 77, 244, 189, 152, 198, 85, 197, 72, 14, 201, 139, 20, 49, - 7, 163, 49, 253, 73, 29, 237, 168, 133, 196, 214, 166, 204, - ], + output: hex!("20aa99d7fe3f4df4bd98c655c5480ec98b143107a331fd491deda885c4d6a6cc"), }, TestVector { password: b"\0", salt: b"\0", s_cost: 3, t_cost: 3, - output: [ - 79, 199, 227, 2, 255, 162, 154, 224, 234, 195, 17, 102, 206, 231, 165, 82, 209, 215, - 17, 53, 244, 224, 218, 102, 72, 111, 182, 138, 116, 155, 115, 164, - ], + output: hex!("4fc7e302ffa29ae0eac31166cee7a552d1d71135f4e0da66486fb68a749b73a4"), }, TestVector { password: b"password", salt: b"salt", s_cost: 1, t_cost: 1, - output: [ - 238, 253, 164, 168, 167, 91, 70, 31, 163, 137, 193, 220, 250, 243, 233, 223, 172, 188, - 38, 248, 31, 34, 230, 242, 128, 209, 92, 193, 140, 65, 117, 69, - ], + output: hex!("eefda4a8a75b461fa389c1dcfaf3e9dfacbc26f81f22e6f280d15cc18c417545"), }, ]; From 826eabfeb55865260ecb9bdc297107d918c0bbbc Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Sat, 6 Nov 2021 01:04:04 +0100 Subject: [PATCH 31/40] Only convert `s_cost` to `UInt` once --- balloon/src/lib.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/balloon/src/lib.rs b/balloon/src/lib.rs index ea6fda25..3fb93183 100644 --- a/balloon/src/lib.rs +++ b/balloon/src/lib.rs @@ -196,6 +196,12 @@ where ) -> Result> { // we will use `s_cost` to index arrays regularly let s_cost = self.params.s_cost.get() as usize; + let s_cost_bigint = { + let mut s_cost = GenericArray::::default(); + s_cost[..mem::size_of::()] + .copy_from_slice(&self.params.s_cost.get().to_le_bytes()); + NonZero::new(s_cost.into_uint_le()).unwrap() + }; let mut digest = D::new(); @@ -278,13 +284,7 @@ where } digest.update(idx_block); - let s_cost = { - let mut s_cost = GenericArray::::default(); - s_cost[..mem::size_of::()] - .copy_from_slice(&self.params.s_cost.get().to_le_bytes()); - NonZero::new(s_cost.into_uint_le()).unwrap() - }; - let other = digest.finalize_reset().into_uint_le() % s_cost; + let other = digest.finalize_reset().into_uint_le() % s_cost_bigint; let other = usize::from_le_bytes( other.to_le_byte_array()[..mem::size_of::()] .try_into() From d7b8038cfbfc7541054476734cb3c8e205e71834 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Mon, 8 Nov 2021 12:42:10 +0100 Subject: [PATCH 32/40] Separate into two algorithm variants: Balloon and Balloon-M --- balloon/src/algorithm.rs | 110 +++++++++++++++++ balloon/src/error.rs | 8 ++ balloon/src/lib.rs | 111 ++++++++++++------ balloon/src/params.rs | 2 + balloon/tests/{test-vectors.rs => balloon.rs} | 11 +- balloon/tests/balloon_m.rs | 102 ++++++++++++++++ 6 files changed, 304 insertions(+), 40 deletions(-) create mode 100644 balloon/src/algorithm.rs rename balloon/tests/{test-vectors.rs => balloon.rs} (86%) create mode 100644 balloon/tests/balloon_m.rs diff --git a/balloon/src/algorithm.rs b/balloon/src/algorithm.rs new file mode 100644 index 00000000..ebea21a1 --- /dev/null +++ b/balloon/src/algorithm.rs @@ -0,0 +1,110 @@ +//! Balloon algorithms (e.g. Balloon, BalloonM). + +use crate::{Error, Result}; +use core::{ + fmt::{self, Display}, + str::FromStr, +}; + +#[cfg(feature = "password-hash")] +use {core::convert::TryFrom, password_hash::Ident}; + +/// Balloon primitive type: variants of the algorithm. +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub enum Algorithm { + /// Standard Balloon hashing algorithm. + Balloon, + + /// M-core variant of the Balloon hashing algorithm. + /// + /// Supports parallelism by computing M instances of the + /// single-core Balloon function and XORing all the outputs. + BalloonM, +} + +impl Default for Algorithm { + fn default() -> Algorithm { + Algorithm::BalloonM + } +} + +impl Algorithm { + /// Balloon algorithm identifier + #[cfg(feature = "password-hash")] + #[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))] + pub const BALLOON_IDENT: Ident<'static> = Ident::new("balloon"); + + /// BalloonM algorithm identifier + #[cfg(feature = "password-hash")] + #[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))] + pub const BALLOON_M_IDENT: Ident<'static> = Ident::new("balloon-m"); + + /// Parse an [`Algorithm`] from the provided string. + pub fn new(id: impl AsRef) -> Result { + id.as_ref().parse() + } + + /// Get the identifier string for this Balloon [`Algorithm`]. + pub fn as_str(&self) -> &str { + match self { + Algorithm::Balloon => "balloon", + Algorithm::BalloonM => "balloon-m", + } + } + + /// Get the [`Ident`] that corresponds to this Balloon [`Algorithm`]. + #[cfg(feature = "password-hash")] + #[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))] + pub fn ident(&self) -> Ident<'static> { + match self { + Algorithm::Balloon => Self::BALLOON_IDENT, + Algorithm::BalloonM => Self::BALLOON_M_IDENT, + } + } +} + +impl AsRef for Algorithm { + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl Display for Algorithm { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +impl FromStr for Algorithm { + type Err = Error; + + fn from_str(s: &str) -> Result { + match s { + "balloon" => Ok(Algorithm::Balloon), + "balloon-m" => Ok(Algorithm::BalloonM), + _ => Err(Error::AlgorithmInvalid), + } + } +} + +#[cfg(feature = "password-hash")] +#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))] +impl From for Ident<'static> { + fn from(alg: Algorithm) -> Ident<'static> { + alg.ident() + } +} + +#[cfg(feature = "password-hash")] +#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))] +impl<'a> TryFrom> for Algorithm { + type Error = password_hash::Error; + + fn try_from(ident: Ident<'a>) -> password_hash::Result { + match ident { + Self::BALLOON_IDENT => Ok(Algorithm::Balloon), + Self::BALLOON_M_IDENT => Ok(Algorithm::BalloonM), + _ => Err(password_hash::Error::Algorithm), + } + } +} diff --git a/balloon/src/error.rs b/balloon/src/error.rs index e7452ef8..771996e6 100644 --- a/balloon/src/error.rs +++ b/balloon/src/error.rs @@ -11,10 +11,14 @@ pub type Result = core::result::Result; /// Error type. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Error { + /// Algorithm identifier invalid. + AlgorithmInvalid, /// Memory cost is too small. MemoryTooLittle, /// Not enough threads. ThreadsTooFew, + /// Too many threads. + ThreadsTooMany, /// Time cost is too small. TimeTooSmall, } @@ -22,8 +26,10 @@ pub enum Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { + Error::AlgorithmInvalid => "algorithm identifier invalid", Error::MemoryTooLittle => "memory cost is too small", Error::ThreadsTooFew => "not enough threads", + Error::ThreadsTooMany => "too many threads", Error::TimeTooSmall => "time cost is too small", }) } @@ -34,8 +40,10 @@ impl fmt::Display for Error { impl From for password_hash::Error { fn from(err: Error) -> password_hash::Error { match err { + Error::AlgorithmInvalid => password_hash::Error::Algorithm, Error::MemoryTooLittle => InvalidValue::TooShort.param_error(), Error::ThreadsTooFew => InvalidValue::TooShort.param_error(), + Error::ThreadsTooMany => InvalidValue::TooLong.param_error(), Error::TimeTooSmall => InvalidValue::TooShort.param_error(), } } diff --git a/balloon/src/lib.rs b/balloon/src/lib.rs index 3fb93183..1613e320 100644 --- a/balloon/src/lib.rs +++ b/balloon/src/lib.rs @@ -62,10 +62,12 @@ #[cfg(feature = "alloc")] extern crate alloc; +mod algorithm; mod error; mod params; pub use crate::{ + algorithm::Algorithm, error::{Error, Result}, params::Params, }; @@ -79,7 +81,10 @@ use digest::Digest; #[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))] pub use password_hash::{self, PasswordHash, PasswordHasher, PasswordVerifier}; #[cfg(all(feature = "alloc", feature = "password-hash"))] -use password_hash::{Decimal, Ident, ParamsString, Salt}; +use { + core::convert::TryFrom, + password_hash::{Decimal, Ident, ParamsString, Salt}, +}; /// Balloon context. /// @@ -94,6 +99,8 @@ where { /// Storing which hash function is used pub digest: PhantomData, + /// Algorithm to use + pub algorithm: Algorithm, /// Algorithm parameters pub params: Params, /// Key array @@ -105,9 +112,10 @@ where GenericArray: ArrayDecoding, { /// Create a new Balloon context. - pub fn new(params: Params, secret: Option<&'key [u8]>) -> Self { + pub fn new(algorithm: Algorithm, params: Params, secret: Option<&'key [u8]>) -> Self { Self { digest: PhantomData, + algorithm, params, secret, } @@ -140,11 +148,20 @@ where salt: &[u8], memory_blocks: &mut [GenericArray], ) -> Result> { - if self.params.p_cost.get() == 1 { - self.hash_internal(pwd, salt, memory_blocks, None) - } else { + if matches!(self.algorithm, Algorithm::Balloon) && self.params.p_cost.get() > 1 { + return Err(Error::ThreadsTooMany); + } + + match self.algorithm { + Algorithm::Balloon => { + if self.params.p_cost.get() == 1 { + self.hash_internal(pwd, salt, memory_blocks, None) + } else { + Err(Error::ThreadsTooMany) + } + } #[cfg(not(feature = "parallel"))] - { + Algorithm::BalloonM => { let mut result = GenericArray::default(); for thread in 1..=u64::from(self.params.p_cost.get()) { @@ -155,7 +172,7 @@ where Ok(result) } #[cfg(feature = "parallel")] - { + Algorithm::BalloonM => { use rayon::iter::{ParallelBridge, ParallelIterator}; if memory_blocks.len() @@ -164,25 +181,41 @@ where return Err(Error::MemoryTooLittle); } - (1..=u64::from(self.params.p_cost.get())) - .zip(memory_blocks.chunks_exact_mut(self.params.s_cost.get() as usize)) - .par_bridge() - .map_with( - (self.params, self.secret), - |(params, secret), (thread, memory)| { - // `PhantomData` doesn't implement `Sync` unless `D` does, so we build a - // new `Balloon`, which is free - Self::new(*params, *secret).hash_internal( - pwd, - salt, - memory, - Some(thread), - ) - }, - ) - .try_reduce(GenericArray::default, |a, b| { - Ok(a.into_iter().zip(b).map(|(a, b)| a ^ b).collect()) - }) + // Shortcut if p_cost is one. + let output = if self.params.p_cost.get() == 1 { + self.hash_internal(pwd, salt, memory_blocks, Some(1)) + } else { + (1..=u64::from(self.params.p_cost.get())) + .zip(memory_blocks.chunks_exact_mut(self.params.s_cost.get() as usize)) + .par_bridge() + .map_with( + (self.algorithm, self.params, self.secret), + |(algorithm, params, secret), (thread, memory)| { + // `PhantomData` doesn't implement `Sync` unless `D` does, so we + // build a new `Balloon`, which is free. + Self::new(*algorithm, *params, *secret).hash_internal( + pwd, + salt, + memory, + Some(thread), + ) + }, + ) + .try_reduce(GenericArray::default, |a, b| { + Ok(a.into_iter().zip(b).map(|(a, b)| a ^ b).collect()) + }) + }?; + + let mut digest = D::new(); + digest.update(pwd); + digest.update(salt); + + if let Some(secret) = self.secret { + digest.update(secret); + } + + digest.update(output); + Ok(digest.finalize_reset()) } } } @@ -324,8 +357,6 @@ where where S: AsRef + ?Sized, { - use core::convert::TryFrom; - let salt = Salt::try_from(salt.as_ref())?; let mut salt_arr = [0u8; 64]; let salt_bytes = salt.b64_decode(&mut salt_arr)?; @@ -333,7 +364,7 @@ where let output = password_hash::Output::new(&self.hash(password, salt_bytes)?)?; Ok(PasswordHash { - algorithm: Ident::new("balloon"), + algorithm: self.algorithm.ident(), version: Some(1), params: ParamsString::try_from(&self.params)?, salt: Some(salt), @@ -349,11 +380,10 @@ where params: Params, salt: impl Into>, ) -> password_hash::Result> { - if let Some(alg_id) = alg_id { - if alg_id.as_str() != "balloon" { - return Err(password_hash::Error::Algorithm); - } - } + let algorithm = alg_id + .map(Algorithm::try_from) + .transpose()? + .unwrap_or_default(); if let Some(version) = version { if version != 1 { @@ -363,7 +393,16 @@ where let salt = salt.into(); - Self::new(params, self.secret).hash_password(password, salt.as_str()) + Self::new(algorithm, params, self.secret).hash_password(password, salt.as_str()) + } +} + +impl<'key, D: Digest> From for Balloon<'key, D> +where + GenericArray: ArrayDecoding, +{ + fn from(params: Params) -> Self { + Self::new(Algorithm::default(), params, None) } } @@ -384,7 +423,7 @@ fn hash_simple_retains_configured_params() { let p_cost = 2; let params = Params::new(s_cost, t_cost, p_cost).unwrap(); - let hasher = Balloon::::new(params, None); + let hasher = Balloon::::new(Algorithm::default(), params, None); let hash = hasher .hash_password(EXAMPLE_PASSWORD, EXAMPLE_SALT) .unwrap(); diff --git a/balloon/src/params.rs b/balloon/src/params.rs index 8f886b60..cca65fcf 100644 --- a/balloon/src/params.rs +++ b/balloon/src/params.rs @@ -18,6 +18,8 @@ pub struct Params { /// Time cost, expressed in number of rounds. pub t_cost: NonZeroU32, /// Degree of parallelism, expressed in number of threads. + /// Only allowed to be higher then 1 when used in combination + /// with [`Algorithm::BalloonM`](crate::Algorithm::BalloonM). pub p_cost: NonZeroU32, } diff --git a/balloon/tests/test-vectors.rs b/balloon/tests/balloon.rs similarity index 86% rename from balloon/tests/test-vectors.rs rename to balloon/tests/balloon.rs index 31b43123..ee066ece 100644 --- a/balloon/tests/test-vectors.rs +++ b/balloon/tests/balloon.rs @@ -1,6 +1,6 @@ #![cfg(feature = "alloc")] -use balloon_hash::{Balloon, Params}; +use balloon_hash::{Algorithm, Balloon, Params}; use hex_literal::hex; struct TestVector { @@ -11,7 +11,9 @@ struct TestVector { output: [u8; 32], } -/// Created and tested here: . +/// Tested with the following implementations: +/// - +/// - const TEST_VECTORS: &[TestVector] = &[ TestVector { password: b"hunter42", @@ -54,16 +56,17 @@ const TEST_VECTORS: &[TestVector] = &[ fn test() { for test_vector in TEST_VECTORS { let balloon = Balloon::::new( + Algorithm::Balloon, Params::new(test_vector.s_cost, test_vector.t_cost, 1).unwrap(), None, ); assert_eq!( - test_vector.output, balloon .hash(test_vector.password, test_vector.salt) .unwrap() - .as_slice() + .as_slice(), + test_vector.output, ); } } diff --git a/balloon/tests/balloon_m.rs b/balloon/tests/balloon_m.rs new file mode 100644 index 00000000..b21de847 --- /dev/null +++ b/balloon/tests/balloon_m.rs @@ -0,0 +1,102 @@ +#![cfg(feature = "alloc")] + +use balloon_hash::{Algorithm, Balloon, Params}; +use hex_literal::hex; + +struct TestVector { + password: &'static [u8], + salt: &'static [u8], + s_cost: u32, + t_cost: u32, + p_cost: u32, + output: [u8; 32], +} + +/// Tested with the following implementations: +/// - +/// - +const TEST_VECTORS: &[TestVector] = &[ + TestVector { + password: b"hunter42", + salt: b"examplesalt", + s_cost: 1024, + t_cost: 3, + p_cost: 4, + output: hex!("1832bd8e5cbeba1cb174a13838095e7e66508e9bf04c40178990adbc8ba9eb6f"), + }, + TestVector { + password: b"", + salt: b"salt", + s_cost: 3, + t_cost: 3, + p_cost: 2, + output: hex!("f8767fe04059cef67b4427cda99bf8bcdd983959dbd399a5e63ea04523716c23"), + }, + TestVector { + password: b"password", + salt: b"", + s_cost: 3, + t_cost: 3, + p_cost: 3, + output: hex!("bcad257eff3d1090b50276514857e60db5d0ec484129013ef3c88f7d36e438d6"), + }, + TestVector { + password: b"password", + salt: b"", + s_cost: 3, + t_cost: 3, + p_cost: 1, + output: hex!("498344ee9d31baf82cc93ebb3874fe0b76e164302c1cefa1b63a90a69afb9b4d"), + }, + TestVector { + password: b"\0", + salt: b"\0", + s_cost: 3, + t_cost: 3, + p_cost: 4, + output: hex!("8a665611e40710ba1fd78c181549c750f17c12e423c11930ce997f04c7153e0c"), + }, + TestVector { + password: b"\0", + salt: b"\0", + s_cost: 3, + t_cost: 3, + p_cost: 1, + output: hex!("d9e33c683451b21fb3720afbd78bf12518c1d4401fa39f054b052a145c968bb1"), + }, + TestVector { + password: b"password", + salt: b"salt", + s_cost: 1, + t_cost: 1, + p_cost: 16, + output: hex!("a67b383bb88a282aef595d98697f90820adf64582a4b3627c76b7da3d8bae915"), + }, + TestVector { + password: b"password", + salt: b"salt", + s_cost: 1, + t_cost: 1, + p_cost: 1, + output: hex!("97a11df9382a788c781929831d409d3599e0b67ab452ef834718114efdcd1c6d"), + }, +]; + +#[test] +fn test() { + for test_vector in TEST_VECTORS { + let balloon = Balloon::::new( + Algorithm::BalloonM, + Params::new(test_vector.s_cost, test_vector.t_cost, test_vector.p_cost).unwrap(), + None, + ); + + assert_eq!( + balloon + .hash(test_vector.password, test_vector.salt) + .unwrap() + .as_slice(), + test_vector.output, + ); + } +} From 95a52d5c7d05b3cd5cd6e6a7024d9ed7f3d345ce Mon Sep 17 00:00:00 2001 From: daxpedda Date: Mon, 15 Nov 2021 00:06:25 +0100 Subject: [PATCH 33/40] Fix BallonM implementation without Rayon --- .github/workflows/balloon.yml | 1 + balloon/src/lib.rs | 83 ++++++++++++++++++----------------- 2 files changed, 44 insertions(+), 40 deletions(-) diff --git a/.github/workflows/balloon.yml b/.github/workflows/balloon.yml index dbc4932a..5243db9d 100644 --- a/.github/workflows/balloon.yml +++ b/.github/workflows/balloon.yml @@ -53,4 +53,5 @@ jobs: profile: minimal toolchain: ${{ matrix.rust }} override: true + - run: cargo test --release - run: cargo test --release --all-features diff --git a/balloon/src/lib.rs b/balloon/src/lib.rs index 1613e320..809c28b1 100644 --- a/balloon/src/lib.rs +++ b/balloon/src/lib.rs @@ -160,51 +160,54 @@ where Err(Error::ThreadsTooMany) } } - #[cfg(not(feature = "parallel"))] Algorithm::BalloonM => { - let mut result = GenericArray::default(); + #[cfg(not(feature = "parallel"))] + let output = { + let mut output = GenericArray::<_, D::OutputSize>::default(); - for thread in 1..=u64::from(self.params.p_cost.get()) { - let hash = self.hash_internal(pwd, salt, memory_blocks, Some(thread))?; - result = result.into_iter().zip(hash).map(|(a, b)| a ^ b).collect(); - } + for thread in 1..=u64::from(self.params.p_cost.get()) { + let hash = self.hash_internal(pwd, salt, memory_blocks, Some(thread))?; + output = output.into_iter().zip(hash).map(|(a, b)| a ^ b).collect(); + } - Ok(result) - } - #[cfg(feature = "parallel")] - Algorithm::BalloonM => { - use rayon::iter::{ParallelBridge, ParallelIterator}; + output + }; - if memory_blocks.len() - < (self.params.s_cost.get() * self.params.p_cost.get()) as usize - { - return Err(Error::MemoryTooLittle); - } + #[cfg(feature = "parallel")] + let output = { + use rayon::iter::{ParallelBridge, ParallelIterator}; - // Shortcut if p_cost is one. - let output = if self.params.p_cost.get() == 1 { - self.hash_internal(pwd, salt, memory_blocks, Some(1)) - } else { - (1..=u64::from(self.params.p_cost.get())) - .zip(memory_blocks.chunks_exact_mut(self.params.s_cost.get() as usize)) - .par_bridge() - .map_with( - (self.algorithm, self.params, self.secret), - |(algorithm, params, secret), (thread, memory)| { - // `PhantomData` doesn't implement `Sync` unless `D` does, so we - // build a new `Balloon`, which is free. - Self::new(*algorithm, *params, *secret).hash_internal( - pwd, - salt, - memory, - Some(thread), - ) - }, - ) - .try_reduce(GenericArray::default, |a, b| { - Ok(a.into_iter().zip(b).map(|(a, b)| a ^ b).collect()) - }) - }?; + if memory_blocks.len() + < (self.params.s_cost.get() * self.params.p_cost.get()) as usize + { + return Err(Error::MemoryTooLittle); + } + + // Shortcut if p_cost is one. + if self.params.p_cost.get() == 1 { + self.hash_internal(pwd, salt, memory_blocks, Some(1)) + } else { + (1..=u64::from(self.params.p_cost.get())) + .zip(memory_blocks.chunks_exact_mut(self.params.s_cost.get() as usize)) + .par_bridge() + .map_with( + (self.algorithm, self.params, self.secret), + |(algorithm, params, secret), (thread, memory)| { + // `PhantomData` doesn't implement `Sync` unless `D` does, so we + // build a new `Balloon`, which is free. + Self::new(*algorithm, *params, *secret).hash_internal( + pwd, + salt, + memory, + Some(thread), + ) + }, + ) + .try_reduce(GenericArray::default, |a, b| { + Ok(a.into_iter().zip(b).map(|(a, b)| a ^ b).collect()) + }) + }? + }; let mut digest = D::new(); digest.update(pwd); From 1bae62a583f7cb383928d5beb9b9289274d53aa7 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Mon, 15 Nov 2021 00:10:47 +0100 Subject: [PATCH 34/40] Update crypto-bigint dependency --- Cargo.lock | 4 ++-- balloon/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 70267847..8b2eb17a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -163,9 +163,9 @@ dependencies = [ [[package]] name = "crypto-bigint" -version = "0.2.10" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d12477e115c0d570c12a2dfd859f80b55b60ddb5075df210d3af06d133a69f45" +checksum = "476ecdba12db8402a1664482de8c37fff7dc96241258bd0de1f0d70e760a45fd" dependencies = [ "generic-array", "subtle", diff --git a/balloon/Cargo.toml b/balloon/Cargo.toml index d31fad29..341b215c 100644 --- a/balloon/Cargo.toml +++ b/balloon/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" [dependencies] digest = { version = "0.9", default-features = false } -crypto-bigint = { version = "0.2.10", default-features = false, features = ["generic-array"] } +crypto-bigint = { version = "0.3", default-features = false, features = ["generic-array"] } # optional dependencies password-hash = { version = "0.3", default-features = false, optional = true } From 6125149c8dd5c25c2458183c44738def2b8d3c14 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Mon, 15 Nov 2021 00:14:49 +0100 Subject: [PATCH 35/40] Typos --- balloon/src/params.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/balloon/src/params.rs b/balloon/src/params.rs index cca65fcf..1b5e0e4a 100644 --- a/balloon/src/params.rs +++ b/balloon/src/params.rs @@ -1,4 +1,4 @@ -//! Argon2 password hash parameters. +//! Balloon password hash parameters. use crate::{Error, Result}; use core::num::NonZeroU32; @@ -8,7 +8,7 @@ use { password_hash::{errors::InvalidValue, ParamsString, PasswordHash}, }; -/// Argon2 password hash parameters. +/// Balloon password hash parameters. /// /// These are parameters which can be encoded into a PHC hash string. #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -18,7 +18,7 @@ pub struct Params { /// Time cost, expressed in number of rounds. pub t_cost: NonZeroU32, /// Degree of parallelism, expressed in number of threads. - /// Only allowed to be higher then 1 when used in combination + /// Only allowed to be higher than 1 when used in combination /// with [`Algorithm::BalloonM`](crate::Algorithm::BalloonM). pub p_cost: NonZeroU32, } From 89f7530b7adb48010b9dacd237021f22ef344454 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Mon, 15 Nov 2021 00:23:16 +0100 Subject: [PATCH 36/40] Update MSRV because crypto-bigint dependency --- .github/workflows/balloon.yml | 2 +- .github/workflows/workspace.yml | 2 +- balloon/Cargo.toml | 3 ++- balloon/README.md | 4 ++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/balloon.yml b/.github/workflows/balloon.yml index 5243db9d..a339c2f0 100644 --- a/.github/workflows/balloon.yml +++ b/.github/workflows/balloon.yml @@ -22,7 +22,7 @@ jobs: strategy: matrix: rust: - - 1.51.0 # MSRV + - 1.56.1 # MSRV - stable target: - thumbv7em-none-eabi diff --git a/.github/workflows/workspace.yml b/.github/workflows/workspace.yml index 90c59925..0350ef95 100644 --- a/.github/workflows/workspace.yml +++ b/.github/workflows/workspace.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@v1 - uses: actions-rs/toolchain@v1 with: - toolchain: 1.51.0 # MSRV + toolchain: 1.56.1 # MSRV components: clippy override: true profile: minimal diff --git a/balloon/Cargo.toml b/balloon/Cargo.toml index 341b215c..6ea63573 100644 --- a/balloon/Cargo.toml +++ b/balloon/Cargo.toml @@ -8,7 +8,8 @@ documentation = "https://docs.rs/balloon-hash" repository = "https://github.com/RustCrypto/password-hashes/tree/master/balloon" keywords = ["crypto", "password", "hashing"] categories = ["cryptography", "no-std"] -edition = "2018" +edition = "2021" +rust-version = "1.56.1" readme = "README.md" [dependencies] diff --git a/balloon/README.md b/balloon/README.md index 73189028..8902b5e6 100644 --- a/balloon/README.md +++ b/balloon/README.md @@ -13,7 +13,7 @@ Pure Rust implementation of the [Balloon] password hashing function. ## Minimum Supported Rust Version -Rust **1.51** or higher. +Rust **1.56** or higher. Minimum supported Rust version can be changed in the future, but it will be done with a minor version bump. @@ -45,7 +45,7 @@ dual licensed as above, without any additional terms or conditions. [docs-image]: https://docs.rs/balloon-hash/badge.svg [docs-link]: https://docs.rs/balloon-hash/ [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg -[rustc-image]: https://img.shields.io/badge/rustc-1.51+-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/260046-password-hashes [build-image]: https://github.com/RustCrypto/password-hashes/workflows/balloon/badge.svg?branch=master&event=push From 3d8c134da7a24497fc1b88b4a92f8ead21279cdf Mon Sep 17 00:00:00 2001 From: daxpedda Date: Mon, 15 Nov 2021 00:25:19 +0100 Subject: [PATCH 37/40] Fix CI --- .github/workflows/balloon.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/balloon.yml b/.github/workflows/balloon.yml index a339c2f0..a68787af 100644 --- a/.github/workflows/balloon.yml +++ b/.github/workflows/balloon.yml @@ -44,7 +44,7 @@ jobs: strategy: matrix: rust: - - 1.51.0 # MSRV + - 1.56.1 # MSRV - stable steps: - uses: actions/checkout@v1 From 15b23312a51709346e6d93a8ef8d795bfcef4656 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Wed, 8 Dec 2021 01:00:15 +0100 Subject: [PATCH 38/40] Update to digest 0.10 and sha2 0.10 --- Cargo.lock | 57 ++++++++++--------------------------------- balloon/Cargo.toml | 4 ++-- balloon/src/lib.rs | 60 +++++++++++++++++++++++----------------------- 3 files changed, 45 insertions(+), 76 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8b2eb17a..8194d55f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,11 +25,11 @@ name = "balloon-hash" version = "0.1.0" dependencies = [ "crypto-bigint", - "digest 0.9.0", + "digest", "hex-literal", "password-hash", "rayon", - "sha2 0.9.8", + "sha2", ] [[package]] @@ -45,7 +45,7 @@ dependencies = [ "blowfish", "hex-literal", "pbkdf2", - "sha2 0.10.0", + "sha2", "zeroize", ] @@ -55,16 +55,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a58bdf5134c5beae6fc382002c4d88950bad1feea20f8f7165494b6b43b049de" dependencies = [ - "digest 0.10.0", -] - -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "generic-array", + "digest", ] [[package]] @@ -180,22 +171,13 @@ dependencies = [ "generic-array", ] -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array", -] - [[package]] name = "digest" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8549e6bfdecd113b7e221fe60b433087f6957387a20f8118ebca9b12af19143d" dependencies = [ - "block-buffer 0.10.0", + "block-buffer", "crypto-common", "generic-array", "subtle", @@ -249,7 +231,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddca131f3e7f2ce2df364b57949a9d47915cfbd35e46cfee355ccebbf794d6a2" dependencies = [ - "digest 0.10.0", + "digest", ] [[package]] @@ -304,13 +286,13 @@ dependencies = [ name = "pbkdf2" version = "0.10.0" dependencies = [ - "digest 0.10.0", + "digest", "hex-literal", "hmac", "password-hash", "rayon", "sha-1", - "sha2 0.10.0", + "sha2", "streebog", ] @@ -408,7 +390,7 @@ dependencies = [ "password-hash", "pbkdf2", "salsa20", - "sha2 0.10.0", + "sha2", ] [[package]] @@ -419,7 +401,7 @@ checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.0", + "digest", ] [[package]] @@ -427,23 +409,10 @@ name = "sha-crypt" version = "0.3.2" dependencies = [ "rand", - "sha2 0.10.0", + "sha2", "subtle", ] -[[package]] -name = "sha2" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] - [[package]] name = "sha2" version = "0.10.0" @@ -452,7 +421,7 @@ checksum = "900d964dd36bb15bcf2f2b35694c072feab74969a54f2bbeec7a2d725d2bdcb6" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.0", + "digest", ] [[package]] @@ -461,7 +430,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f2a93b52a311873ee038192d8a95dc3bad1d638ac926c2afee0ea9887ecfaf0" dependencies = [ - "digest 0.10.0", + "digest", ] [[package]] diff --git a/balloon/Cargo.toml b/balloon/Cargo.toml index 6ea63573..299aa6b6 100644 --- a/balloon/Cargo.toml +++ b/balloon/Cargo.toml @@ -13,7 +13,7 @@ rust-version = "1.56.1" readme = "README.md" [dependencies] -digest = { version = "0.9", default-features = false } +digest = { version = "0.10", default-features = false } crypto-bigint = { version = "0.3", default-features = false, features = ["generic-array"] } # optional dependencies @@ -22,7 +22,7 @@ rayon = { version = "1", optional = true } [dev-dependencies] hex-literal = "0.3" -sha2 = "0.9" +sha2 = "0.10" [features] default = ["alloc", "password-hash", "rand"] diff --git a/balloon/src/lib.rs b/balloon/src/lib.rs index 809c28b1..31151718 100644 --- a/balloon/src/lib.rs +++ b/balloon/src/lib.rs @@ -76,7 +76,7 @@ use core::marker::PhantomData; use core::mem; use crypto_bigint::{ArrayDecoding, ArrayEncoding, NonZero}; use digest::generic_array::GenericArray; -use digest::Digest; +use digest::{Digest, FixedOutputReset}; #[cfg(feature = "password-hash")] #[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))] pub use password_hash::{self, PasswordHash, PasswordHasher, PasswordVerifier}; @@ -93,7 +93,7 @@ use { /// - Default set of [`Params`] to be used /// - (Optional) Secret key a.k.a. "pepper" to be used #[derive(Clone, Default)] -pub struct Balloon<'key, D: Digest> +pub struct Balloon<'key, D: Digest + FixedOutputReset> where GenericArray: ArrayDecoding, { @@ -107,7 +107,7 @@ where pub secret: Option<&'key [u8]>, } -impl<'key, D: Digest> Balloon<'key, D> +impl<'key, D: Digest + FixedOutputReset> Balloon<'key, D> where GenericArray: ArrayDecoding, { @@ -210,14 +210,14 @@ where }; let mut digest = D::new(); - digest.update(pwd); - digest.update(salt); + Digest::update(&mut digest, pwd); + Digest::update(&mut digest, salt); if let Some(secret) = self.secret { - digest.update(secret); + Digest::update(&mut digest, secret); } - digest.update(output); + Digest::update(&mut digest, output); Ok(digest.finalize_reset()) } } @@ -253,17 +253,17 @@ where // Step 1. Expand input into buffer. // buf[0] = hash(cnt++, passwd, salt) - digest.update(cnt.to_le_bytes()); + Digest::update(&mut digest, cnt.to_le_bytes()); cnt += 1; - digest.update(pwd); - digest.update(salt); + Digest::update(&mut digest, pwd); + Digest::update(&mut digest, salt); if let Some(secret) = self.secret { - digest.update(secret); + Digest::update(&mut digest, secret); } if let Some(thread_id) = thread_id { - digest.update(thread_id.to_le_bytes()); + Digest::update(&mut digest, thread_id.to_le_bytes()); } buf[0] = digest.finalize_reset(); @@ -271,9 +271,9 @@ where // for m from 1 to s_cost-1: for m in 1..s_cost { // buf[m] = hash(cnt++, buf[m-1]) - digest.update(&cnt.to_le_bytes()); + Digest::update(&mut digest, &cnt.to_le_bytes()); cnt += 1; - digest.update(&buf[m - 1]); + Digest::update(&mut digest, &buf[m - 1]); buf[m] = digest.finalize_reset(); } @@ -291,35 +291,35 @@ where }; // buf[m] = hash(cnt++, prev, buf[m]) - digest.update(&cnt.to_le_bytes()); + Digest::update(&mut digest, &cnt.to_le_bytes()); cnt += 1; - digest.update(prev); - digest.update(&buf[m]); + Digest::update(&mut digest, prev); + Digest::update(&mut digest, &buf[m]); buf[m] = digest.finalize_reset(); // Step 2b. Hash in pseudorandomly chosen blocks. // for i from 0 to delta-1: for i in 0..DELTA { // block_t idx_block = ints_to_block(t, m, i) - digest.update(&t.to_le_bytes()); - digest.update(&(m as u64).to_le_bytes()); - digest.update(&i.to_le_bytes()); + Digest::update(&mut digest, &t.to_le_bytes()); + Digest::update(&mut digest, &(m as u64).to_le_bytes()); + Digest::update(&mut digest, &i.to_le_bytes()); let idx_block = digest.finalize_reset(); // int other = to_int(hash(cnt++, salt, idx_block)) mod s_cost - digest.update(&cnt.to_le_bytes()); + Digest::update(&mut digest, &cnt.to_le_bytes()); cnt += 1; - digest.update(salt); + Digest::update(&mut digest, salt); if let Some(secret) = self.secret { - digest.update(secret); + Digest::update(&mut digest, secret); } if let Some(thread_id) = thread_id { - digest.update(thread_id.to_le_bytes()); + Digest::update(&mut digest, thread_id.to_le_bytes()); } - digest.update(idx_block); + Digest::update(&mut digest, idx_block); let other = digest.finalize_reset().into_uint_le() % s_cost_bigint; let other = usize::from_le_bytes( other.to_le_byte_array()[..mem::size_of::()] @@ -328,10 +328,10 @@ where ); // buf[m] = hash(cnt++, buf[m], buf[other]) - digest.update(&cnt.to_le_bytes()); + Digest::update(&mut digest, &cnt.to_le_bytes()); cnt += 1; - digest.update(&buf[m]); - digest.update(&buf[other]); + Digest::update(&mut digest, &buf[m]); + Digest::update(&mut digest, &buf[other]); buf[m] = digest.finalize_reset(); } } @@ -346,7 +346,7 @@ where #[cfg(all(feature = "alloc", feature = "password-hash"))] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] #[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))] -impl PasswordHasher for Balloon<'_, D> +impl PasswordHasher for Balloon<'_, D> where GenericArray: ArrayDecoding, { @@ -400,7 +400,7 @@ where } } -impl<'key, D: Digest> From for Balloon<'key, D> +impl<'key, D: Digest + FixedOutputReset> From for Balloon<'key, D> where GenericArray: ArrayDecoding, { From b90de2f1cf3fa7bc5b3e781394303e761343e2fa Mon Sep 17 00:00:00 2001 From: daxpedda Date: Wed, 8 Dec 2021 12:58:42 +0100 Subject: [PATCH 39/40] Address nits --- balloon/src/lib.rs | 5 ++++- balloon/src/params.rs | 2 +- balloon/tests/balloon.rs | 9 +++++---- balloon/tests/balloon_m.rs | 15 +++++++++++---- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/balloon/src/lib.rs b/balloon/src/lib.rs index 31151718..8f2ce2d0 100644 --- a/balloon/src/lib.rs +++ b/balloon/src/lib.rs @@ -50,7 +50,7 @@ //! //! [Balloon]: https://en.wikipedia.org/wiki/Balloon_hashing -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] #![cfg_attr(docsrs, feature(doc_cfg))] #![doc( html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", @@ -62,6 +62,9 @@ #[cfg(feature = "alloc")] extern crate alloc; +#[cfg(feature = "std")] +extern crate std; + mod algorithm; mod error; mod params; diff --git a/balloon/src/params.rs b/balloon/src/params.rs index 1b5e0e4a..ef471efc 100644 --- a/balloon/src/params.rs +++ b/balloon/src/params.rs @@ -13,7 +13,7 @@ use { /// These are parameters which can be encoded into a PHC hash string. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct Params { - /// Space cost, expressed in of blocks. + /// Space cost, expressed in number of blocks. pub s_cost: NonZeroU32, /// Time cost, expressed in number of rounds. pub t_cost: NonZeroU32, diff --git a/balloon/tests/balloon.rs b/balloon/tests/balloon.rs index ee066ece..e7cf98d7 100644 --- a/balloon/tests/balloon.rs +++ b/balloon/tests/balloon.rs @@ -1,6 +1,5 @@ -#![cfg(feature = "alloc")] - use balloon_hash::{Algorithm, Balloon, Params}; +use digest::generic_array::GenericArray; use hex_literal::hex; struct TestVector { @@ -53,7 +52,7 @@ const TEST_VECTORS: &[TestVector] = &[ ]; #[test] -fn test() { +fn test_vectors() { for test_vector in TEST_VECTORS { let balloon = Balloon::::new( Algorithm::Balloon, @@ -61,9 +60,11 @@ fn test() { None, ); + let mut memory = vec![GenericArray::default(); balloon.params.s_cost.get() as usize]; + assert_eq!( balloon - .hash(test_vector.password, test_vector.salt) + .hash_with_memory(test_vector.password, test_vector.salt, &mut memory) .unwrap() .as_slice(), test_vector.output, diff --git a/balloon/tests/balloon_m.rs b/balloon/tests/balloon_m.rs index b21de847..09bb81c5 100644 --- a/balloon/tests/balloon_m.rs +++ b/balloon/tests/balloon_m.rs @@ -1,6 +1,5 @@ -#![cfg(feature = "alloc")] - use balloon_hash::{Algorithm, Balloon, Params}; +use digest::generic_array::GenericArray; use hex_literal::hex; struct TestVector { @@ -83,7 +82,7 @@ const TEST_VECTORS: &[TestVector] = &[ ]; #[test] -fn test() { +fn test_vectors() { for test_vector in TEST_VECTORS { let balloon = Balloon::::new( Algorithm::BalloonM, @@ -91,9 +90,17 @@ fn test() { None, ); + #[cfg(not(feature = "parallel"))] + let mut memory = vec![GenericArray::default(); balloon.params.s_cost.get() as usize]; + #[cfg(feature = "parallel")] + let mut memory = vec![ + GenericArray::default(); + (balloon.params.s_cost.get() * balloon.params.p_cost.get()) as usize + ]; + assert_eq!( balloon - .hash(test_vector.password, test_vector.salt) + .hash_with_memory(test_vector.password, test_vector.salt, &mut memory) .unwrap() .as_slice(), test_vector.output, From 6242e124cb006330183434cf7744ae8be04f167d Mon Sep 17 00:00:00 2001 From: daxpedda Date: Wed, 8 Dec 2021 13:28:37 +0100 Subject: [PATCH 40/40] Move Balloon implementation to separate free-standing functions --- balloon/src/balloon.rs | 203 +++++++++++++++++++++++++++++++++++++++++ balloon/src/lib.rs | 193 +-------------------------------------- 2 files changed, 207 insertions(+), 189 deletions(-) create mode 100644 balloon/src/balloon.rs diff --git a/balloon/src/balloon.rs b/balloon/src/balloon.rs new file mode 100644 index 00000000..7fe3dbb5 --- /dev/null +++ b/balloon/src/balloon.rs @@ -0,0 +1,203 @@ +use crate::error::{Error, Result}; +use crate::Params; +use core::mem; +use crypto_bigint::{ArrayDecoding, ArrayEncoding, NonZero}; +use digest::generic_array::GenericArray; +use digest::{Digest, FixedOutputReset}; + +pub fn balloon( + pwd: &[u8], + salt: &[u8], + secret: Option<&[u8]>, + params: Params, + memory_blocks: &mut [GenericArray], +) -> Result> +where + GenericArray: ArrayDecoding, +{ + if params.p_cost.get() == 1 { + hash_internal::(pwd, salt, secret, params, memory_blocks, None) + } else { + Err(Error::ThreadsTooMany) + } +} + +pub fn balloon_m( + pwd: &[u8], + salt: &[u8], + secret: Option<&[u8]>, + params: Params, + memory_blocks: &mut [GenericArray], +) -> Result> +where + GenericArray: ArrayDecoding, +{ + #[cfg(not(feature = "parallel"))] + let output = { + let mut output = GenericArray::<_, D::OutputSize>::default(); + + for thread in 1..=u64::from(params.p_cost.get()) { + let hash = hash_internal::(pwd, salt, secret, params, memory_blocks, Some(thread))?; + output = output.into_iter().zip(hash).map(|(a, b)| a ^ b).collect(); + } + + output + }; + + #[cfg(feature = "parallel")] + let output = { + use rayon::iter::{ParallelBridge, ParallelIterator}; + + if memory_blocks.len() < (params.s_cost.get() * params.p_cost.get()) as usize { + return Err(Error::MemoryTooLittle); + } + + // Shortcut if p_cost is one. + if params.p_cost.get() == 1 { + hash_internal::(pwd, salt, secret, params, memory_blocks, Some(1)) + } else { + (1..=u64::from(params.p_cost.get())) + .zip(memory_blocks.chunks_exact_mut(params.s_cost.get() as usize)) + .par_bridge() + .map_with((params, secret), |(params, secret), (thread, memory)| { + hash_internal::(pwd, salt, *secret, *params, memory, Some(thread)) + }) + .try_reduce(GenericArray::default, |a, b| { + Ok(a.into_iter().zip(b).map(|(a, b)| a ^ b).collect()) + }) + }? + }; + + let mut digest = D::new(); + Digest::update(&mut digest, pwd); + Digest::update(&mut digest, salt); + + if let Some(secret) = secret { + Digest::update(&mut digest, secret); + } + + Digest::update(&mut digest, output); + Ok(digest.finalize_reset()) +} + +fn hash_internal( + pwd: &[u8], + salt: &[u8], + secret: Option<&[u8]>, + params: Params, + memory_blocks: &mut [GenericArray], + thread_id: Option, +) -> Result> +where + GenericArray: ArrayDecoding, +{ + // we will use `s_cost` to index arrays regularly + let s_cost = params.s_cost.get() as usize; + let s_cost_bigint = { + let mut s_cost = GenericArray::::default(); + s_cost[..mem::size_of::()].copy_from_slice(¶ms.s_cost.get().to_le_bytes()); + NonZero::new(s_cost.into_uint_le()).unwrap() + }; + + let mut digest = D::new(); + + // This is a direct translation of the `Balloon` from chapter 3.1. + // int delta = 3 // Number of dependencies per block + const DELTA: u64 = 3; + // int cnt = 0 // A counter (used in security proof) + let mut cnt: u64 = 0; + // block_t buf[s_cost]): // The main buffer + let buf = memory_blocks + .get_mut(..s_cost) + .ok_or(Error::MemoryTooLittle)?; + + // Step 1. Expand input into buffer. + // buf[0] = hash(cnt++, passwd, salt) + Digest::update(&mut digest, cnt.to_le_bytes()); + cnt += 1; + Digest::update(&mut digest, pwd); + Digest::update(&mut digest, salt); + + if let Some(secret) = secret { + Digest::update(&mut digest, secret); + } + + if let Some(thread_id) = thread_id { + Digest::update(&mut digest, thread_id.to_le_bytes()); + } + + buf[0] = digest.finalize_reset(); + + // for m from 1 to s_cost-1: + for m in 1..s_cost { + // buf[m] = hash(cnt++, buf[m-1]) + Digest::update(&mut digest, &cnt.to_le_bytes()); + cnt += 1; + Digest::update(&mut digest, &buf[m - 1]); + buf[m] = digest.finalize_reset(); + } + + // Step 2. Mix buffer contents. + // for t from 0 to t_cost-1: + for t in 0..u64::from(params.t_cost.get()) { + // for m from 0 to s_cost-1: + for m in 0..s_cost { + // Step 2a. Hash last and current blocks. + // block_t prev = buf[(m-1) mod s_cost] + let prev = if m == 0 { + buf.last().unwrap() + } else { + &buf[m - 1] + }; + + // buf[m] = hash(cnt++, prev, buf[m]) + Digest::update(&mut digest, &cnt.to_le_bytes()); + cnt += 1; + Digest::update(&mut digest, prev); + Digest::update(&mut digest, &buf[m]); + buf[m] = digest.finalize_reset(); + + // Step 2b. Hash in pseudorandomly chosen blocks. + // for i from 0 to delta-1: + for i in 0..DELTA { + // block_t idx_block = ints_to_block(t, m, i) + Digest::update(&mut digest, &t.to_le_bytes()); + Digest::update(&mut digest, &(m as u64).to_le_bytes()); + Digest::update(&mut digest, &i.to_le_bytes()); + let idx_block = digest.finalize_reset(); + + // int other = to_int(hash(cnt++, salt, idx_block)) mod s_cost + Digest::update(&mut digest, &cnt.to_le_bytes()); + cnt += 1; + Digest::update(&mut digest, salt); + + if let Some(secret) = secret { + Digest::update(&mut digest, secret); + } + + if let Some(thread_id) = thread_id { + Digest::update(&mut digest, thread_id.to_le_bytes()); + } + + Digest::update(&mut digest, idx_block); + let other = digest.finalize_reset().into_uint_le() % s_cost_bigint; + let other = usize::from_le_bytes( + other.to_le_byte_array()[..mem::size_of::()] + .try_into() + .unwrap(), + ); + + // buf[m] = hash(cnt++, buf[m], buf[other]) + Digest::update(&mut digest, &cnt.to_le_bytes()); + cnt += 1; + Digest::update(&mut digest, &buf[m]); + Digest::update(&mut digest, &buf[other]); + buf[m] = digest.finalize_reset(); + } + } + } + + // Step 3. Extract output from buffer. + // return buf[s_cost-1] + Ok(buf.last().unwrap().clone()) +} diff --git a/balloon/src/lib.rs b/balloon/src/lib.rs index 8f2ce2d0..155cb795 100644 --- a/balloon/src/lib.rs +++ b/balloon/src/lib.rs @@ -66,6 +66,7 @@ extern crate alloc; extern crate std; mod algorithm; +mod balloon; mod error; mod params; @@ -74,10 +75,8 @@ pub use crate::{ error::{Error, Result}, params::Params, }; -use core::convert::TryInto; use core::marker::PhantomData; -use core::mem; -use crypto_bigint::{ArrayDecoding, ArrayEncoding, NonZero}; +use crypto_bigint::ArrayDecoding; use digest::generic_array::GenericArray; use digest::{Digest, FixedOutputReset}; #[cfg(feature = "password-hash")] @@ -151,198 +150,14 @@ where salt: &[u8], memory_blocks: &mut [GenericArray], ) -> Result> { - if matches!(self.algorithm, Algorithm::Balloon) && self.params.p_cost.get() > 1 { - return Err(Error::ThreadsTooMany); - } - match self.algorithm { Algorithm::Balloon => { - if self.params.p_cost.get() == 1 { - self.hash_internal(pwd, salt, memory_blocks, None) - } else { - Err(Error::ThreadsTooMany) - } + balloon::balloon::(pwd, salt, self.secret, self.params, memory_blocks) } Algorithm::BalloonM => { - #[cfg(not(feature = "parallel"))] - let output = { - let mut output = GenericArray::<_, D::OutputSize>::default(); - - for thread in 1..=u64::from(self.params.p_cost.get()) { - let hash = self.hash_internal(pwd, salt, memory_blocks, Some(thread))?; - output = output.into_iter().zip(hash).map(|(a, b)| a ^ b).collect(); - } - - output - }; - - #[cfg(feature = "parallel")] - let output = { - use rayon::iter::{ParallelBridge, ParallelIterator}; - - if memory_blocks.len() - < (self.params.s_cost.get() * self.params.p_cost.get()) as usize - { - return Err(Error::MemoryTooLittle); - } - - // Shortcut if p_cost is one. - if self.params.p_cost.get() == 1 { - self.hash_internal(pwd, salt, memory_blocks, Some(1)) - } else { - (1..=u64::from(self.params.p_cost.get())) - .zip(memory_blocks.chunks_exact_mut(self.params.s_cost.get() as usize)) - .par_bridge() - .map_with( - (self.algorithm, self.params, self.secret), - |(algorithm, params, secret), (thread, memory)| { - // `PhantomData` doesn't implement `Sync` unless `D` does, so we - // build a new `Balloon`, which is free. - Self::new(*algorithm, *params, *secret).hash_internal( - pwd, - salt, - memory, - Some(thread), - ) - }, - ) - .try_reduce(GenericArray::default, |a, b| { - Ok(a.into_iter().zip(b).map(|(a, b)| a ^ b).collect()) - }) - }? - }; - - let mut digest = D::new(); - Digest::update(&mut digest, pwd); - Digest::update(&mut digest, salt); - - if let Some(secret) = self.secret { - Digest::update(&mut digest, secret); - } - - Digest::update(&mut digest, output); - Ok(digest.finalize_reset()) - } - } - } - - fn hash_internal( - &self, - pwd: &[u8], - salt: &[u8], - memory_blocks: &mut [GenericArray], - thread_id: Option, - ) -> Result> { - // we will use `s_cost` to index arrays regularly - let s_cost = self.params.s_cost.get() as usize; - let s_cost_bigint = { - let mut s_cost = GenericArray::::default(); - s_cost[..mem::size_of::()] - .copy_from_slice(&self.params.s_cost.get().to_le_bytes()); - NonZero::new(s_cost.into_uint_le()).unwrap() - }; - - let mut digest = D::new(); - - // This is a direct translation of the `Balloon` from chapter 3.1. - // int delta = 3 // Number of dependencies per block - const DELTA: u64 = 3; - // int cnt = 0 // A counter (used in security proof) - let mut cnt: u64 = 0; - // block_t buf[s_cost]): // The main buffer - let buf = memory_blocks - .get_mut(..s_cost) - .ok_or(Error::MemoryTooLittle)?; - - // Step 1. Expand input into buffer. - // buf[0] = hash(cnt++, passwd, salt) - Digest::update(&mut digest, cnt.to_le_bytes()); - cnt += 1; - Digest::update(&mut digest, pwd); - Digest::update(&mut digest, salt); - - if let Some(secret) = self.secret { - Digest::update(&mut digest, secret); - } - - if let Some(thread_id) = thread_id { - Digest::update(&mut digest, thread_id.to_le_bytes()); - } - - buf[0] = digest.finalize_reset(); - - // for m from 1 to s_cost-1: - for m in 1..s_cost { - // buf[m] = hash(cnt++, buf[m-1]) - Digest::update(&mut digest, &cnt.to_le_bytes()); - cnt += 1; - Digest::update(&mut digest, &buf[m - 1]); - buf[m] = digest.finalize_reset(); - } - - // Step 2. Mix buffer contents. - // for t from 0 to t_cost-1: - for t in 0..u64::from(self.params.t_cost.get()) { - // for m from 0 to s_cost-1: - for m in 0..s_cost { - // Step 2a. Hash last and current blocks. - // block_t prev = buf[(m-1) mod s_cost] - let prev = if m == 0 { - buf.last().unwrap() - } else { - &buf[m - 1] - }; - - // buf[m] = hash(cnt++, prev, buf[m]) - Digest::update(&mut digest, &cnt.to_le_bytes()); - cnt += 1; - Digest::update(&mut digest, prev); - Digest::update(&mut digest, &buf[m]); - buf[m] = digest.finalize_reset(); - - // Step 2b. Hash in pseudorandomly chosen blocks. - // for i from 0 to delta-1: - for i in 0..DELTA { - // block_t idx_block = ints_to_block(t, m, i) - Digest::update(&mut digest, &t.to_le_bytes()); - Digest::update(&mut digest, &(m as u64).to_le_bytes()); - Digest::update(&mut digest, &i.to_le_bytes()); - let idx_block = digest.finalize_reset(); - - // int other = to_int(hash(cnt++, salt, idx_block)) mod s_cost - Digest::update(&mut digest, &cnt.to_le_bytes()); - cnt += 1; - Digest::update(&mut digest, salt); - - if let Some(secret) = self.secret { - Digest::update(&mut digest, secret); - } - - if let Some(thread_id) = thread_id { - Digest::update(&mut digest, thread_id.to_le_bytes()); - } - - Digest::update(&mut digest, idx_block); - let other = digest.finalize_reset().into_uint_le() % s_cost_bigint; - let other = usize::from_le_bytes( - other.to_le_byte_array()[..mem::size_of::()] - .try_into() - .unwrap(), - ); - - // buf[m] = hash(cnt++, buf[m], buf[other]) - Digest::update(&mut digest, &cnt.to_le_bytes()); - cnt += 1; - Digest::update(&mut digest, &buf[m]); - Digest::update(&mut digest, &buf[other]); - buf[m] = digest.finalize_reset(); - } + balloon::balloon_m::(pwd, salt, self.secret, self.params, memory_blocks) } } - - // Step 3. Extract output from buffer. - // return buf[s_cost-1] - Ok(buf.last().unwrap().clone()) } }