argon2-0.5.3/.cargo_vcs_info.json0000644000000001440000000000100122300ustar { "git": { "sha1": "5b6a74e0799955b21c9d3854de1674a78a472648" }, "path_in_vcs": "argon2" }argon2-0.5.3/CHANGELOG.md000064400000000000000000000154131046102023000126360ustar 00000000000000# 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.5.3 (2024-01-20) ### Fixed - Documentation ([#458], [#459]) - Big endian support ([#482]) [#458]: https://github.com/RustCrypto/password-hashes/pull/458 [#459]: https://github.com/RustCrypto/password-hashes/pull/459 [#482]: https://github.com/RustCrypto/password-hashes/pull/482 ## 0.5.2 (2023-09-03) ### Changed - Improved `const fn` support ([#450]) ### Fixed - Max params ([#452]) [#450]: https://github.com/RustCrypto/password-hashes/pull/450 [#452]: https://github.com/RustCrypto/password-hashes/pull/452 ## 0.5.1 (2023-07-13) ### Added - Provide `std::error::Error::source` for `argon2::Error` ([#379]) - `ParamsBuilder::context` ([#400]) - Enable `password-hash/alloc` when `alloc` feature is enabled ([#422]) - Impl `Debug` for `Argon2` ([#423]) - `Block::new()` const initializer ([#427]) - `Params::DEFAULT` constant ([#439]) - Initial AVX2 SIMD optimizations ([#440]) [#379]: https://github.com/RustCrypto/password-hashes/pull/379 [#400]: https://github.com/RustCrypto/password-hashes/pull/400 [#422]: https://github.com/RustCrypto/password-hashes/pull/422 [#423]: https://github.com/RustCrypto/password-hashes/pull/423 [#427]: https://github.com/RustCrypto/password-hashes/pull/427 [#439]: https://github.com/RustCrypto/password-hashes/pull/439 [#440]: https://github.com/RustCrypto/password-hashes/pull/440 ## 0.5.0 (2023-03-04) ### Added - Key derivation usage example ([#366]) - Inherent constants for `Params` recommendations ([#387]) ### Changed - Merge `Argon2` and `Instance` structs ([#247]) - Refactor `ParamsBuilder` to make it more ergonomic ([#247]) - Bump `password-hash` dependency to v0.5 ([#383]) - Adopt OWASP recommended default `Params` ([#386]) - MSRV 1.65 ([#391]) ### Fixed - Erroneous docs for `m_cost` and `Block` ([#247]) - Allow `zeroize` in heapless environments (i.e. without `alloc`) ([#374]) ### Removed - `Memory` struct ([#247]) - Unsound `parallel` feature - see [#380] ([#247]) [#247]: https://github.com/RustCrypto/password-hashes/pull/247 [#366]: https://github.com/RustCrypto/password-hashes/pull/366 [#374]: https://github.com/RustCrypto/password-hashes/pull/374 [#380]: https://github.com/RustCrypto/password-hashes/pull/380 [#383]: https://github.com/RustCrypto/password-hashes/pull/383 [#386]: https://github.com/RustCrypto/password-hashes/pull/386 [#387]: https://github.com/RustCrypto/password-hashes/pull/387 [#391]: https://github.com/RustCrypto/password-hashes/pull/391 ## 0.4.1 (2022-06-27) ### Added - `argon2::RECOMMENDED_SALT_LEN` ([#307]) [#307]: https://github.com/RustCrypto/password-hashes/pull/307 ## 0.4.0 (2022-03-18) ### Changed - Bump `password-hash` dependency to v0.4; MSRV 1.57 ([#283]) - 2021 edition upgrade ([#284]) [#283]: https://github.com/RustCrypto/password-hashes/pull/283 [#284]: https://github.com/RustCrypto/password-hashes/pull/284 ## 0.3.4 (2022-02-17) ### Fixed - Minimal versions build ([#273]) [#273]: https://github.com/RustCrypto/password-hashes/pull/273 ## 0.3.3 (2022-01-30) ### Changed - Make `Params::block_count()` public ([#258]) [#258]: https://github.com/RustCrypto/password-hashes/pull/258 ## 0.3.2 (2021-12-07) ### Changed - Bump `blake2` dependency to v0.10 ([#254]) [#254]: https://github.com/RustCrypto/password-hashes/pull/254 ## 0.3.1 (2021-09-11) ### Fixed - Handling of `p_cost` parameter ([#235]) [#235]: https://github.com/RustCrypto/password-hashes/pull/235 ## 0.3.0 (2021-08-27) [YANKED] ### Added - `alloc` feature ([#215]) - `Params` now supports `keyid` and `data` fields ([#216]) ### Changed - `Argon2::new` now has explicit `version` and `params` ([#213]) - Factored apart `Argon2::new` and `Argon2::new_with_secret`, making the former infallible ([#213]) - `Params` struct is now opaque with field accessors, and ensured to always represent a valid set of parameters ([#213]) - Removed `version` parameter from `hash_password_into`, using the one supplied to `Argon2::new` ([#213]) - Bump `password-hash` to v0.3 ([#217], [RustCrypto/traits#724]) - Use `resolver = "2"`; MSRV 1.51+ ([#220]) ### Removed - `Params` no longer has a `version` field ([#211]) [#211]: https://github.com/RustCrypto/password-hashes/pull/211 [#213]: https://github.com/RustCrypto/password-hashes/pull/213 [#215]: https://github.com/RustCrypto/password-hashes/pull/215 [#216]: https://github.com/RustCrypto/password-hashes/pull/216 [#217]: https://github.com/RustCrypto/password-hashes/pull/217 [#220]: https://github.com/RustCrypto/password-hashes/pull/220 [RustCrypto/traits#724]: https://github.com/RustCrypto/traits/pull/724 ## 0.2.4 (2021-08-21) ### Added - Impl `std::error::Error` for `argon2::Error` ([#200]) - Impl `TryFrom` for `Argon2` ([#202]) - `Result` type alias ([#203]) - `ParamsBuilder` ([#204]) [#200]: https://github.com/RustCrypto/password-hashes/pull/200 [#202]: https://github.com/RustCrypto/password-hashes/pull/202 [#203]: https://github.com/RustCrypto/password-hashes/pull/203 [#204]: https://github.com/RustCrypto/password-hashes/pull/204 ## 0.2.3 (2021-08-15) ### Changed - Relax `zeroize` requirements to `>=1, <1.4` ([#195]) [#195]: https://github.com/RustCrypto/password-hashes/pull/195 ## 0.2.2 (2021-07-20) ### Changed - Pin `zeroize` dependency to v1.3 ([#190]) [#190]: https://github.com/RustCrypto/password-hashes/pull/190 ## 0.2.1 (2021-05-28) ### Changed - `Params` always available; no longer feature-gated on `password-hash` ([#182]) ### Fixed - Configured params are used with `hash_password_simple` ([#182]) [#182]: https://github.com/RustCrypto/password-hashes/pull/182 ## 0.2.0 (2021-04-29) ### Changed - Forbid unsafe code outside parallel implementation ([#157]) - Bump `password-hash` crate dependency to v0.2 ([#164]) ### Removed - `argon2::BLOCK_SIZE` constant ([#161]) [#157]: https://github.com/RustCrypto/password-hashes/pull/157 [#161]: https://github.com/RustCrypto/password-hashes/pull/161 [#164]: https://github.com/RustCrypto/password-hashes/pull/164 ## 0.1.5 (2021-04-18) ### Added - Parallel lane processing using `rayon` ([#149]) [#149]: https://github.com/RustCrypto/password-hashes/pull/149 ## 0.1.4 (2021-02-28) ### Added - `std` feature ([#141]) [#141]: https://github.com/RustCrypto/password-hashes/pull/141 ## 0.1.3 (2021-02-12) ### Fixed - Salt-length related panic ([#135]) [#135]: https://github.com/RustCrypto/password-hashes/pull/135 ## 0.1.2 (2021-02-07) ### Fixed - rustdoc typo ([#128]) [#128]: https://github.com/RustCrypto/password-hashes/pull/128 ## 0.1.1 (2021-02-07) ### Added - `rand` feature; enabled-by-default ([#126]) [#126]: https://github.com/RustCrypto/password-hashes/pull/126 ## 0.1.0 (2021-01-29) - Initial release argon2-0.5.3/Cargo.toml0000644000000034760000000000100102410ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.65" name = "argon2" version = "0.5.3" authors = ["RustCrypto Developers"] description = """ Pure Rust implementation of the Argon2 password hashing function with support for the Argon2d, Argon2i, and Argon2id algorithmic variants """ documentation = "https://docs.rs/argon2" readme = "README.md" keywords = [ "crypto", "hashing", "password", "phf", ] categories = [ "authentication", "cryptography", "no-std", ] license = "MIT OR Apache-2.0" repository = "https://github.com/RustCrypto/password-hashes/tree/master/argon2" [package.metadata.docs.rs] all-features = true rustdoc-args = [ "--cfg", "docsrs", ] [dependencies.base64ct] version = "1" [dependencies.blake2] version = "0.10.6" default-features = false [dependencies.password-hash] version = "0.5" optional = true [dependencies.zeroize] version = "1" optional = true default-features = false [dev-dependencies.hex-literal] version = "0.4" [dev-dependencies.password-hash] version = "0.5" features = ["rand_core"] [features] alloc = ["password-hash/alloc"] default = [ "alloc", "password-hash", "rand", ] rand = ["password-hash/rand_core"] simple = ["password-hash"] std = [ "alloc", "password-hash/std", ] [target."cfg(any(target_arch = \"x86\", target_arch = \"x86_64\"))".dependencies.cpufeatures] version = "0.2.12" argon2-0.5.3/Cargo.toml.orig000064400000000000000000000023731046102023000137150ustar 00000000000000[package] name = "argon2" version = "0.5.3" description = """ Pure Rust implementation of the Argon2 password hashing function with support for the Argon2d, Argon2i, and Argon2id algorithmic variants """ authors = ["RustCrypto Developers"] license = "MIT OR Apache-2.0" documentation = "https://docs.rs/argon2" repository = "https://github.com/RustCrypto/password-hashes/tree/master/argon2" keywords = ["crypto", "hashing", "password", "phf"] categories = ["authentication", "cryptography", "no-std"] readme = "README.md" edition = "2021" rust-version = "1.65" [dependencies] base64ct = "1" blake2 = { version = "0.10.6", default-features = false } # optional dependencies password-hash = { version = "0.5", optional = true } zeroize = { version = "1", default-features = false, optional = true } [target.'cfg(any(target_arch = "x86", target_arch = "x86_64"))'.dependencies] cpufeatures = "0.2.12" [dev-dependencies] hex-literal = "0.4" password-hash = { version = "0.5", features = ["rand_core"] } [features] default = ["alloc", "password-hash", "rand"] alloc = ["password-hash/alloc"] std = ["alloc", "password-hash/std"] rand = ["password-hash/rand_core"] simple = ["password-hash"] [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] argon2-0.5.3/LICENSE-APACHE000064400000000000000000000251411046102023000127500ustar 00000000000000 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. argon2-0.5.3/LICENSE-MIT000064400000000000000000000020721046102023000124560ustar 00000000000000Copyright (c) 2021-2024 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. argon2-0.5.3/README.md000064400000000000000000000046751046102023000123140ustar 00000000000000# RustCrypto: Argon2 [![crate][crate-image]][crate-link] [![Docs][docs-image]][docs-link] [![Build Status][build-image]][build-link] ![Apache2/MIT licensed][license-image] ![Rust Version][rustc-image] [![Project Chat][chat-image]][chat-link] Pure Rust implementation of the [Argon2] password hashing function. [Documentation][docs-link] # About Argon2 is a memory-hard [key derivation function] chosen as the winner of the [Password Hashing Competition] in July 2015. It implements the following three algorithmic variants: - **Argon2d**: maximizes resistance to GPU cracking attacks - **Argon2i**: optimized to resist side-channel attacks - **Argon2id**: (default) hybrid version combining both Argon2i and Argon2d Support is provided for embedded (i.e. `no_std`) environments, including ones without `alloc` support. ## Minimum Supported Rust Version Rust **1.65** 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://buildstats.info/crate/argon2 [crate-link]: https://crates.io/crates/argon2 [docs-image]: https://docs.rs/argon2/badge.svg [docs-link]: https://docs.rs/argon2/ [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg [rustc-image]: https://img.shields.io/badge/rustc-1.65+-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/argon2/badge.svg?branch=master&event=push [build-link]: https://github.com/RustCrypto/password-hashes/actions?query=workflow%3Aargon2 [//]: # (general links) [Argon2]: https://en.wikipedia.org/wiki/Argon2 [key derivation function]: https://en.wikipedia.org/wiki/Key_derivation_function [Password Hashing Competition]: https://www.password-hashing.net/ argon2-0.5.3/src/algorithm.rs000064400000000000000000000101721046102023000141450ustar 00000000000000//! Argon2 algorithms (e.g. Argon2d, Argon2i, Argon2id). use crate::{Error, Result}; use core::{ fmt::{self, Display}, str::FromStr, }; #[cfg(feature = "password-hash")] use password_hash::Ident; /// Argon2d algorithm identifier #[cfg(feature = "password-hash")] #[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))] pub const ARGON2D_IDENT: Ident<'_> = Ident::new_unwrap("argon2d"); /// Argon2i algorithm identifier #[cfg(feature = "password-hash")] #[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))] pub const ARGON2I_IDENT: Ident<'_> = Ident::new_unwrap("argon2i"); /// Argon2id algorithm identifier #[cfg(feature = "password-hash")] #[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))] pub const ARGON2ID_IDENT: Ident<'_> = Ident::new_unwrap("argon2id"); /// Argon2 primitive type: variants of the algorithm. #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Default, Ord)] pub enum Algorithm { /// Optimizes against GPU cracking attacks but vulnerable to side-channels. /// /// Accesses the memory array in a password dependent order, reducing the /// possibility of time–memory tradeoff (TMTO) attacks. Argon2d = 0, /// Optimized to resist side-channel attacks. /// /// Accesses the memory array in a password independent order, increasing the /// possibility of time-memory tradeoff (TMTO) attacks. Argon2i = 1, /// Hybrid that mixes Argon2i and Argon2d passes (*default*). /// /// Uses the Argon2i approach for the first half pass over memory and /// Argon2d approach for subsequent passes. This effectively places it in /// the "middle" between the other two: it doesn't provide as good /// TMTO/GPU cracking resistance as Argon2d, nor as good of side-channel /// resistance as Argon2i, but overall provides the most well-rounded /// approach to both classes of attacks. #[default] Argon2id = 2, } impl Algorithm { /// Parse an [`Algorithm`] from the provided string. pub fn new(id: impl AsRef) -> Result { id.as_ref().parse() } /// Get the identifier string for this PBKDF2 [`Algorithm`]. pub const fn as_str(&self) -> &'static str { match self { Algorithm::Argon2d => "argon2d", Algorithm::Argon2i => "argon2i", Algorithm::Argon2id => "argon2id", } } /// Get the [`Ident`] that corresponds to this Argon2 [`Algorithm`]. #[cfg(feature = "password-hash")] #[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))] pub const fn ident(&self) -> Ident<'static> { match self { Algorithm::Argon2d => ARGON2D_IDENT, Algorithm::Argon2i => ARGON2I_IDENT, Algorithm::Argon2id => ARGON2ID_IDENT, } } /// Serialize primitive type as little endian bytes pub(crate) const fn to_le_bytes(self) -> [u8; 4] { (self as u32).to_le_bytes() } } 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 { "argon2d" => Ok(Algorithm::Argon2d), "argon2i" => Ok(Algorithm::Argon2i), "argon2id" => Ok(Algorithm::Argon2id), _ => 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 { ARGON2D_IDENT => Ok(Algorithm::Argon2d), ARGON2I_IDENT => Ok(Algorithm::Argon2i), ARGON2ID_IDENT => Ok(Algorithm::Argon2id), _ => Err(password_hash::Error::Algorithm), } } } argon2-0.5.3/src/blake2b_long.rs000064400000000000000000000046361046102023000145100ustar 00000000000000//! The variable length hash function used in the Argon2 algorithm. use crate::{Error, Result}; use blake2::{ digest::{self, Digest, VariableOutput}, Blake2b512, Blake2bVar, }; use core::convert::TryFrom; pub fn blake2b_long(inputs: &[&[u8]], out: &mut [u8]) -> Result<()> { if out.is_empty() { return Err(Error::OutputTooShort); } let len_bytes = u32::try_from(out.len()) .map(|v| v.to_le_bytes()) .map_err(|_| Error::OutputTooLong)?; // Use blake2b directly if the output is small enough. if out.len() <= Blake2b512::output_size() { let mut digest = Blake2bVar::new(out.len()).map_err(|_| Error::OutputTooLong)?; // Conflicting method name from `Digest` and `Update` traits digest::Update::update(&mut digest, &len_bytes); for input in inputs { digest::Update::update(&mut digest, input); } digest .finalize_variable(out) .map_err(|_| Error::OutputTooLong)?; return Ok(()); } // Calculate longer hashes by first calculating a full 64 byte hash let half_hash_len = Blake2b512::output_size() / 2; let mut digest = Blake2b512::new(); digest.update(len_bytes); for input in inputs { digest.update(input); } let mut last_output = digest.finalize(); // Then we write the first 32 bytes of this hash to the output out[..half_hash_len].copy_from_slice(&last_output[..half_hash_len]); // Next, we write a number of 32 byte blocks to the output. // Each block is the first 32 bytes of the hash of the last block. // The very last block of the output is excluded, and has a variable // length in range [1, 32]. let mut counter = 0; let out_len = out.len(); for chunk in out[half_hash_len..] .chunks_exact_mut(half_hash_len) .take_while(|_| { counter += half_hash_len; out_len - counter > 64 }) { last_output = Blake2b512::digest(last_output); chunk.copy_from_slice(&last_output[..half_hash_len]); } // Calculate the last block with VarBlake2b. let last_block_size = out.len() - counter; let mut digest = Blake2bVar::new(last_block_size).map_err(|_| Error::OutputTooLong)?; digest::Update::update(&mut digest, &last_output); digest .finalize_variable(&mut out[counter..]) .expect("invalid Blake2bVar out length"); Ok(()) } argon2-0.5.3/src/block.rs000064400000000000000000000102521046102023000132500ustar 00000000000000//! Argon2 memory block functions use core::{ convert::{AsMut, AsRef}, num::Wrapping, ops::{BitXor, BitXorAssign}, slice, }; #[cfg(feature = "zeroize")] use zeroize::Zeroize; const TRUNC: u64 = u32::MAX as u64; #[rustfmt::skip] macro_rules! permute_step { ($a:expr, $b:expr, $c:expr, $d:expr) => { $a = (Wrapping($a) + Wrapping($b) + (Wrapping(2) * Wrapping(($a & TRUNC) * ($b & TRUNC)))).0; $d = ($d ^ $a).rotate_right(32); $c = (Wrapping($c) + Wrapping($d) + (Wrapping(2) * Wrapping(($c & TRUNC) * ($d & TRUNC)))).0; $b = ($b ^ $c).rotate_right(24); $a = (Wrapping($a) + Wrapping($b) + (Wrapping(2) * Wrapping(($a & TRUNC) * ($b & TRUNC)))).0; $d = ($d ^ $a).rotate_right(16); $c = (Wrapping($c) + Wrapping($d) + (Wrapping(2) * Wrapping(($c & TRUNC) * ($d & TRUNC)))).0; $b = ($b ^ $c).rotate_right(63); }; } macro_rules! permute { ( $v0:expr, $v1:expr, $v2:expr, $v3:expr, $v4:expr, $v5:expr, $v6:expr, $v7:expr, $v8:expr, $v9:expr, $v10:expr, $v11:expr, $v12:expr, $v13:expr, $v14:expr, $v15:expr, ) => { permute_step!($v0, $v4, $v8, $v12); permute_step!($v1, $v5, $v9, $v13); permute_step!($v2, $v6, $v10, $v14); permute_step!($v3, $v7, $v11, $v15); permute_step!($v0, $v5, $v10, $v15); permute_step!($v1, $v6, $v11, $v12); permute_step!($v2, $v7, $v8, $v13); permute_step!($v3, $v4, $v9, $v14); }; } /// Structure for the (1 KiB) memory block implemented as 128 64-bit words. #[derive(Copy, Clone, Debug)] #[repr(align(64))] pub struct Block([u64; Self::SIZE / 8]); impl Block { /// Memory block size in bytes pub const SIZE: usize = 1024; /// Returns a Block initialized with zeros. pub const fn new() -> Self { Self([0u64; Self::SIZE / 8]) } /// Load a block from a block-sized byte slice #[inline(always)] pub(crate) fn load(&mut self, input: &[u8; Block::SIZE]) { for (i, chunk) in input.chunks(8).enumerate() { self.0[i] = u64::from_le_bytes(chunk.try_into().expect("should be 8 bytes")); } } /// Iterate over the `u64` values contained in this block #[inline(always)] pub(crate) fn iter(&self) -> slice::Iter<'_, u64> { self.0.iter() } /// NOTE: do not call this directly. It should only be called via /// `Argon2::compress`. #[inline(always)] pub(crate) fn compress(rhs: &Self, lhs: &Self) -> Self { let r = *rhs ^ lhs; // Apply permutations rowwise let mut q = r; for chunk in q.0.chunks_exact_mut(16) { #[rustfmt::skip] permute!( chunk[0], chunk[1], chunk[2], chunk[3], chunk[4], chunk[5], chunk[6], chunk[7], chunk[8], chunk[9], chunk[10], chunk[11], chunk[12], chunk[13], chunk[14], chunk[15], ); } // Apply permutations columnwise for i in 0..8 { let b = i * 2; #[rustfmt::skip] permute!( q.0[b], q.0[b + 1], q.0[b + 16], q.0[b + 17], q.0[b + 32], q.0[b + 33], q.0[b + 48], q.0[b + 49], q.0[b + 64], q.0[b + 65], q.0[b + 80], q.0[b + 81], q.0[b + 96], q.0[b + 97], q.0[b + 112], q.0[b + 113], ); } q ^= &r; q } } impl Default for Block { fn default() -> Self { Self([0u64; Self::SIZE / 8]) } } impl AsRef<[u64]> for Block { fn as_ref(&self) -> &[u64] { &self.0 } } impl AsMut<[u64]> for Block { fn as_mut(&mut self) -> &mut [u64] { &mut self.0 } } impl BitXor<&Block> for Block { type Output = Block; fn bitxor(mut self, rhs: &Block) -> Self::Output { self ^= rhs; self } } impl BitXorAssign<&Block> for Block { fn bitxor_assign(&mut self, rhs: &Block) { for (dst, src) in self.0.iter_mut().zip(rhs.0.iter()) { *dst ^= src; } } } #[cfg(feature = "zeroize")] impl Zeroize for Block { fn zeroize(&mut self) { self.0.zeroize(); } } argon2-0.5.3/src/error.rs000064400000000000000000000102101046102023000133010ustar 00000000000000//! Error type use core::fmt; #[cfg(feature = "password-hash")] use {crate::Params, core::cmp::Ordering, password_hash::errors::InvalidValue}; /// Result with argon2's [`Error`] type. pub type Result = core::result::Result; /// Error type. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Error { /// Associated data is too long. AdTooLong, /// Algorithm identifier invalid. AlgorithmInvalid, /// "B64" encoding is invalid. B64Encoding(base64ct::Error), /// Key ID is too long. KeyIdTooLong, /// Memory cost is too small. MemoryTooLittle, /// Memory cost is too large. MemoryTooMuch, /// Output is too short. OutputTooShort, /// Output is too long. OutputTooLong, /// Password is too long. PwdTooLong, /// Salt is too short. SaltTooShort, /// Salt is too long. SaltTooLong, /// Secret is too long. SecretTooLong, /// Not enough threads. ThreadsTooFew, /// Too many threads. ThreadsTooMany, /// Time cost is too small. TimeTooSmall, /// Invalid version VersionInvalid, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { Error::AdTooLong => "associated data is too long", Error::AlgorithmInvalid => "algorithm identifier invalid", Error::B64Encoding(inner) => return write!(f, "B64 encoding invalid: {inner}"), Error::KeyIdTooLong => "key ID is too long", Error::MemoryTooLittle => "memory cost is too small", Error::MemoryTooMuch => "memory cost is too large", Error::OutputTooShort => "output is too short", Error::OutputTooLong => "output is too long", Error::PwdTooLong => "password is too long", Error::SaltTooShort => "salt is too short", Error::SaltTooLong => "salt is too long", Error::SecretTooLong => "secret is too long", Error::ThreadsTooFew => "not enough threads", Error::ThreadsTooMany => "too many threads", Error::TimeTooSmall => "time cost is too small", Error::VersionInvalid => "invalid version", }) } } impl From for Error { fn from(err: base64ct::Error) -> Error { Error::B64Encoding(err) } } #[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::AdTooLong => InvalidValue::TooLong.param_error(), Error::AlgorithmInvalid => password_hash::Error::Algorithm, Error::B64Encoding(inner) => password_hash::Error::B64Encoding(inner), Error::KeyIdTooLong => InvalidValue::TooLong.param_error(), Error::MemoryTooLittle => InvalidValue::TooShort.param_error(), Error::MemoryTooMuch => InvalidValue::TooLong.param_error(), Error::PwdTooLong => password_hash::Error::Password, Error::OutputTooShort => password_hash::Error::OutputSize { provided: Ordering::Less, expected: Params::MIN_OUTPUT_LEN, }, Error::OutputTooLong => password_hash::Error::OutputSize { provided: Ordering::Greater, expected: Params::MAX_OUTPUT_LEN, }, Error::SaltTooShort => InvalidValue::TooShort.salt_error(), Error::SaltTooLong => InvalidValue::TooLong.salt_error(), Error::SecretTooLong => InvalidValue::TooLong.param_error(), Error::ThreadsTooFew => InvalidValue::TooShort.param_error(), Error::ThreadsTooMany => InvalidValue::TooLong.param_error(), Error::TimeTooSmall => InvalidValue::TooShort.param_error(), Error::VersionInvalid => password_hash::Error::Version, } } } #[cfg(feature = "std")] impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::B64Encoding(err) => Some(err), _ => None, } } } argon2-0.5.3/src/lib.rs000064400000000000000000000553741046102023000127420ustar 00000000000000#![no_std] #![cfg_attr(docsrs, feature(doc_cfg))] #![doc = include_str!("../README.md")] #![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" )] #![warn( clippy::cast_lossless, clippy::cast_possible_truncation, clippy::cast_possible_wrap, clippy::cast_precision_loss, clippy::cast_sign_loss, clippy::checked_conversions, clippy::implicit_saturating_sub, clippy::panic, clippy::panic_in_result_fn, clippy::unwrap_used, missing_docs, rust_2018_idioms, unused_lifetimes, unused_qualifications )] //! ## Usage //! //! ### Password Hashing //! //! This API hashes a password to a "PHC string" suitable for the purposes of //! password-based authentication. Do not use this API to derive cryptographic //! keys: see the "key derivation" usage example below. //! #![cfg_attr(feature = "std", doc = "```")] #![cfg_attr(not(feature = "std"), doc = "```ignore")] //! # fn main() -> Result<(), Box> { //! use argon2::{ //! password_hash::{ //! rand_core::OsRng, //! PasswordHash, PasswordHasher, PasswordVerifier, SaltString //! }, //! Argon2 //! }; //! //! let password = b"hunter42"; // Bad password; don't actually use! //! let salt = SaltString::generate(&mut OsRng); //! //! // Argon2 with default params (Argon2id v19) //! let argon2 = Argon2::default(); //! //! // Hash password to PHC string ($argon2id$v=19$...) //! let password_hash = argon2.hash_password(password, &salt)?.to_string(); //! //! // Verify password against PHC string. //! // //! // NOTE: hash params from `parsed_hash` are used instead of what is configured in the //! // `Argon2` instance. //! let parsed_hash = PasswordHash::new(&password_hash)?; //! assert!(Argon2::default().verify_password(password, &parsed_hash).is_ok()); //! # Ok(()) //! # } //! ``` //! //! ### Key Derivation //! //! This API is useful for transforming a password into cryptographic keys for //! e.g. password-based encryption. //! #![cfg_attr(feature = "std", doc = "```")] #![cfg_attr(not(feature = "std"), doc = "```ignore")] //! # fn main() -> Result<(), Box> { //! use argon2::Argon2; //! //! let password = b"hunter42"; // Bad password; don't actually use! //! let salt = b"example salt"; // Salt should be unique per password //! //! let mut output_key_material = [0u8; 32]; // Can be any desired size //! Argon2::default().hash_password_into(password, salt, &mut output_key_material)?; //! # Ok(()) //! # } //! ``` // Call sites which cast `u32` to `usize` and are annotated with // allow(clippy::cast_possible_truncation) need this check to avoid truncation. #[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))] compile_error!("this crate builds on 32-bit and 64-bit platforms only"); #[cfg(feature = "alloc")] #[macro_use] extern crate alloc; #[cfg(feature = "std")] extern crate std; mod algorithm; mod blake2b_long; mod block; mod error; mod params; mod version; pub use crate::{ algorithm::Algorithm, block::Block, error::{Error, Result}, params::{AssociatedData, KeyId, Params, ParamsBuilder}, version::Version, }; #[cfg(feature = "password-hash")] #[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))] pub use { crate::algorithm::{ARGON2D_IDENT, ARGON2ID_IDENT, ARGON2I_IDENT}, password_hash::{self, PasswordHash, PasswordHasher, PasswordVerifier}, }; use crate::blake2b_long::blake2b_long; use blake2::{digest, Blake2b512, Digest}; use core::fmt; #[cfg(all(feature = "alloc", feature = "password-hash"))] use password_hash::{Decimal, Ident, ParamsString, Salt}; #[cfg(feature = "zeroize")] use zeroize::Zeroize; /// Maximum password length in bytes. pub const MAX_PWD_LEN: usize = 0xFFFFFFFF; /// Minimum salt length in bytes. pub const MIN_SALT_LEN: usize = 8; /// Maximum salt length in bytes. pub const MAX_SALT_LEN: usize = 0xFFFFFFFF; /// Recommended salt length for password hashing in bytes. pub const RECOMMENDED_SALT_LEN: usize = 16; /// Maximum secret key length in bytes. pub const MAX_SECRET_LEN: usize = 0xFFFFFFFF; /// Number of synchronization points between lanes per pass pub(crate) const SYNC_POINTS: usize = 4; /// To generate reference block positions const ADDRESSES_IN_BLOCK: usize = 128; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] cpufeatures::new!(avx2_cpuid, "avx2"); /// Argon2 context. /// /// This is the primary type of this crate's API, and contains the following: /// /// - Argon2 [`Algorithm`] variant to be used /// - Argon2 [`Version`] to be used /// - Default set of [`Params`] to be used /// - (Optional) Secret key a.k.a. "pepper" to be used #[derive(Clone)] pub struct Argon2<'key> { /// Algorithm to use algorithm: Algorithm, /// Version number version: Version, /// Algorithm parameters params: Params, /// Key array secret: Option<&'key [u8]>, #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] cpu_feat_avx2: avx2_cpuid::InitToken, } impl Default for Argon2<'_> { fn default() -> Self { Self::new(Algorithm::default(), Version::default(), Params::default()) } } impl fmt::Debug for Argon2<'_> { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_struct("Argon2") .field("algorithm", &self.algorithm) .field("version", &self.version) .field("params", &self.params) .finish_non_exhaustive() } } impl<'key> Argon2<'key> { /// Create a new Argon2 context. pub fn new(algorithm: Algorithm, version: Version, params: Params) -> Self { Self { algorithm, version, params, secret: None, #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] cpu_feat_avx2: avx2_cpuid::init(), } } /// Create a new Argon2 context. pub fn new_with_secret( secret: &'key [u8], algorithm: Algorithm, version: Version, params: Params, ) -> Result { if MAX_SECRET_LEN < secret.len() { return Err(Error::SecretTooLong); } Ok(Self { algorithm, version, params, secret: Some(secret), #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] cpu_feat_avx2: avx2_cpuid::init(), }) } /// Hash a password and associated parameters into the provided output buffer. #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] pub fn hash_password_into(&self, pwd: &[u8], salt: &[u8], out: &mut [u8]) -> Result<()> { let mut blocks = vec![Block::default(); self.params.block_count()]; self.hash_password_into_with_memory(pwd, salt, out, &mut blocks) } /// Hash a password and associated parameters into the provided output buffer. /// /// 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 [`Argon2::hash_password_into`] /// to have it allocated for them. /// - `no_std` users on "heapless" targets can use an array of the [`Block`] type /// to stack allocate this buffer. pub fn hash_password_into_with_memory( &self, pwd: &[u8], salt: &[u8], out: &mut [u8], mut memory_blocks: impl AsMut<[Block]>, ) -> Result<()> { // Validate output length if out.len() < self.params.output_len().unwrap_or(Params::MIN_OUTPUT_LEN) { return Err(Error::OutputTooShort); } if out.len() > self.params.output_len().unwrap_or(Params::MAX_OUTPUT_LEN) { return Err(Error::OutputTooLong); } Self::verify_inputs(pwd, salt)?; // Hashing all inputs let initial_hash = self.initial_hash(pwd, salt, out); self.fill_blocks(memory_blocks.as_mut(), initial_hash)?; self.finalize(memory_blocks.as_mut(), out) } /// Use a password and associated parameters only to fill the given memory blocks. /// /// This method omits the calculation of a hash and can be used when only the /// filled memory is required. It is not necessary to call this method /// before calling any of the hashing functions. pub fn fill_memory( &self, pwd: &[u8], salt: &[u8], mut memory_blocks: impl AsMut<[Block]>, ) -> Result<()> { Self::verify_inputs(pwd, salt)?; let initial_hash = self.initial_hash(pwd, salt, &[]); self.fill_blocks(memory_blocks.as_mut(), initial_hash) } #[allow(clippy::cast_possible_truncation, unused_mut)] fn fill_blocks( &self, memory_blocks: &mut [Block], mut initial_hash: digest::Output, ) -> Result<()> { let block_count = self.params.block_count(); let memory_blocks = memory_blocks .get_mut(..block_count) .ok_or(Error::MemoryTooLittle)?; let segment_length = self.params.segment_length(); let iterations = self.params.t_cost() as usize; let lane_length = self.params.lane_length(); let lanes = self.params.lanes(); // Initialize the first two blocks in each lane for (l, lane) in memory_blocks.chunks_exact_mut(lane_length).enumerate() { for (i, block) in lane[..2].iter_mut().enumerate() { let i = i as u32; let l = l as u32; // Make the first and second block in each lane as G(H0||0||i) or // G(H0||1||i) let inputs = &[ initial_hash.as_ref(), &i.to_le_bytes()[..], &l.to_le_bytes()[..], ]; let mut hash = [0u8; Block::SIZE]; blake2b_long(inputs, &mut hash)?; block.load(&hash); } } #[cfg(feature = "zeroize")] initial_hash.zeroize(); // Run passes on blocks for pass in 0..iterations { for slice in 0..SYNC_POINTS { let data_independent_addressing = self.algorithm == Algorithm::Argon2i || (self.algorithm == Algorithm::Argon2id && pass == 0 && slice < SYNC_POINTS / 2); for lane in 0..lanes { let mut address_block = Block::default(); let mut input_block = Block::default(); let zero_block = Block::default(); if data_independent_addressing { input_block.as_mut()[..6].copy_from_slice(&[ pass as u64, lane as u64, slice as u64, memory_blocks.len() as u64, iterations as u64, self.algorithm as u64, ]); } let first_block = if pass == 0 && slice == 0 { if data_independent_addressing { // Generate first set of addresses self.update_address_block( &mut address_block, &mut input_block, &zero_block, ); } // The first two blocks of each lane are already initialized 2 } else { 0 }; let mut cur_index = lane * lane_length + slice * segment_length + first_block; let mut prev_index = if slice == 0 && first_block == 0 { // Last block in current lane cur_index + lane_length - 1 } else { // Previous block cur_index - 1 }; // Fill blocks in the segment for block in first_block..segment_length { // Extract entropy let rand = if data_independent_addressing { let addres_index = block % ADDRESSES_IN_BLOCK; if addres_index == 0 { self.update_address_block( &mut address_block, &mut input_block, &zero_block, ); } address_block.as_ref()[addres_index] } else { memory_blocks[prev_index].as_ref()[0] }; // Calculate source block index for compress function let ref_lane = if pass == 0 && slice == 0 { // Cannot reference other lanes yet lane } else { (rand >> 32) as usize % lanes }; let reference_area_size = if pass == 0 { // First pass if slice == 0 { // First slice block - 1 // all but the previous } else if ref_lane == lane { // The same lane => add current segment slice * segment_length + block - 1 } else { slice * segment_length - if block == 0 { 1 } else { 0 } } } else { // Second pass if ref_lane == lane { lane_length - segment_length + block - 1 } else { lane_length - segment_length - if block == 0 { 1 } else { 0 } } }; // 1.2.4. Mapping rand to 0.. and produce // relative position let mut map = rand & 0xFFFFFFFF; map = (map * map) >> 32; let relative_position = reference_area_size - 1 - ((reference_area_size as u64 * map) >> 32) as usize; // 1.2.5 Computing starting position let start_position = if pass != 0 && slice != SYNC_POINTS - 1 { (slice + 1) * segment_length } else { 0 }; let lane_index = (start_position + relative_position) % lane_length; let ref_index = ref_lane * lane_length + lane_index; // Calculate new block let result = self.compress(&memory_blocks[prev_index], &memory_blocks[ref_index]); if self.version == Version::V0x10 || pass == 0 { memory_blocks[cur_index] = result; } else { memory_blocks[cur_index] ^= &result; }; prev_index = cur_index; cur_index += 1; } } } } Ok(()) } fn compress(&self, rhs: &Block, lhs: &Block) -> Block { #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] { /// Enable AVX2 optimizations. #[target_feature(enable = "avx2")] unsafe fn compress_avx2(rhs: &Block, lhs: &Block) -> Block { Block::compress(rhs, lhs) } if self.cpu_feat_avx2.get() { return unsafe { compress_avx2(rhs, lhs) }; } } Block::compress(rhs, lhs) } /// Get default configured [`Params`]. pub const fn params(&self) -> &Params { &self.params } fn finalize(&self, memory_blocks: &[Block], out: &mut [u8]) -> Result<()> { let lane_length = self.params.lane_length(); let mut blockhash = memory_blocks[lane_length - 1]; // XOR the last blocks for l in 1..self.params.lanes() { let last_block_in_lane = l * lane_length + (lane_length - 1); blockhash ^= &memory_blocks[last_block_in_lane]; } // Hash the result let mut blockhash_bytes = [0u8; Block::SIZE]; for (chunk, v) in blockhash_bytes.chunks_mut(8).zip(blockhash.iter()) { chunk.copy_from_slice(&v.to_le_bytes()) } blake2b_long(&[&blockhash_bytes], out)?; #[cfg(feature = "zeroize")] { blockhash.zeroize(); blockhash_bytes.zeroize(); } Ok(()) } fn update_address_block( &self, address_block: &mut Block, input_block: &mut Block, zero_block: &Block, ) { input_block.as_mut()[6] += 1; *address_block = self.compress(zero_block, input_block); *address_block = self.compress(zero_block, address_block); } /// Hashes all the inputs into `blockhash[PREHASH_DIGEST_LEN]`. #[allow(clippy::cast_possible_truncation)] fn initial_hash(&self, pwd: &[u8], salt: &[u8], out: &[u8]) -> digest::Output { let mut digest = Blake2b512::new(); digest.update(self.params.p_cost().to_le_bytes()); digest.update((out.len() as u32).to_le_bytes()); digest.update(self.params.m_cost().to_le_bytes()); digest.update(self.params.t_cost().to_le_bytes()); digest.update(self.version.to_le_bytes()); digest.update(self.algorithm.to_le_bytes()); digest.update((pwd.len() as u32).to_le_bytes()); digest.update(pwd); digest.update((salt.len() as u32).to_le_bytes()); digest.update(salt); if let Some(secret) = &self.secret { digest.update((secret.len() as u32).to_le_bytes()); digest.update(secret); } else { digest.update(0u32.to_le_bytes()); } digest.update((self.params.data().len() as u32).to_le_bytes()); digest.update(self.params.data()); digest.finalize() } const fn verify_inputs(pwd: &[u8], salt: &[u8]) -> Result<()> { if pwd.len() > MAX_PWD_LEN { return Err(Error::PwdTooLong); } // Validate salt (required param) if salt.len() < MIN_SALT_LEN { return Err(Error::SaltTooShort); } if salt.len() > MAX_SALT_LEN { return Err(Error::SaltTooLong); } Ok(()) } } #[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 Argon2<'_> { type Params = Params; fn hash_password<'a>( &self, password: &[u8], salt: impl Into>, ) -> password_hash::Result> { let salt = salt.into(); let mut salt_arr = [0u8; 64]; let salt_bytes = salt.decode_b64(&mut salt_arr)?; let output_len = self .params .output_len() .unwrap_or(Params::DEFAULT_OUTPUT_LEN); let output = password_hash::Output::init_with(output_len, |out| { Ok(self.hash_password_into(password, salt_bytes, out)?) })?; Ok(PasswordHash { algorithm: self.algorithm.ident(), version: Some(self.version.into()), 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> { let algorithm = alg_id .map(Algorithm::try_from) .transpose()? .unwrap_or_default(); let version = version .map(Version::try_from) .transpose()? .unwrap_or_default(); let salt = salt.into(); Self { secret: self.secret, algorithm, version, params, #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] cpu_feat_avx2: self.cpu_feat_avx2, } .hash_password(password, salt) } } impl<'key> From for Argon2<'key> { fn from(params: Params) -> Self { Self::new(Algorithm::default(), Version::default(), params) } } impl<'key> From<&Params> for Argon2<'key> { fn from(params: &Params) -> Self { Self::from(params.clone()) } } #[cfg(all(test, feature = "alloc", feature = "password-hash"))] #[allow(clippy::unwrap_used)] mod tests { use crate::{Algorithm, Argon2, Params, PasswordHasher, Salt, Version}; /// 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 = "examplesaltvalue"; #[test] fn decoded_salt_too_short() { let argon2 = Argon2::default(); // Too short after decoding let salt = Salt::from_b64("somesalt").unwrap(); let res = argon2.hash_password_customized(EXAMPLE_PASSWORD, None, None, Params::default(), salt); assert_eq!( res, Err(password_hash::Error::SaltInvalid( password_hash::errors::InvalidValue::TooShort )) ); } #[test] fn hash_simple_retains_configured_params() { // Non-default but valid parameters let t_cost = 4; let m_cost = 2048; let p_cost = 2; let version = Version::V0x10; let params = Params::new(m_cost, t_cost, p_cost, None).unwrap(); let hasher = Argon2::new(Algorithm::default(), version, params); let salt = Salt::from_b64(EXAMPLE_SALT).unwrap(); let hash = hasher.hash_password(EXAMPLE_PASSWORD, salt).unwrap(); assert_eq!(hash.version.unwrap(), version.into()); for &(param, value) in &[("t", t_cost), ("m", m_cost), ("p", p_cost)] { assert_eq!( hash.params .get(param) .and_then(|p| p.decimal().ok()) .unwrap(), value, ); } } } argon2-0.5.3/src/params.rs000064400000000000000000000371621046102023000134520ustar 00000000000000//! Argon2 password hash parameters. use crate::{Algorithm, Argon2, Error, Result, Version, SYNC_POINTS}; use base64ct::{Base64Unpadded as B64, Encoding}; use core::str::FromStr; #[cfg(feature = "password-hash")] use password_hash::{ParamsString, PasswordHash}; /// Argon2 password hash parameters. /// /// These are parameters which can be encoded into a PHC hash string. #[derive(Clone, Debug, Eq, PartialEq)] pub struct Params { /// Memory size, expressed in kibibytes, between 8\*`p_cost` and (2^32)-1. /// /// Value is an integer in decimal (1 to 10 digits). m_cost: u32, /// Number of iterations, between 1 and (2^32)-1. /// /// Value is an integer in decimal (1 to 10 digits). t_cost: u32, /// Degree of parallelism, between 1 and (2^24)-1. /// /// Value is an integer in decimal (1 to 8 digits). p_cost: u32, /// Key identifier. keyid: KeyId, /// Associated data. data: AssociatedData, /// Size of the output (in bytes). output_len: Option, } impl Params { /// Default memory cost. pub const DEFAULT_M_COST: u32 = 19 * 1024; /// Minimum number of 1 KiB memory blocks. #[allow(clippy::cast_possible_truncation)] pub const MIN_M_COST: u32 = 2 * SYNC_POINTS as u32; // 2 blocks per slice /// Maximum number of 1 KiB memory blocks. pub const MAX_M_COST: u32 = u32::MAX; /// Default number of iterations (i.e. "time"). pub const DEFAULT_T_COST: u32 = 2; /// Minimum number of passes. pub const MIN_T_COST: u32 = 1; /// Maximum number of passes. pub const MAX_T_COST: u32 = u32::MAX; /// Default degree of parallelism. pub const DEFAULT_P_COST: u32 = 1; /// Minimum and maximum number of threads (i.e. parallelism). pub const MIN_P_COST: u32 = 1; /// Minimum and maximum number of threads (i.e. parallelism). pub const MAX_P_COST: u32 = 0xFFFFFF; /// Maximum length of a key ID in bytes. pub const MAX_KEYID_LEN: usize = 8; /// Maximum length of associated data in bytes. pub const MAX_DATA_LEN: usize = 32; /// Default output length. pub const DEFAULT_OUTPUT_LEN: usize = 32; /// Minimum digest size in bytes. pub const MIN_OUTPUT_LEN: usize = 4; /// Maximum digest size in bytes. pub const MAX_OUTPUT_LEN: usize = 0xFFFFFFFF; /// Default parameters (recommended). pub const DEFAULT: Self = Params { m_cost: Self::DEFAULT_M_COST, t_cost: Self::DEFAULT_T_COST, p_cost: Self::DEFAULT_P_COST, keyid: KeyId { bytes: [0u8; Self::MAX_KEYID_LEN], len: 0, }, data: AssociatedData { bytes: [0u8; Self::MAX_DATA_LEN], len: 0, }, output_len: None, }; /// Create new parameters. /// /// # Arguments /// - `m_cost`: memory size in 1 KiB blocks. Between 8\*`p_cost` and (2^32)-1. /// - `t_cost`: number of iterations. Between 1 and (2^32)-1. /// - `p_cost`: degree of parallelism. Between 1 and (2^24)-1. /// - `output_len`: size of the KDF output in bytes. Default 32. pub const fn new( m_cost: u32, t_cost: u32, p_cost: u32, output_len: Option, ) -> Result { if m_cost < Params::MIN_M_COST { return Err(Error::MemoryTooLittle); } // Note: we don't need to check `MAX_M_COST`, since it's `u32::MAX` if m_cost < p_cost * 8 { return Err(Error::MemoryTooLittle); } if t_cost < Params::MIN_T_COST { return Err(Error::TimeTooSmall); } // Note: we don't need to check `MAX_T_COST`, since it's `u32::MAX` if p_cost < Params::MIN_P_COST { return Err(Error::ThreadsTooFew); } if p_cost > Params::MAX_P_COST { return Err(Error::ThreadsTooMany); } if let Some(len) = output_len { if len < Params::MIN_OUTPUT_LEN { return Err(Error::OutputTooShort); } if len > Params::MAX_OUTPUT_LEN { return Err(Error::OutputTooLong); } } Ok(Params { m_cost, t_cost, p_cost, keyid: KeyId::EMPTY, data: AssociatedData::EMPTY, output_len, }) } /// Memory size, expressed in kibibytes. Between 8\*`p_cost` and (2^32)-1. /// /// Value is an integer in decimal (1 to 10 digits). pub const fn m_cost(&self) -> u32 { self.m_cost } /// Number of iterations. Between 1 and (2^32)-1. /// /// Value is an integer in decimal (1 to 10 digits). pub const fn t_cost(&self) -> u32 { self.t_cost } /// Degree of parallelism. Between 1 and (2^24)-1. /// /// Value is an integer in decimal (1 to 3 digits). pub const fn p_cost(&self) -> u32 { self.p_cost } /// Key identifier: byte slice between 0 and 8 bytes in length. /// /// Defaults to an empty byte slice. /// /// Note this field is only present as a helper for reading/storing in /// the PHC hash string format (i.e. it is totally ignored from a /// cryptographical standpoint). /// /// On top of that, this field is not longer part of the Argon2 standard /// (see: ), and should /// not be used for any non-legacy work. pub fn keyid(&self) -> &[u8] { self.keyid.as_bytes() } /// Associated data: byte slice between 0 and 32 bytes in length. /// /// Defaults to an empty byte slice. /// /// This field is not longer part of the argon2 standard /// (see: ), and should /// not be used for any non-legacy work. pub fn data(&self) -> &[u8] { self.data.as_bytes() } /// Length of the output (in bytes). pub const fn output_len(&self) -> Option { self.output_len } /// Get the number of lanes. #[allow(clippy::cast_possible_truncation)] pub(crate) const fn lanes(&self) -> usize { self.p_cost as usize } /// Get the number of blocks in a lane. pub(crate) const fn lane_length(&self) -> usize { self.segment_length() * SYNC_POINTS } /// Get the segment length given the configured `m_cost` and `p_cost`. /// /// Minimum memory_blocks = 8*`L` blocks, where `L` is the number of lanes. pub(crate) const fn segment_length(&self) -> usize { let m_cost = self.m_cost as usize; let memory_blocks = if m_cost < 2 * SYNC_POINTS * self.lanes() { 2 * SYNC_POINTS * self.lanes() } else { m_cost }; memory_blocks / (self.lanes() * SYNC_POINTS) } /// Get the number of blocks required given the configured `m_cost` and `p_cost`. pub const fn block_count(&self) -> usize { self.segment_length() * self.lanes() * SYNC_POINTS } } impl Default for Params { fn default() -> Params { Params::DEFAULT } } macro_rules! param_buf { ($ty:ident, $name:expr, $max_len:expr, $error:expr, $doc:expr) => { #[doc = $doc] #[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] pub struct $ty { /// Byte array bytes: [u8; Self::MAX_LEN], /// Length of byte array len: usize, } impl $ty { /// Maximum length in bytes pub const MAX_LEN: usize = $max_len; #[doc = "Create a new"] #[doc = $name] #[doc = "from a slice."] pub fn new(slice: &[u8]) -> Result { let mut bytes = [0u8; Self::MAX_LEN]; let len = slice.len(); bytes.get_mut(..len).ok_or($error)?.copy_from_slice(slice); Ok(Self { bytes, len }) } /// Empty value. pub const EMPTY: Self = Self { bytes: [0u8; Self::MAX_LEN], len: 0, }; #[doc = "Decode"] #[doc = $name] #[doc = " from a B64 string"] pub fn from_b64(s: &str) -> Result { let mut bytes = [0u8; Self::MAX_LEN]; let len = B64::decode(s, &mut bytes)?.len(); Ok(Self { bytes, len }) } /// Borrow the inner value as a byte slice. pub fn as_bytes(&self) -> &[u8] { &self.bytes[..self.len] } /// Get the length in bytes. pub const fn len(&self) -> usize { self.len } /// Is this value empty? pub const fn is_empty(&self) -> bool { self.len() == 0 } } impl AsRef<[u8]> for $ty { fn as_ref(&self) -> &[u8] { self.as_bytes() } } impl FromStr for $ty { type Err = Error; fn from_str(s: &str) -> Result { Self::from_b64(s) } } impl TryFrom<&[u8]> for $ty { type Error = Error; fn try_from(bytes: &[u8]) -> Result { Self::new(bytes) } } }; } // KeyId param_buf!( KeyId, "KeyId", Params::MAX_KEYID_LEN, Error::KeyIdTooLong, "Key identifier" ); // AssociatedData param_buf!( AssociatedData, "AssociatedData", Params::MAX_DATA_LEN, Error::AdTooLong, "Associated data" ); #[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 builder = ParamsBuilder::new(); for (ident, value) in hash.params.iter() { match ident.as_str() { "m" => { builder.m_cost(value.decimal()?); } "t" => { builder.t_cost(value.decimal()?); } "p" => { builder.p_cost(value.decimal()?); } "keyid" => { builder.keyid(value.as_str().parse()?); } "data" => { builder.data(value.as_str().parse()?); } _ => return Err(password_hash::Error::ParamNameInvalid), } } if let Some(output) = &hash.hash { builder.output_len(output.len()); } Ok(builder.build()?) } } #[cfg(feature = "password-hash")] #[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))] impl 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 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("m", params.m_cost)?; output.add_decimal("t", params.t_cost)?; output.add_decimal("p", params.p_cost)?; if !params.keyid.is_empty() { output.add_b64_bytes("keyid", params.keyid.as_bytes())?; } if !params.data.is_empty() { output.add_b64_bytes("data", params.data.as_bytes())?; } Ok(output) } } /// Builder for Argon2 [`Params`]. #[derive(Clone, Debug, Eq, PartialEq)] pub struct ParamsBuilder { m_cost: u32, t_cost: u32, p_cost: u32, keyid: Option, data: Option, output_len: Option, } impl ParamsBuilder { /// Create a new builder with the default parameters. pub const fn new() -> Self { Self::DEFAULT } /// Set memory size, expressed in kibibytes, between 8\*`p_cost` and (2^32)-1. pub fn m_cost(&mut self, m_cost: u32) -> &mut Self { self.m_cost = m_cost; self } /// Set number of iterations, between 1 and (2^32)-1. pub fn t_cost(&mut self, t_cost: u32) -> &mut Self { self.t_cost = t_cost; self } /// Set degree of parallelism, between 1 and (2^24)-1. pub fn p_cost(&mut self, p_cost: u32) -> &mut Self { self.p_cost = p_cost; self } /// Set key identifier. pub fn keyid(&mut self, keyid: KeyId) -> &mut Self { self.keyid = Some(keyid); self } /// Set associated data. pub fn data(&mut self, data: AssociatedData) -> &mut Self { self.data = Some(data); self } /// Set length of the output (in bytes). pub fn output_len(&mut self, len: usize) -> &mut Self { self.output_len = Some(len); self } /// Get the finished [`Params`]. /// /// This performs validations to ensure that the given parameters are valid /// and compatible with each other, and will return an error if they are not. pub const fn build(&self) -> Result { let mut params = match Params::new(self.m_cost, self.t_cost, self.p_cost, self.output_len) { Ok(params) => params, Err(err) => return Err(err), }; if let Some(keyid) = self.keyid { params.keyid = keyid; } if let Some(data) = self.data { params.data = data; }; Ok(params) } /// Create a new [`Argon2`] context using the provided algorithm/version. pub fn context(&self, algorithm: Algorithm, version: Version) -> Result> { Ok(Argon2::new(algorithm, version, self.build()?)) } /// Default parameters (recommended). pub const DEFAULT: ParamsBuilder = { let params = Params::DEFAULT; Self { m_cost: params.m_cost, t_cost: params.t_cost, p_cost: params.p_cost, keyid: None, data: None, output_len: params.output_len, } }; } impl Default for ParamsBuilder { fn default() -> Self { Self::DEFAULT } } impl TryFrom for Params { type Error = Error; fn try_from(builder: ParamsBuilder) -> Result { builder.build() } } #[cfg(all(test, feature = "alloc", feature = "password-hash"))] mod tests { use super::*; #[test] fn params_builder_bad_values() { assert_eq!( ParamsBuilder::new().m_cost(Params::MIN_M_COST - 1).build(), Err(Error::MemoryTooLittle) ); assert_eq!( ParamsBuilder::new().t_cost(Params::MIN_T_COST - 1).build(), Err(Error::TimeTooSmall) ); assert_eq!( ParamsBuilder::new().p_cost(Params::MIN_P_COST - 1).build(), Err(Error::ThreadsTooFew) ); assert_eq!( ParamsBuilder::new() .m_cost(Params::DEFAULT_P_COST * 8 - 1) .build(), Err(Error::MemoryTooLittle) ); assert_eq!( ParamsBuilder::new() .m_cost((Params::MAX_P_COST + 1) * 8) .p_cost(Params::MAX_P_COST + 1) .build(), Err(Error::ThreadsTooMany) ); } #[test] fn associated_data_too_long() { let ret = AssociatedData::new(&[0u8; Params::MAX_DATA_LEN + 1]); assert_eq!(ret, Err(Error::AdTooLong)); } #[test] fn keyid_too_long() { let ret = KeyId::new(&[0u8; Params::MAX_KEYID_LEN + 1]); assert_eq!(ret, Err(Error::KeyIdTooLong)); } } argon2-0.5.3/src/version.rs000064400000000000000000000016721046102023000136510ustar 00000000000000//! Version of the algorithm. use crate::{Error, Result}; /// Version of the algorithm. #[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)] #[repr(u32)] pub enum Version { /// Version 16 (0x10 in hex) /// /// Performs overwrite internally V0x10 = 0x10, /// Version 19 (0x13 in hex, default) /// /// Performs XOR internally #[default] V0x13 = 0x13, } impl Version { /// Serialize version as little endian bytes pub(crate) const fn to_le_bytes(self) -> [u8; 4] { (self as u32).to_le_bytes() } } impl From for u32 { fn from(version: Version) -> u32 { version as u32 } } impl TryFrom for Version { type Error = Error; fn try_from(version_id: u32) -> Result { match version_id { 0x10 => Ok(Version::V0x10), 0x13 => Ok(Version::V0x13), _ => Err(Error::VersionInvalid), } } } argon2-0.5.3/tests/kat.rs000064400000000000000000000621131046102023000133130ustar 00000000000000//! Argon2 Known Answer Tests (KAT). //! //! Taken from the Argon2 reference implementation as well as //! `draft-irtf-cfrg-argon2-12` Section 5: //! #![cfg(all(feature = "alloc", feature = "password-hash"))] // TODO(tarcieri): test full set of vectors from the reference implementation: // https://github.com/P-H-C/phc-winner-argon2/blob/master/src/test.c use argon2::{ Algorithm, Argon2, AssociatedData, Error, Params, ParamsBuilder, PasswordHash, PasswordHasher, PasswordVerifier, Version, }; use hex_literal::hex; use password_hash::SaltString; /// Params used by the KATs. fn example_params() -> Params { ParamsBuilder::new() .m_cost(32) .t_cost(3) .p_cost(4) .data(AssociatedData::new(&[0x04; 12]).unwrap()) .build() .unwrap() } /// ======================================= /// Argon2d version number 16 /// ======================================= /// Memory: 32 KiB, Iterations: 3, Parallelism: 4 lanes, Tag length: 32 bytes /// Password[32]: /// 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 /// 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 /// Salt[16]: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 /// Secret[8]: 03 03 03 03 03 03 03 03 /// Associated data[12]: 04 04 04 04 04 04 04 04 04 04 04 04 /// Pre-hashing digest: /// ec dc 26 dc 6b dd 21 56 19 68 97 aa 8c c9 a0 4c /// 03 ed 07 cd 12 92 67 c5 3c a6 ae f7 76 a4 30 89 /// 6a 09 80 54 e4 de c3 e0 2e cd 82 c4 7f 56 2c a2 /// 73 d2 f6 97 8a 5c 05 41 1a 0c d0 9d 47 7b 7b 06 /// Tag[32]: /// 96 a9 d4 e5 a1 73 40 92 c8 5e 29 f4 10 a4 59 14 /// a5 dd 1f 5c bf 08 b2 67 0d a6 8a 02 85 ab f3 2b #[test] fn argon2d_v0x10() { let algorithm = Algorithm::Argon2d; let version = Version::V0x10; let params = example_params(); let password = [0x01; 32]; let salt = [0x02; 16]; let secret = [0x03; 8]; let expected_tag = hex!( " 96 a9 d4 e5 a1 73 40 92 c8 5e 29 f4 10 a4 59 14 a5 dd 1f 5c bf 08 b2 67 0d a6 8a 02 85 ab f3 2b " ); let ctx = Argon2::new_with_secret(&secret, algorithm, version, params).unwrap(); let mut out = [0u8; 32]; ctx.hash_password_into(&password, &salt, &mut out).unwrap(); assert_eq!(out, expected_tag); } /// ======================================= /// Argon2i version number 16 /// ======================================= /// Memory: 32 KiB, Iterations: 3, Parallelism: 4 lanes, Tag length: 32 bytes /// Password[32]: /// 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 /// 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 /// Salt[16]: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 /// Secret[8]: 03 03 03 03 03 03 03 03 /// Associated data[12]: 04 04 04 04 04 04 04 04 04 04 04 04 /// Pre-hashing digest: /// 1c dc ec c8 58 ca 1b 6d 45 c7 3c 78 d0 00 76 c5 /// ec fc 5e df 14 45 b4 43 73 97 b1 b8 20 83 ff bf /// e3 c9 1a a8 f5 06 67 ad 8f b9 d4 e7 52 df b3 85 /// 34 71 9f ba d2 22 61 33 7b 2b 55 29 81 44 09 af /// Tag[32]: /// 87 ae ed d6 51 7a b8 30 cd 97 65 cd 82 31 ab b2 /// e6 47 a5 de e0 8f 7c 05 e0 2f cb 76 33 35 d0 fd #[test] fn argon2i_v0x10() { let algorithm = Algorithm::Argon2i; let version = Version::V0x10; let params = example_params(); let password = [0x01; 32]; let salt = [0x02; 16]; let secret = [0x03; 8]; let expected_tag = hex!( " 87 ae ed d6 51 7a b8 30 cd 97 65 cd 82 31 ab b2 e6 47 a5 de e0 8f 7c 05 e0 2f cb 76 33 35 d0 fd " ); let ctx = Argon2::new_with_secret(&secret, algorithm, version, params).unwrap(); let mut out = [0u8; 32]; ctx.hash_password_into(&password, &salt, &mut out).unwrap(); assert_eq!(out, expected_tag); } /// ======================================= /// Argon2id version number 16 /// ======================================= /// Memory: 32 KiB, Iterations: 3, Parallelism: 4 lanes, Tag length: 32 bytes /// Password[32]: /// 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 /// 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 /// Salt[16]: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 /// Secret[8]: 03 03 03 03 03 03 03 03 /// Associated data[12]: 04 04 04 04 04 04 04 04 04 04 04 04 /// Pre-hashing digest: /// 70 65 ab 9c 82 b5 f0 e8 71 28 c7 84 7a 02 1d 1e /// 59 aa 16 66 6f c8 b4 ef ac a3 86 3f bf d6 5e 0e /// 8b a6 f6 09 eb bc 9b 60 e2 78 22 c8 24 b7 50 6f /// b9 f9 5b e9 0e e5 84 2a ac 6e d6 b7 da 67 30 44 /// Tag[32]: /// b6 46 15 f0 77 89 b6 6b 64 5b 67 ee 9e d3 b3 77 /// ae 35 0b 6b fc bb 0f c9 51 41 ea 8f 32 26 13 c0 #[test] fn argon2id_v0x10() { let algorithm = Algorithm::Argon2id; let version = Version::V0x10; let params = example_params(); let password = [0x01; 32]; let salt = [0x02; 16]; let secret = [0x03; 8]; let expected_tag = hex!( " b6 46 15 f0 77 89 b6 6b 64 5b 67 ee 9e d3 b3 77 ae 35 0b 6b fc bb 0f c9 51 41 ea 8f 32 26 13 c0 " ); let ctx = Argon2::new_with_secret(&secret, algorithm, version, params).unwrap(); let mut out = [0u8; 32]; ctx.hash_password_into(&password, &salt, &mut out).unwrap(); assert_eq!(out, expected_tag); } /// ======================================= /// Argon2d version number 19 /// ======================================= /// Memory: 32 KiB /// Passes: 3 /// Parallelism: 4 lanes /// Tag length: 32 bytes /// Password[32]: /// 01 01 01 01 01 01 01 01 /// 01 01 01 01 01 01 01 01 /// 01 01 01 01 01 01 01 01 /// 01 01 01 01 01 01 01 01 /// Salt[16]: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 /// Secret[8]: 03 03 03 03 03 03 03 03 /// Associated data[12]: 04 04 04 04 04 04 04 04 04 04 04 04 /// Pre-hashing digest: /// b8 81 97 91 a0 35 96 60 /// bb 77 09 c8 5f a4 8f 04 /// d5 d8 2c 05 c5 f2 15 cc /// db 88 54 91 71 7c f7 57 /// 08 2c 28 b9 51 be 38 14 /// 10 b5 fc 2e b7 27 40 33 /// b9 fd c7 ae 67 2b ca ac /// 5d 17 90 97 a4 af 31 09 /// Tag[32]: /// 51 2b 39 1b 6f 11 62 97 /// 53 71 d3 09 19 73 42 94 /// f8 68 e3 be 39 84 f3 c1 /// a1 3a 4d b9 fa be 4a cb #[test] fn argon2d_v0x13() { let algorithm = Algorithm::Argon2d; let version = Version::V0x13; let params = example_params(); let password = [0x01; 32]; let salt = [0x02; 16]; let secret = [0x03; 8]; let expected_tag = hex!( " 51 2b 39 1b 6f 11 62 97 53 71 d3 09 19 73 42 94 f8 68 e3 be 39 84 f3 c1 a1 3a 4d b9 fa be 4a cb " ); let ctx = Argon2::new_with_secret(&secret, algorithm, version, params).unwrap(); let mut out = [0u8; 32]; ctx.hash_password_into(&password, &salt, &mut out).unwrap(); assert_eq!(out, expected_tag); } /// ======================================= /// Argon2i version number 19 /// ======================================= /// Memory: 32 KiB /// Passes: 3 /// Parallelism: 4 lanes /// Tag length: 32 bytes /// Password[32]: /// 01 01 01 01 01 01 01 01 /// 01 01 01 01 01 01 01 01 /// 01 01 01 01 01 01 01 01 /// 01 01 01 01 01 01 01 01 /// Salt[16]: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 /// Secret[8]: 03 03 03 03 03 03 03 03 /// Associated data[12]: 04 04 04 04 04 04 04 04 04 04 04 04 /// Pre-hashing digest: /// c4 60 65 81 52 76 a0 b3 /// e7 31 73 1c 90 2f 1f d8 /// 0c f7 76 90 7f bb 7b 6a /// 5c a7 2e 7b 56 01 1f ee /// ca 44 6c 86 dd 75 b9 46 /// 9a 5e 68 79 de c4 b7 2d /// 08 63 fb 93 9b 98 2e 5f /// 39 7c c7 d1 64 fd da a9 /// Tag[32]: /// c8 14 d9 d1 dc 7f 37 aa /// 13 f0 d7 7f 24 94 bd a1 /// c8 de 6b 01 6d d3 88 d2 /// 99 52 a4 c4 67 2b 6c e8 #[test] fn argon2i_v0x13() { let algorithm = Algorithm::Argon2i; let version = Version::V0x13; let params = example_params(); let password = [0x01; 32]; let salt = [0x02; 16]; let secret = [0x03; 8]; let expected_tag = hex!( " c8 14 d9 d1 dc 7f 37 aa 13 f0 d7 7f 24 94 bd a1 c8 de 6b 01 6d d3 88 d2 99 52 a4 c4 67 2b 6c e8 " ); let ctx = Argon2::new_with_secret(&secret, algorithm, version, params).unwrap(); let mut out = [0u8; 32]; ctx.hash_password_into(&password, &salt, &mut out).unwrap(); assert_eq!(out, expected_tag); } /// ======================================= /// Argon2id version number 19 /// ======================================= /// Memory: 32 KiB, Passes: 3, /// Parallelism: 4 lanes, Tag length: 32 bytes /// Password[32]: /// 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 /// 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 /// Salt[16]: 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 /// Secret[8]: 03 03 03 03 03 03 03 03 /// Associated data[12]: 04 04 04 04 04 04 04 04 04 04 04 04 /// Pre-hashing digest: /// 28 89 de 48 7e b4 2a e5 00 c0 00 7e d9 25 2f 10 /// 69 ea de c4 0d 57 65 b4 85 de 6d c2 43 7a 67 b8 /// 54 6a 2f 0a cc 1a 08 82 db 8f cf 74 71 4b 47 2e /// 94 df 42 1a 5d a1 11 2f fa 11 43 43 70 a1 e9 97 /// Tag[32]: /// 0d 64 0d f5 8d 78 76 6c 08 c0 37 a3 4a 8b 53 c9 /// d0 1e f0 45 2d 75 b6 5e b5 25 20 e9 6b 01 e6 59 #[test] fn argon2id_v0x13() { let algorithm = Algorithm::Argon2id; let version = Version::V0x13; let params = example_params(); let password = [0x01; 32]; let salt = [0x02; 16]; let secret = [0x03; 8]; let expected_tag = hex!( " 0d 64 0d f5 8d 78 76 6c 08 c0 37 a3 4a 8b 53 c9 d0 1e f0 45 2d 75 b6 5e b5 25 20 e9 6b 01 e6 59 " ); let ctx = Argon2::new_with_secret(&secret, algorithm, version, params).unwrap(); let mut out = [0u8; 32]; ctx.hash_password_into(&password, &salt, &mut out).unwrap(); assert_eq!(out, expected_tag); } // ======================================= // Basic error checks // ======================================= #[test] fn salt_bad_length() { let ctx = Argon2::new(Algorithm::Argon2id, Version::V0x13, example_params()); let mut out = [0u8; 32]; let too_short_salt = [0u8; argon2::MIN_SALT_LEN - 1]; let ret = ctx.hash_password_into(b"password", &too_short_salt, &mut out); assert_eq!(ret, Err(Error::SaltTooShort)); #[cfg(target_pointer_width = "64")] // MAX_SALT_LEN + 1 is too big for 32-bit targets { // 4 GiB of RAM seems big, but as long as we ask for a zero-initialized vector // optimizations kicks in an nothing is really allocated let too_long_salt = vec![0u8; argon2::MAX_SALT_LEN + 1]; let ret = ctx.hash_password_into(b"password", &too_long_salt, &mut out); assert_eq!(ret, Err(Error::SaltTooLong)); } } #[test] fn output_bad_length() { let ctx = Argon2::new(Algorithm::Argon2id, Version::V0x13, example_params()); let mut out = [0u8; Params::MIN_OUTPUT_LEN - 1]; let ret = ctx.hash_password_into(b"password", b"diffsalt", &mut out); assert_eq!(ret, Err(Error::OutputTooShort)); #[cfg(target_pointer_width = "64")] // MAX_SALT_LEN + 1 is too big for 32-bit targets { // 4 GiB of RAM seems big, but as long as we ask for a zero-initialized vector // optimizations kicks in an nothing is really allocated let mut out = vec![0u8; Params::MAX_OUTPUT_LEN + 1]; let ret = ctx.hash_password_into(b"password", b"diffsalt", &mut out); assert_eq!(ret, Err(Error::OutputTooLong)); } } // ======================================= // Reference implementation's test suite // ======================================= // Taken from https://github.com/P-H-C/phc-winner-argon2/blob/master/src/test.c #[allow(clippy::too_many_arguments)] fn hashtest( algorithm: Algorithm, version: Version, t: u32, m: u32, p: u32, pwd: &[u8], salt: &[u8], expected_raw_hash: [u8; 32], expected_phc_hash: &str, alternative_phc_hash: &str, ) { let params = ParamsBuilder::new() .t_cost(t) .m_cost(1 << m) .p_cost(p) .build() .unwrap(); let ctx = Argon2::new(algorithm, version, params); // Test raw hash let mut out = [0u8; 32]; ctx.hash_password_into(pwd, salt, &mut out).unwrap(); assert_eq!(out, expected_raw_hash); // Test hash encoding let salt_string = SaltString::encode_b64(salt).unwrap(); let phc_hash = ctx.hash_password(pwd, &salt_string).unwrap().to_string(); assert_eq!(phc_hash, expected_phc_hash); let hash = PasswordHash::new(alternative_phc_hash).unwrap(); assert!(Argon2::default().verify_password(pwd, &hash).is_ok()); } macro_rules! testcase_good { ($name: ident, $algorithm: expr, $version: expr, $t: expr, $m: expr, $p: expr, $pwd: expr, $salt: expr, $expected_raw: expr, $expected_phc: expr) => { #[test] fn $name() { hashtest( $algorithm, $version, $t, $m, $p, $pwd, $salt, $expected_raw, $expected_phc, $expected_phc, ) } }; ($name: ident, $algorithm: expr, $version: expr, $t: expr, $m: expr, $p: expr, $pwd: expr, $salt: expr, $expected_raw: expr, $expected_phc: expr, $alternative_phc: expr) => { #[test] fn $name() { hashtest( $algorithm, $version, $t, $m, $p, $pwd, $salt, $expected_raw, $expected_phc, $alternative_phc, ) } }; } macro_rules! ignored_testcase_good { ($name: ident, $algorithm: expr, $version: expr, $t: expr, $m: expr, $p: expr, $pwd: expr, $salt: expr, $expected_raw: expr, $expected_phc: expr, $alternative_phc: expr) => { #[test] #[ignore] fn $name() { hashtest( $algorithm, $version, $t, $m, $p, $pwd, $salt, $expected_raw, $expected_phc, $alternative_phc, ) } }; } /* Argon2i V0x10: Multiple test cases for various input values */ // TODO: If version is not provided, verifier incorrectly uses version 0x13 ignored_testcase_good!( reference_argon2i_v0x10_2_16_1, Algorithm::Argon2i, Version::V0x10, 2, 16, 1, b"password", b"somesalt", hex!("f6c4db4a54e2a370627aff3db6176b94a2a209a62c8e36152711802f7b30c694"), "$argon2i$v=16$m=65536,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ", "$argon2i$m=65536,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ" ); // TODO: If version is not provided, verifier incorrectly uses version 0x13 #[ignore] #[cfg(feature = "test_large_ram")] testcase_good!( reference_argon2i_v0x10_2_20_1_large_ram, Algorithm::Argon2i, Version::V0x10, 2, 20, 1, b"password", b"somesalt", hex!("9690ec55d28d3ed32562f2e73ea62b02b018757643a2ae6e79528459de8106e9"), "$argon2i$v=16$m=1048576,t=2,p=1$c29tZXNhbHQ$lpDsVdKNPtMlYvLnPqYrArAYdXZDoq5ueVKEWd6BBuk", "$argon2i$m=1048576,t=2,p=1$c29tZXNhbHQ$lpDsVdKNPtMlYvLnPqYrArAYdXZDoq5ueVKEWd6BBuk" ); // TODO: If version is not provided, verifier incorrectly uses version 0x13 ignored_testcase_good!( reference_argon2i_v0x10_2_18_1, Algorithm::Argon2i, Version::V0x10, 2, 18, 1, b"password", b"somesalt", hex!("3e689aaa3d28a77cf2bc72a51ac53166761751182f1ee292e3f677a7da4c2467"), "$argon2i$v=16$m=262144,t=2,p=1$c29tZXNhbHQ$Pmiaqj0op3zyvHKlGsUxZnYXURgvHuKS4/Z3p9pMJGc", "$argon2i$m=262144,t=2,p=1$c29tZXNhbHQ$Pmiaqj0op3zyvHKlGsUxZnYXURgvHuKS4/Z3p9pMJGc" ); // TODO: If version is not provided, verifier incorrectly uses version 0x13 ignored_testcase_good!( reference_argon2i_v0x10_2_8_1, Algorithm::Argon2i, Version::V0x10, 2, 8, 1, b"password", b"somesalt", hex!("fd4dd83d762c49bdeaf57c47bdcd0c2f1babf863fdeb490df63ede9975fccf06"), "$argon2i$v=16$m=256,t=2,p=1$c29tZXNhbHQ$/U3YPXYsSb3q9XxHvc0MLxur+GP960kN9j7emXX8zwY", "$argon2i$m=256,t=2,p=1$c29tZXNhbHQ$/U3YPXYsSb3q9XxHvc0MLxur+GP960kN9j7emXX8zwY" ); // TODO: If version is not provided, verifier incorrectly uses version 0x13 ignored_testcase_good!( reference_argon2i_v0x10_2_8_2, Algorithm::Argon2i, Version::V0x10, 2, 8, 2, b"password", b"somesalt", hex!("b6c11560a6a9d61eac706b79a2f97d68b4463aa3ad87e00c07e2b01e90c564fb"), "$argon2i$v=16$m=256,t=2,p=2$c29tZXNhbHQ$tsEVYKap1h6scGt5ovl9aLRGOqOth+AMB+KwHpDFZPs", "$argon2i$m=256,t=2,p=2$c29tZXNhbHQ$tsEVYKap1h6scGt5ovl9aLRGOqOth+AMB+KwHpDFZPs" ); // TODO: If version is not provided, verifier incorrectly uses version 0x13 ignored_testcase_good!( reference_argon2i_v0x10_1_16_1, Algorithm::Argon2i, Version::V0x10, 1, 16, 1, b"password", b"somesalt", hex!("81630552b8f3b1f48cdb1992c4c678643d490b2b5eb4ff6c4b3438b5621724b2"), "$argon2i$v=16$m=65536,t=1,p=1$c29tZXNhbHQ$gWMFUrjzsfSM2xmSxMZ4ZD1JCytetP9sSzQ4tWIXJLI", "$argon2i$m=65536,t=1,p=1$c29tZXNhbHQ$gWMFUrjzsfSM2xmSxMZ4ZD1JCytetP9sSzQ4tWIXJLI" ); // TODO: If version is not provided, verifier incorrectly uses version 0x13 ignored_testcase_good!( reference_argon2i_v0x10_4_16_1, Algorithm::Argon2i, Version::V0x10, 4, 16, 1, b"password", b"somesalt", hex!("f212f01615e6eb5d74734dc3ef40ade2d51d052468d8c69440a3a1f2c1c2847b"), "$argon2i$v=16$m=65536,t=4,p=1$c29tZXNhbHQ$8hLwFhXm6110c03D70Ct4tUdBSRo2MaUQKOh8sHChHs", "$argon2i$m=65536,t=4,p=1$c29tZXNhbHQ$8hLwFhXm6110c03D70Ct4tUdBSRo2MaUQKOh8sHChHs" ); // TODO: If version is not provided, verifier incorrectly uses version 0x13 ignored_testcase_good!( reference_argon2i_v0x10_2_16_1_differentpassword, Algorithm::Argon2i, Version::V0x10, 2, 16, 1, b"differentpassword", b"somesalt", hex!("e9c902074b6754531a3a0be519e5baf404b30ce69b3f01ac3bf21229960109a3"), "$argon2i$v=16$m=65536,t=2,p=1$c29tZXNhbHQ$6ckCB0tnVFMaOgvlGeW69ASzDOabPwGsO/ISKZYBCaM", "$argon2i$m=65536,t=2,p=1$c29tZXNhbHQ$6ckCB0tnVFMaOgvlGeW69ASzDOabPwGsO/ISKZYBCaM" ); // TODO: If version is not provided, verifier incorrectly uses version 0x13 ignored_testcase_good!( reference_argon2i_v0x10_2_16_1_diffsalt, Algorithm::Argon2i, Version::V0x10, 2, 16, 1, b"password", b"diffsalt", hex!("79a103b90fe8aef8570cb31fc8b22259778916f8336b7bdac3892569d4f1c497"), "$argon2i$v=16$m=65536,t=2,p=1$ZGlmZnNhbHQ$eaEDuQ/orvhXDLMfyLIiWXeJFvgza3vaw4kladTxxJc", "$argon2i$m=65536,t=2,p=1$ZGlmZnNhbHQ$eaEDuQ/orvhXDLMfyLIiWXeJFvgza3vaw4kladTxxJc" ); /* Argon2i V0x10: Error state tests */ // TODO: If version is not provided, verifier incorrectly uses version 0x13 #[ignore] #[test] fn reference_argon2i_v0x10_mismatching_hash() { /* Handle an mismatching hash (the encoded password is "passwore") */ let hash = PasswordHash::new( "$argon2i$m=65536,t=2,p=1$c29tZXNhbHQ$b2G3seW+uPzerwQQC+/E1K50CLLO7YXy0JRcaTuswRo", ) .unwrap(); assert_eq!( Argon2::default().verify_password(b"password", &hash), Err(password_hash::errors::Error::Password) ); } /* Argon2i V0x13: Multiple test cases for various input values */ testcase_good!( reference_argon2i_v0x13_2_16_1, Algorithm::Argon2i, Version::V0x13, 2, 16, 1, b"password", b"somesalt", hex!("c1628832147d9720c5bd1cfd61367078729f6dfb6f8fea9ff98158e0d7816ed0"), "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$wWKIMhR9lyDFvRz9YTZweHKfbftvj+qf+YFY4NeBbtA" ); #[cfg(feature = "test_large_ram")] testcase_good!( reference_argon2i_v0x13_2_20_1, Algorithm::Argon2i, Version::V0x13, 2, 20, 1, b"password", b"somesalt", hex!("d1587aca0922c3b5d6a83edab31bee3c4ebaef342ed6127a55d19b2351ad1f41"), "$argon2i$v=19$m=1048576,t=2,p=1$c29tZXNhbHQ$0Vh6ygkiw7XWqD7asxvuPE667zQu1hJ6VdGbI1GtH0E" ); testcase_good!( reference_argon2i_v0x13_2_18_1, Algorithm::Argon2i, Version::V0x13, 2, 18, 1, b"password", b"somesalt", hex!("296dbae80b807cdceaad44ae741b506f14db0959267b183b118f9b24229bc7cb"), "$argon2i$v=19$m=262144,t=2,p=1$c29tZXNhbHQ$KW266AuAfNzqrUSudBtQbxTbCVkmexg7EY+bJCKbx8s" ); testcase_good!( reference_argon2i_v0x13_2_8_1, Algorithm::Argon2i, Version::V0x13, 2, 8, 1, b"password", b"somesalt", hex!("89e9029f4637b295beb027056a7336c414fadd43f6b208645281cb214a56452f"), "$argon2i$v=19$m=256,t=2,p=1$c29tZXNhbHQ$iekCn0Y3spW+sCcFanM2xBT63UP2sghkUoHLIUpWRS8" ); testcase_good!( reference_argon2i_v0x13_2_8_2, Algorithm::Argon2i, Version::V0x13, 2, 8, 2, b"password", b"somesalt", hex!("4ff5ce2769a1d7f4c8a491df09d41a9fbe90e5eb02155a13e4c01e20cd4eab61"), "$argon2i$v=19$m=256,t=2,p=2$c29tZXNhbHQ$T/XOJ2mh1/TIpJHfCdQan76Q5esCFVoT5MAeIM1Oq2E" ); testcase_good!( reference_argon2i_v0x13_1_16_1, Algorithm::Argon2i, Version::V0x13, 1, 16, 1, b"password", b"somesalt", hex!("d168075c4d985e13ebeae560cf8b94c3b5d8a16c51916b6f4ac2da3ac11bbecf"), "$argon2i$v=19$m=65536,t=1,p=1$c29tZXNhbHQ$0WgHXE2YXhPr6uVgz4uUw7XYoWxRkWtvSsLaOsEbvs8" ); testcase_good!( reference_argon2i_v0x13_4_16_1, Algorithm::Argon2i, Version::V0x13, 4, 16, 1, b"password", b"somesalt", hex!("aaa953d58af3706ce3df1aefd4a64a84e31d7f54175231f1285259f88174ce5b"), "$argon2i$v=19$m=65536,t=4,p=1$c29tZXNhbHQ$qqlT1YrzcGzj3xrv1KZKhOMdf1QXUjHxKFJZ+IF0zls" ); testcase_good!( reference_argon2i_v0x13_2_16_1_differentpassword, Algorithm::Argon2i, Version::V0x13, 2, 16, 1, b"differentpassword", b"somesalt", hex!("14ae8da01afea8700c2358dcef7c5358d9021282bd88663a4562f59fb74d22ee"), "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$FK6NoBr+qHAMI1jc73xTWNkCEoK9iGY6RWL1n7dNIu4" ); testcase_good!( reference_argon2i_v0x13_2_16_1_diffsalt, Algorithm::Argon2i, Version::V0x13, 2, 16, 1, b"password", b"diffsalt", hex!("b0357cccfbef91f3860b0dba447b2348cbefecadaf990abfe9cc40726c521271"), "$argon2i$v=19$m=65536,t=2,p=1$ZGlmZnNhbHQ$sDV8zPvvkfOGCw26RHsjSMvv7K2vmQq/6cxAcmxSEnE" ); #[test] fn reference_argon2i_v0x13_mismatching_hash() { /* Handle an mismatching hash (the encoded password is "passwore") */ let hash = PasswordHash::new( "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$8iIuixkI73Js3G1uMbezQXD0b8LG4SXGsOwoQkdAQIM", ) .unwrap(); assert_eq!( Argon2::default().verify_password(b"password", &hash), Err(password_hash::errors::Error::Password) ); } /* Argon2id V0x13: Multiple test cases for various input values */ testcase_good!( reference_argon2id_v0x13_2_16_1, Algorithm::Argon2id, Version::V0x13, 2, 16, 1, b"password", b"somesalt", hex!("09316115d5cf24ed5a15a31a3ba326e5cf32edc24702987c02b6566f61913cf7"), "$argon2id$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc" ); testcase_good!( reference_argon2id_v0x13_2_18_1, Algorithm::Argon2id, Version::V0x13, 2, 18, 1, b"password", b"somesalt", hex!("78fe1ec91fb3aa5657d72e710854e4c3d9b9198c742f9616c2f085bed95b2e8c"), "$argon2id$v=19$m=262144,t=2,p=1$c29tZXNhbHQ$eP4eyR+zqlZX1y5xCFTkw9m5GYx0L5YWwvCFvtlbLow" ); testcase_good!( reference_argon2id_v0x13_2_8_1, Algorithm::Argon2id, Version::V0x13, 2, 8, 1, b"password", b"somesalt", hex!("9dfeb910e80bad0311fee20f9c0e2b12c17987b4cac90c2ef54d5b3021c68bfe"), "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4" ); testcase_good!( reference_argon2id_v0x13_2_8_2, Algorithm::Argon2id, Version::V0x13, 2, 8, 2, b"password", b"somesalt", hex!("6d093c501fd5999645e0ea3bf620d7b8be7fd2db59c20d9fff9539da2bf57037"), "$argon2id$v=19$m=256,t=2,p=2$c29tZXNhbHQ$bQk8UB/VmZZF4Oo79iDXuL5/0ttZwg2f/5U52iv1cDc" ); testcase_good!( reference_argon2id_v0x13_1_16_1, Algorithm::Argon2id, Version::V0x13, 1, 16, 1, b"password", b"somesalt", hex!("f6a5adc1ba723dddef9b5ac1d464e180fcd9dffc9d1cbf76cca2fed795d9ca98"), "$argon2id$v=19$m=65536,t=1,p=1$c29tZXNhbHQ$9qWtwbpyPd3vm1rB1GThgPzZ3/ydHL92zKL+15XZypg" ); testcase_good!( reference_argon2id_v0x13_4_16_1, Algorithm::Argon2id, Version::V0x13, 4, 16, 1, b"password", b"somesalt", hex!("9025d48e68ef7395cca9079da4c4ec3affb3c8911fe4f86d1a2520856f63172c"), "$argon2id$v=19$m=65536,t=4,p=1$c29tZXNhbHQ$kCXUjmjvc5XMqQedpMTsOv+zyJEf5PhtGiUghW9jFyw" ); testcase_good!( reference_argon2id_v0x13_2_16_1_differentpassword, Algorithm::Argon2id, Version::V0x13, 2, 16, 1, b"differentpassword", b"somesalt", hex!("0b84d652cf6b0c4beaef0dfe278ba6a80df6696281d7e0d2891b817d8c458fde"), "$argon2id$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$C4TWUs9rDEvq7w3+J4umqA32aWKB1+DSiRuBfYxFj94" ); testcase_good!( reference_argon2id_v0x13_2_16_1_diffsalt, Algorithm::Argon2id, Version::V0x13, 2, 16, 1, b"password", b"diffsalt", hex!("bdf32b05ccc42eb15d58fd19b1f856b113da1e9a5874fdcc544308565aa8141c"), "$argon2id$v=19$m=65536,t=2,p=1$ZGlmZnNhbHQ$vfMrBczELrFdWP0ZsfhWsRPaHppYdP3MVEMIVlqoFBw" ); argon2-0.5.3/tests/phc_strings.rs000064400000000000000000000200731046102023000150560ustar 00000000000000//! Test vectors for Argon2 password hashes in the PHC string format //! //! Adapted from: #![cfg(all(feature = "alloc", feature = "password-hash"))] use argon2::{ Algorithm, Argon2, AssociatedData, KeyId, ParamsBuilder, PasswordHash, PasswordHasher, PasswordVerifier, Version, }; use password_hash::{ errors::{Error, InvalidValue}, SaltString, }; /// Valid password pub const VALID_PASSWORD: &[u8] = b"password"; /// Invalid password pub const INVALID_PASSWORD: &[u8] = b"sassword"; /// Password hashes for "password" pub const VALID_PASSWORD_HASHES: &[&str] = &[ "$argon2i$v=19$m=65536,t=1,p=1$c29tZXNhbHQAAAAAAAAAAA$+r0d29hqEB0yasKr55ZgICsQGSkl0v0kgwhd+U3wyRo", "$argon2id$v=19$m=262144,t=2,p=1$c29tZXNhbHQ$eP4eyR+zqlZX1y5xCFTkw9m5GYx0L5YWwvCFvtlbLow", "$argon2id$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc", "$argon2d$v=19$m=65536,t=2,p=1$YzI5dFpYTmhiSFFBQUFBQUFBQUFBQQ$Jxy74cswY2mq9y+u+iJcJy8EqOp4t/C7DWDzGwGB3IM", // Password with optional keyid "$argon2d$v=19$m=65536,t=2,p=1,keyid=8PDw8A$YzI5dFpYTmhiSFFBQUFBQUFBQUFBQQ$Jxy74cswY2mq9y+u+iJcJy8EqOp4t/C7DWDzGwGB3IM", // Password with optional data "$argon2d$v=16$m=32,t=2,p=3,data=Dw8PDw8P$AAAAAAAAAAA$KnH4gniiaFnDvlA1xev3yovC4cnrrI6tnHOYtmja90o", // Password with optional keyid&data "$argon2d$v=16$m=32,t=2,p=3,keyid=8PDw8A,data=Dw8PDw8P$AAAAAAAAAAA$KnH4gniiaFnDvlA1xev3yovC4cnrrI6tnHOYtmja90o", ]; #[test] fn verifies_correct_password() { for hash_string in VALID_PASSWORD_HASHES { let hash = PasswordHash::new(hash_string).unwrap(); assert_eq!( Argon2::default().verify_password(VALID_PASSWORD, &hash), Ok(()) ); } } #[test] fn rejects_incorrect_password() { for hash_string in VALID_PASSWORD_HASHES { let hash = PasswordHash::new(hash_string).unwrap(); assert!(Argon2::default() .verify_password(INVALID_PASSWORD, &hash) .is_err()); } } // Test PHC string format according to spec // see: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#argon2-encoding macro_rules! testcase_bad_encoding { ($name: ident, $err: expr, $hash: expr) => { #[test] fn $name() { let hash = PasswordHash::new($hash).unwrap(); assert_eq!( Argon2::default().verify_password(b"password", &hash), Err($err) ); } }; } macro_rules! ignored_testcase_bad_encoding { ($name: ident, $err: expr, $hash: expr) => { #[test] #[ignore] fn $name() { let hash = PasswordHash::new($hash).unwrap(); assert_eq!( Argon2::default().verify_password(b"password", &hash), Err($err) ); } }; } testcase_bad_encoding!( argon2i_invalid_encoding_invalid_version, Error::Version, "$argon2i$v=42$m=65536,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ" ); testcase_bad_encoding!( argon2i_invalid_encoding_m_not_a_number, Error::ParamValueInvalid(InvalidValue::InvalidChar('d')), "$argon2i$m=dummy,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ" ); testcase_bad_encoding!( argon2i_invalid_encoding_m_too_small, Error::ParamValueInvalid(InvalidValue::TooShort), "$argon2i$m=0,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ" ); testcase_bad_encoding!( argon2i_invalid_encoding_m_too_big, Error::ParamValueInvalid(InvalidValue::InvalidFormat), "$argon2i$m=4294967296,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ" ); testcase_bad_encoding!( argon2i_invalid_encoding_t_not_a_number, Error::ParamValueInvalid(InvalidValue::InvalidChar('d')), "$argon2i$m=65536,t=dummy,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ" ); testcase_bad_encoding!( argon2i_invalid_encoding_t_too_small, Error::ParamValueInvalid(InvalidValue::TooShort), "$argon2i$m=65536,t=0,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ" ); testcase_bad_encoding!( argon2i_invalid_encoding_t_too_big, Error::ParamValueInvalid(InvalidValue::InvalidFormat), "$argon2i$m=65536,t=4294967296,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ" ); testcase_bad_encoding!( argon2i_invalid_encoding_p_not_a_number, Error::ParamValueInvalid(InvalidValue::InvalidChar('d')), "$argon2i$m=65536,t=2,p=dummy$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ" ); testcase_bad_encoding!( argon2i_invalid_encoding_p_too_small, Error::ParamValueInvalid(InvalidValue::TooShort), "$argon2i$m=65536,t=2,p=0$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ" ); // TODO: Wrong error is returned, this should be changed in the `password-hash` crate ignored_testcase_bad_encoding!( argon2i_invalid_encoding_p_too_big, Error::ParamValueInvalid(InvalidValue::InvalidFormat), "$argon2i$m=65536,t=2,p=256$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ" ); testcase_bad_encoding!( argon2i_invalid_encoding_keyid_not_b64, Error::B64Encoding(base64ct::Error::InvalidEncoding), "$argon2i$m=65536,t=2,p=1,keyid=dummy$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ" ); testcase_bad_encoding!( argon2i_invalid_encoding_data_not_b64, Error::B64Encoding(base64ct::Error::InvalidEncoding), "$argon2i$m=65536,t=2,p=1,data=dummy$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ" ); #[test] fn check_decoding_supports_out_of_order_parameters() { // parameters order is not mandatory let hash = "$argon2d$v=16$m=32,t=2,p=3,keyid=8PDw8A,data=Dw8PDw8P$AAAAAAAAAAA$KnH4gniiaFnDvlA1xev3yovC4cnrrI6tnHOYtmja90o"; let hash = PasswordHash::new(hash).unwrap(); assert!(Argon2::default() .verify_password(b"password", &hash) .is_ok()); } // TODO: Fix default parameters for decoding ! #[test] #[ignore] fn check_decoding_supports_default_parameters() { // parameters order is not mandatory let hash = "$argon2i$p=2,m=256,t=2$c29tZXNhbHQ$tsEVYKap1h6scGt5ovl9aLRGOqOth+AMB+KwHpDFZPs"; let hash = PasswordHash::new(hash).unwrap(); assert!(Argon2::default() .verify_password(b"password", &hash) .is_ok()); } // m/t/p parameters are NOT optional according to spec // TODO: Wrong error is returned, this should be changed in the `password-hash` crate ignored_testcase_bad_encoding!( argon2i_invalid_encoding_missing_m, Error::ParamValueInvalid(InvalidValue::InvalidFormat), "$argon2d$v=16$t=2,p=3$8PDw8PDw8PA$Xv5daH0zPuKO3c9tMBG/WOIUsDrPqq815/xyQTukNxY" ); // TODO: Wrong error is returned, this should be changed in the `password-hash` crate ignored_testcase_bad_encoding!( argon2i_invalid_encoding_missing_t, Error::ParamValueInvalid(InvalidValue::InvalidFormat), "$argon2d$v=16$m=32,p=3$8PDw8PDw8PA$Xv5daH0zPuKO3c9tMBG/WOIUsDrPqq815/xyQTukNxY" ); // TODO: Wrong error is returned, this should be changed in the `password-hash` crate ignored_testcase_bad_encoding!( argon2i_invalid_encoding_missing_p, Error::ParamValueInvalid(InvalidValue::InvalidFormat), "$argon2d$v=16$m=32,t=2$8PDw8PDw8PA$Xv5daH0zPuKO3c9tMBG/WOIUsDrPqq815/xyQTukNxY" ); // Missing&invalid id/salt/hash fields is handled by `PasswordHash` so no need to test that here #[test] fn check_hash_encoding_parameters_order() { let params = ParamsBuilder::new() .m_cost(32) .t_cost(2) .p_cost(3) .data(AssociatedData::new(&[0x0f; 6]).unwrap()) .keyid(KeyId::new(&[0xf0; 4]).unwrap()) .build() .unwrap(); let ctx = Argon2::new(Algorithm::Argon2d, Version::V0x10, params); let salt = vec![0; 8]; let password = b"password"; let salt_string = SaltString::encode_b64(&salt).unwrap(); let password_hash = ctx .hash_password(password, &salt_string) .unwrap() .to_string(); // The parameters shall appear in the m,t,p,keyid,data order assert_eq!(password_hash, "$argon2d$v=16$m=32,t=2,p=3,keyid=8PDw8A,data=Dw8PDw8P$AAAAAAAAAAA$KnH4gniiaFnDvlA1xev3yovC4cnrrI6tnHOYtmja90o"); }