├── benches ├── src │ ├── lib.rs │ └── argon2.rs └── Cargo.toml ├── .gitignore ├── .readme ├── src │ └── lib.rs └── Cargo.toml ├── fuzz ├── .gitignore ├── Cargo.toml ├── LICENSE-MIT └── fuzz_targets │ └── scrypt.rs ├── yescrypt ├── CHANGELOG.md ├── LICENSE-MIT ├── src │ ├── error.rs │ ├── salsa20.rs │ ├── mode.rs │ ├── util.rs │ ├── mcf.rs │ ├── pwxform.rs │ └── lib.rs ├── Cargo.toml ├── README.md └── tests │ └── mcf.rs ├── Cargo.toml ├── .github ├── dependabot.yml └── workflows │ ├── readme.yml │ ├── fuzz_build.yml │ ├── security-audit.yml │ ├── workspace.yml │ ├── bcrypt-pbkdf.yml │ ├── pbkdf2.yml │ ├── sha-crypt.yml │ ├── scrypt.yml │ ├── balloon-hash.yml │ ├── password-auth.yml │ ├── yescrypt.yml │ └── argon2.yml ├── .typos.toml ├── scrypt ├── src │ ├── errors.rs │ ├── romix.rs │ ├── phc.rs │ └── lib.rs ├── benches │ └── lib.rs ├── LICENSE-MIT ├── Cargo.toml ├── README.md ├── tests │ └── mod.rs └── CHANGELOG.md ├── SECURITY.md ├── bcrypt-pbkdf ├── src │ └── errors.rs ├── Cargo.toml ├── LICENSE-MIT ├── README.md ├── tests │ └── test_vectors.rs └── CHANGELOG.md ├── argon2 ├── src │ ├── version.rs │ ├── blake2b_long.rs │ ├── algorithm.rs │ ├── error.rs │ ├── block.rs │ └── memory.rs ├── LICENSE-MIT ├── Cargo.toml └── README.md ├── password-auth ├── CHANGELOG.md ├── LICENSE-MIT ├── Cargo.toml ├── src │ ├── errors.rs │ └── lib.rs └── README.md ├── balloon-hash ├── LICENSE-MIT ├── CHANGELOG.md ├── Cargo.toml ├── src │ ├── error.rs │ ├── algorithm.rs │ └── params.rs ├── README.md └── tests │ ├── balloon_m.rs │ └── balloon.rs ├── sha-crypt ├── LICENSE-MIT ├── src │ ├── errors.rs │ ├── params.rs │ └── mcf.rs ├── Cargo.toml ├── README.md └── CHANGELOG.md ├── pbkdf2 ├── benches │ └── lib.rs ├── LICENSE-MIT ├── Cargo.toml ├── README.md ├── tests │ ├── phc.rs │ └── mod.rs └── CHANGELOG.md └── README.md /benches/src/lib.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/target/ 3 | **/Cargo.lock 4 | -------------------------------------------------------------------------------- /.readme/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../../README.md")] 2 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | corpus 3 | artifacts 4 | coverage 5 | -------------------------------------------------------------------------------- /yescrypt/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | -------------------------------------------------------------------------------- /.readme/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "readme" 3 | description = "rustdoc tests for toplevel README.md" 4 | version = "0.0.0" 5 | edition = "2021" 6 | publish = false 7 | 8 | [dependencies] 9 | password-hash = "0.6.0-rc.3" 10 | argon2 = { path = "../argon2" } 11 | pbkdf2 = { path = "../pbkdf2", features = ["password-hash"] } 12 | scrypt = { path = "../scrypt", features = ["phc"] } 13 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fuzz" 3 | version = "0.0.0" 4 | publish = false 5 | edition = "2018" 6 | 7 | [package.metadata] 8 | cargo-fuzz = true 9 | 10 | [dependencies] 11 | libfuzzer-sys = "0.4" 12 | scrypt = { path = "../scrypt", features = ["simple"]} 13 | 14 | [[bin]] 15 | name = "scrypt" 16 | path = "fuzz_targets/scrypt.rs" 17 | test = false 18 | doc = false 19 | bench = false 20 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | ".readme", 5 | "argon2", 6 | "balloon-hash", 7 | "bcrypt-pbkdf", 8 | "password-auth", 9 | "pbkdf2", 10 | "scrypt", 11 | "sha-crypt", 12 | "yescrypt" 13 | ] 14 | exclude = ["benches", "fuzz"] 15 | 16 | [profile.dev] 17 | opt-level = 2 18 | 19 | [patch.crates-io] 20 | argon2 = { path = "./argon2" } 21 | pbkdf2 = { path = "./pbkdf2" } 22 | scrypt = { path = "./scrypt" } 23 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | versioning-strategy: lockfile-only 5 | directory: "/" 6 | allow: 7 | - dependency-type: "all" 8 | groups: 9 | all-deps: 10 | patterns: 11 | - "*" 12 | schedule: 13 | interval: weekly 14 | open-pull-requests-limit: 10 15 | - package-ecosystem: github-actions 16 | directory: "/" 17 | schedule: 18 | interval: weekly 19 | open-pull-requests-limit: 10 20 | -------------------------------------------------------------------------------- /.typos.toml: -------------------------------------------------------------------------------- 1 | [files] 2 | extend-exclude = [ 3 | ".git/" 4 | ] 5 | 6 | [default] 7 | extend-ignore-re = [ 8 | # Patterns which appear to be 36 or more characters of Base64/Base64url 9 | '\b[0-9A-Za-z+/]{36,}\b', 10 | '\b[0-9A-Za-z_-]{36,}\b', 11 | # KAT in argon2 12 | '\b34 71 9f ba d2 22 61 33 7b 2b 55 29 81 44 09 af\b', 13 | # KAT in pbkdf2 14 | '\bpasswordPASSWORDpassword\b', 15 | ] 16 | 17 | [default.extend-words] 18 | "GOST" = "GOST" 19 | # Variable name in yescrypt 20 | "clen" = "clen" 21 | -------------------------------------------------------------------------------- /benches/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "benches" 3 | version = "0.0.0" 4 | authors = ["RustCrypto Developers"] 5 | license = "Apache-2.0 OR MIT" 6 | edition = "2021" 7 | publish = false 8 | 9 | [dev-dependencies] 10 | argon2 = { path = "../argon2" } 11 | criterion = { version = "0.5", features = ["html_reports"] } 12 | pprof = { version = "0.14", features = ["flamegraph", "criterion"] } 13 | 14 | [features] 15 | default = [] 16 | parallel = ["argon2/parallel"] 17 | 18 | [[bench]] 19 | name = "argon2" 20 | path = "src/argon2.rs" 21 | harness = false 22 | 23 | [patch.crates-io] 24 | password-hash = { git = "https://github.com/RustCrypto/traits.git" } 25 | -------------------------------------------------------------------------------- /.github/workflows/readme.yml: -------------------------------------------------------------------------------- 1 | name: readme 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - ".github/workflows/readme.yml" 7 | - ".readme" 8 | - "Cargo.lock" 9 | - "README.md" 10 | push: 11 | branches: master 12 | 13 | defaults: 14 | run: 15 | working-directory: ".readme" 16 | 17 | env: 18 | CARGO_INCREMENTAL: 0 19 | RUSTFLAGS: "-Dwarnings" 20 | 21 | jobs: 22 | test: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v6 26 | - uses: RustCrypto/actions/cargo-cache@master 27 | - uses: dtolnay/rust-toolchain@master 28 | with: 29 | toolchain: stable 30 | - run: cargo test 31 | -------------------------------------------------------------------------------- /.github/workflows/fuzz_build.yml: -------------------------------------------------------------------------------- 1 | name: fuzz-build 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - ".github/workflows/fuzz_build.yml" 7 | - "fuzz/**" 8 | - "scrypt/**" 9 | - "Cargo.*" 10 | push: 11 | branches: master 12 | 13 | defaults: 14 | run: 15 | working-directory: fuzz 16 | 17 | env: 18 | CARGO_INCREMENTAL: 0 19 | RUSTFLAGS: "-Dwarnings" 20 | 21 | jobs: 22 | build: 23 | runs-on: ubuntu-latest 24 | if: false # disabled while using pre-releases 25 | steps: 26 | - uses: actions/checkout@v6 27 | - uses: RustCrypto/actions/cargo-cache@master 28 | - uses: dtolnay/rust-toolchain@master 29 | with: 30 | toolchain: stable 31 | - run: cargo build 32 | -------------------------------------------------------------------------------- /scrypt/src/errors.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | 3 | /// `scrypt()` error 4 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 5 | pub struct InvalidOutputLen; 6 | 7 | /// `ScryptParams` error 8 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 9 | pub struct InvalidParams; 10 | 11 | impl fmt::Display for InvalidOutputLen { 12 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 13 | f.write_str("invalid output buffer length") 14 | } 15 | } 16 | 17 | impl core::error::Error for InvalidOutputLen {} 18 | 19 | impl fmt::Display for InvalidParams { 20 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 21 | f.write_str("invalid scrypt parameters") 22 | } 23 | } 24 | 25 | impl core::error::Error for InvalidParams {} 26 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Security updates are applied only to the most recent release. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | If you have discovered a security vulnerability in this project, please report 10 | it privately. **Do not disclose it as a public issue.** This gives us time to 11 | work with you to fix the issue before public exposure, reducing the chance that 12 | the exploit will be used before a patch is released. 13 | 14 | Please disclose it at [security advisory](https://github.com/RustCrypto/password-hashes/security/advisories/new). 15 | 16 | This project is maintained by a team of volunteers on a reasonable-effort basis. 17 | As such, please give us at least 90 days to work on a fix before public exposure. 18 | -------------------------------------------------------------------------------- /scrypt/benches/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![feature(test)] 3 | 4 | extern crate test; 5 | 6 | use test::Bencher; 7 | 8 | #[bench] 9 | pub fn scrypt_17_8_1(bh: &mut Bencher) { 10 | let password = b"my secure password"; 11 | let salt = b"salty salt"; 12 | let mut buf = [0u8; 32]; 13 | let params = scrypt::Params::recommended(); 14 | bh.iter(|| { 15 | scrypt::scrypt(password, salt, ¶ms, &mut buf).unwrap(); 16 | test::black_box(&buf); 17 | }); 18 | } 19 | 20 | #[bench] 21 | pub fn scrypt_17_2_4(bh: &mut Bencher) { 22 | let password = b"my secure password"; 23 | let salt = b"salty salt"; 24 | let mut buf = [0u8; 32]; 25 | let params = scrypt::Params::new(17, 2, 4).unwrap(); 26 | bh.iter(|| { 27 | scrypt::scrypt(password, salt, ¶ms, &mut buf).unwrap(); 28 | test::black_box(&buf); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/security-audit.yml: -------------------------------------------------------------------------------- 1 | name: Security Audit 2 | on: 3 | pull_request: 4 | paths: 5 | - .github/workflows/security-audit.yml 6 | - Cargo.lock 7 | push: 8 | branches: master 9 | paths: 10 | - .github/workflows/security-audit.yml 11 | - Cargo.lock 12 | schedule: 13 | - cron: "0 0 * * *" 14 | 15 | jobs: 16 | security_audit: 17 | name: Security Audit 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v6 21 | - uses: RustCrypto/actions/cargo-cache@master 22 | - uses: dtolnay/rust-toolchain@master 23 | with: 24 | toolchain: stable 25 | - name: Cache cargo bin 26 | uses: actions/cache@v5 27 | with: 28 | path: ~/.cargo/bin 29 | key: ${{ runner.os }}-cargo-audit-v0.21.1-rust1.85 30 | - uses: rustsec/audit-check@v2 31 | with: 32 | token: ${{ secrets.GITHUB_TOKEN }} 33 | -------------------------------------------------------------------------------- /bcrypt-pbkdf/src/errors.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | 3 | /// `bcrypt_pbkdf` error 4 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 5 | pub enum Error { 6 | /// An input parameter has an invalid length. 7 | InvalidParamLen, 8 | /// An invalid number of rounds was specified. 9 | InvalidRounds, 10 | /// The output parameter has an invalid length. 11 | InvalidOutputLen, 12 | /// The manually provided memory was not long enough. 13 | InvalidMemoryLen, 14 | } 15 | 16 | impl fmt::Display for Error { 17 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 18 | match self { 19 | Error::InvalidParamLen => write!(f, "Invalid parameter length"), 20 | Error::InvalidRounds => write!(f, "Invalid number of rounds"), 21 | Error::InvalidOutputLen => write!(f, "Invalid output length"), 22 | Error::InvalidMemoryLen => write!(f, "Invalid memory length"), 23 | } 24 | } 25 | } 26 | 27 | impl core::error::Error for Error {} 28 | -------------------------------------------------------------------------------- /argon2/src/version.rs: -------------------------------------------------------------------------------- 1 | //! Version of the algorithm. 2 | 3 | use crate::{Error, Result}; 4 | 5 | /// Version of the algorithm. 6 | #[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)] 7 | #[repr(u32)] 8 | pub enum Version { 9 | /// Version 16 (0x10 in hex) 10 | /// 11 | /// Performs overwrite internally 12 | V0x10 = 0x10, 13 | 14 | /// Version 19 (0x13 in hex, default) 15 | /// 16 | /// Performs XOR internally 17 | #[default] 18 | V0x13 = 0x13, 19 | } 20 | 21 | impl Version { 22 | /// Serialize version as little endian bytes 23 | pub(crate) const fn to_le_bytes(self) -> [u8; 4] { 24 | (self as u32).to_le_bytes() 25 | } 26 | } 27 | 28 | impl From for u32 { 29 | fn from(version: Version) -> u32 { 30 | version as u32 31 | } 32 | } 33 | 34 | impl TryFrom for Version { 35 | type Error = Error; 36 | 37 | fn try_from(version_id: u32) -> Result { 38 | match version_id { 39 | 0x10 => Ok(Version::V0x10), 40 | 0x13 => Ok(Version::V0x13), 41 | _ => Err(Error::VersionInvalid), 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /bcrypt-pbkdf/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bcrypt-pbkdf" 3 | version = "0.11.0-rc.2" 4 | description = "bcrypt-pbkdf password-based key derivation function" 5 | authors = ["RustCrypto Developers"] 6 | license = "MIT OR Apache-2.0" 7 | readme = "README.md" 8 | documentation = "https://docs.rs/bcrypt-pbkdf" 9 | homepage = "https://github.com/RustCrypto/password-hashes/tree/master/bcrypt-pbkdf" 10 | repository = "https://github.com/RustCrypto/password-hashes" 11 | keywords = ["crypto", "hashing", "password", "phf"] 12 | categories = ["authentication", "cryptography", "no-std"] 13 | edition = "2024" 14 | rust-version = "1.85" 15 | 16 | [dependencies] 17 | blowfish = { version = "0.10.0-rc.2", features = ["bcrypt"] } 18 | pbkdf2 = { version = "0.13.0-rc.4", default-features = false } 19 | sha2 = { version = "0.11.0-rc.3", default-features = false } 20 | 21 | # optional features 22 | zeroize = { version = "1", default-features = false, optional = true } 23 | 24 | [dev-dependencies] 25 | hex-literal = "1" 26 | 27 | [features] 28 | default = ["alloc", "std"] 29 | alloc = [] 30 | std = [] 31 | zeroize = ["dep:zeroize"] 32 | 33 | [package.metadata.docs.rs] 34 | all-features = true 35 | -------------------------------------------------------------------------------- /password-auth/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## 1.0.0 (2023-09-03) 9 | 10 | No changes from v0.3.0. 11 | 12 | ## 0.3.0 (2023-06-24) 13 | ### Added 14 | - Derive `Eq`/`PartialEq` on `*Error` ([#433]) 15 | 16 | ### Changed 17 | - Rename `Error` (back) to `VerifyError` ([#432]) 18 | 19 | [#432]: https://github.com/RustCrypto/password-hashes/pull/432 20 | [#433]: https://github.com/RustCrypto/password-hashes/pull/433 21 | 22 | ## 0.2.0 (2023-06-24) 23 | ### Added 24 | - `is_hash_obsolete` ([#428]) 25 | 26 | ### Changed 27 | - Replace `VerifyError` with `Error` enum ([#429]) 28 | 29 | [#428]: https://github.com/RustCrypto/password-hashes/pull/428 30 | [#429]: https://github.com/RustCrypto/password-hashes/pull/429 31 | 32 | ## 0.1.1 (2023-06-01) 33 | ### Changed 34 | - Improve documentation ([#419]) 35 | 36 | [#419]: https://github.com/RustCrypto/password-hashes/pull/419 37 | 38 | ## 0.1.0 (2023-03-14) 39 | - Initial release 40 | -------------------------------------------------------------------------------- /argon2/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021-2024 The RustCrypto Project Developers 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /scrypt/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021-2023 The RustCrypto Project Developers 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /balloon-hash/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021-2023 The RustCrypto Project Developers 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /password-auth/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022-2023 The RustCrypto Project Developers 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /sha-crypt/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021-2023 The RustCrypto Project Developers 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /pbkdf2/benches/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![feature(test)] 3 | 4 | extern crate test; 5 | 6 | use hmac::Hmac; 7 | use pbkdf2::pbkdf2; 8 | use test::Bencher; 9 | 10 | #[bench] 11 | pub fn pbkdf2_hmac_sha1_16384_20(bh: &mut Bencher) { 12 | let password = b"my secure password"; 13 | let salt = b"salty salt"; 14 | let mut buf = [0u8; 20]; 15 | bh.iter(|| { 16 | pbkdf2::>(password, salt, 16_384, &mut buf).unwrap(); 17 | test::black_box(&buf); 18 | }); 19 | } 20 | 21 | #[bench] 22 | pub fn pbkdf2_hmac_sha256_16384_20(bh: &mut Bencher) { 23 | let password = b"my secure password"; 24 | let salt = b"salty salt"; 25 | let mut buf = [0u8; 20]; 26 | bh.iter(|| { 27 | pbkdf2::>(password, salt, 16_384, &mut buf).unwrap(); 28 | test::black_box(&buf); 29 | }); 30 | } 31 | 32 | #[bench] 33 | pub fn pbkdf2_hmac_sha512_16384_20(bh: &mut Bencher) { 34 | let password = b"my secure password"; 35 | let salt = b"salty salt"; 36 | let mut buf = [0u8; 20]; 37 | bh.iter(|| { 38 | pbkdf2::>(password, salt, 16_384, &mut buf).unwrap(); 39 | test::black_box(&buf); 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /fuzz/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024 The RustCrypto Project Developers 2 | Copyright (c) 2024 Google LLC 3 | 4 | Permission is hereby granted, free of charge, to any 5 | person obtaining a copy of this software and associated 6 | documentation files (the "Software"), to deal in the 7 | Software without restriction, including without 8 | limitation the rights to use, copy, modify, merge, 9 | publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software 11 | is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice 15 | shall be included in all copies or substantial portions 16 | of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 19 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 20 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 21 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 22 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 25 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 | DEALINGS IN THE SOFTWARE. 27 | -------------------------------------------------------------------------------- /sha-crypt/src/errors.rs: -------------------------------------------------------------------------------- 1 | //! Error types. 2 | 3 | use core::fmt; 4 | 5 | #[cfg(doc)] 6 | use crate::Params; 7 | 8 | /// Result type for the `sha-crypt` crate with its [`Error`] type. 9 | pub type Result = core::result::Result; 10 | 11 | /// Error type. 12 | #[derive(Debug)] 13 | pub enum Error { 14 | /// Parameters are invalid (e.g. parse error) 15 | ParamsInvalid, 16 | 17 | /// `rounds=` be within range [`Params::ROUNDS_MIN`]..=[`Params::ROUNDS_MIN`] 18 | RoundsInvalid, 19 | } 20 | 21 | impl core::error::Error for Error {} 22 | 23 | impl fmt::Display for Error { 24 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 25 | match self { 26 | Error::ParamsInvalid => write!(f, "parameters are invalid"), 27 | Error::RoundsInvalid => write!(f, "rounds error"), 28 | } 29 | } 30 | } 31 | 32 | #[cfg(feature = "password-hash")] 33 | impl From for password_hash::Error { 34 | fn from(err: Error) -> Self { 35 | match err { 36 | Error::RoundsInvalid => password_hash::Error::ParamInvalid { name: "rounds" }, 37 | Error::ParamsInvalid => password_hash::Error::ParamsInvalid, 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /bcrypt-pbkdf/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Jack Grigg 2 | Copyright (c) 2019-2023 The RustCrypto Project Developers 3 | 4 | Permission is hereby granted, free of charge, to any 5 | person obtaining a copy of this software and associated 6 | documentation files (the "Software"), to deal in the 7 | Software without restriction, including without 8 | limitation the rights to use, copy, modify, merge, 9 | publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software 11 | is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice 15 | shall be included in all copies or substantial portions 16 | of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 19 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 20 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 21 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 22 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 25 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 | DEALINGS IN THE SOFTWARE. 27 | -------------------------------------------------------------------------------- /pbkdf2/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Artyom Pavlov 2 | Copyright (c) 2018-2023 The RustCrypto Project Developers 3 | 4 | Permission is hereby granted, free of charge, to any 5 | person obtaining a copy of this software and associated 6 | documentation files (the "Software"), to deal in the 7 | Software without restriction, including without 8 | limitation the rights to use, copy, modify, merge, 9 | publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software 11 | is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice 15 | shall be included in all copies or substantial portions 16 | of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 19 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 20 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 21 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 22 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 25 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 | DEALINGS IN THE SOFTWARE. 27 | -------------------------------------------------------------------------------- /yescrypt/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright 2009 Colin Percival 2 | Copyright 2013-2018 Alexander Peslyak 3 | Copyright 2024-2025 RustCrypto Developers 4 | 5 | Permission is hereby granted, free of charge, to any 6 | person obtaining a copy of this software and associated 7 | documentation files (the "Software"), to deal in the 8 | Software without restriction, including without 9 | limitation the rights to use, copy, modify, merge, 10 | publish, distribute, sublicense, and/or sell copies of 11 | the Software, and to permit persons to whom the Software 12 | is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice 16 | shall be included in all copies or substantial portions 17 | of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 20 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 21 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 22 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 23 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 24 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 26 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 27 | DEALINGS IN THE SOFTWARE. 28 | -------------------------------------------------------------------------------- /.github/workflows/workspace.yml: -------------------------------------------------------------------------------- 1 | name: Workspace 2 | 3 | env: 4 | CARGO_INCREMENTAL: 0 5 | RUSTFLAGS: "-Dwarnings" 6 | RUSTDOCFLAGS: "-Dwarnings" 7 | 8 | on: 9 | pull_request: 10 | paths-ignore: 11 | - README.md 12 | push: 13 | branches: master 14 | paths-ignore: 15 | - README.md 16 | 17 | jobs: 18 | clippy: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v6 22 | - uses: RustCrypto/actions/cargo-cache@master 23 | - uses: dtolnay/rust-toolchain@master 24 | with: 25 | toolchain: 1.89.0 26 | components: clippy 27 | - run: cargo clippy --all -- -D warnings 28 | 29 | doc: 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@v6 33 | - uses: dtolnay/rust-toolchain@master 34 | with: 35 | toolchain: stable 36 | - run: cargo doc --workspace --all-features 37 | 38 | rustfmt: 39 | runs-on: ubuntu-latest 40 | steps: 41 | - uses: actions/checkout@v6 42 | - uses: dtolnay/rust-toolchain@master 43 | with: 44 | toolchain: stable 45 | components: rustfmt 46 | - run: cargo fmt --all -- --check 47 | 48 | typos: 49 | runs-on: ubuntu-latest 50 | steps: 51 | - uses: actions/checkout@v6 52 | - uses: crate-ci/typos@v1 53 | -------------------------------------------------------------------------------- /password-auth/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "password-auth" 3 | version = "1.1.0-rc.0" 4 | description = """ 5 | Password authentication library with a focus on simplicity and ease-of-use, 6 | including support for Argon2, PBKDF2, and scrypt password hashing algorithms 7 | """ 8 | authors = ["RustCrypto Developers"] 9 | license = "MIT OR Apache-2.0" 10 | documentation = "https://docs.rs/password-auth" 11 | homepage = "https://github.com/RustCrypto/password-hashes/tree/master/password-auth" 12 | repository = "https://github.com/RustCrypto/password-hashes" 13 | keywords = ["crypto", "password", "hashing"] 14 | categories = ["authentication", "cryptography", "no-std"] 15 | readme = "README.md" 16 | edition = "2024" 17 | rust-version = "1.85" 18 | 19 | [dependencies] 20 | getrandom = { version = "0.3", default-features = false } 21 | password-hash = { version = "0.6.0-rc.6", features = ["alloc", "getrandom", "phc"] } 22 | 23 | # optional dependencies 24 | argon2 = { version = "0.6.0-rc.5", optional = true, default-features = false, features = ["alloc", "password-hash"] } 25 | pbkdf2 = { version = "0.13.0-rc.5", optional = true, default-features = false, features = ["password-hash"] } 26 | scrypt = { version = "0.12.0-rc.6", optional = true, default-features = false, features = ["phc"] } 27 | 28 | [features] 29 | default = ["argon2", "std"] 30 | std = [] 31 | wasm_js = ["getrandom/wasm_js"] 32 | 33 | [package.metadata.docs.rs] 34 | all-features = true 35 | -------------------------------------------------------------------------------- /balloon-hash/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## 0.4.0 (2023-03-04) 9 | ### Changed 10 | - Bump `crypto-bigint` to v0.5; MSRV 1.65 ([#381]) 11 | - Bump `password-hash` to v0.5 ([#383]) 12 | 13 | [#381]: https://github.com/RustCrypto/password-hashes/pull/381 14 | [#383]: https://github.com/RustCrypto/password-hashes/pull/383 15 | 16 | ## 0.3.0 (2022-06-27) 17 | ### Added 18 | - `Balloon::hash_into` ([#313]) 19 | 20 | ### Changed 21 | - Make `Error` enum non-exhaustive ([#313]) 22 | 23 | [#313]: https://github.com/RustCrypto/password-hashes/pull/313 24 | 25 | ## 0.2.1 (2022-06-16) 26 | ### Added 27 | - `zeroize` feature ([#312]) 28 | 29 | [#312]: https://github.com/RustCrypto/password-hashes/pull/312 30 | 31 | ## 0.2.0 (2022-03-18) 32 | ### Changed 33 | - Bump `password-hash` dependency to v0.4; MSRV 1.57 ([#283]) 34 | - 2021 edition upgrade ([#284]) 35 | 36 | [#283]: https://github.com/RustCrypto/password-hashes/pull/283 37 | [#284]: https://github.com/RustCrypto/password-hashes/pull/284 38 | 39 | ## 0.1.1 (2022-02-17) 40 | ### Fixed 41 | - Minimal versions build ([#273]) 42 | 43 | [#273]: https://github.com/RustCrypto/password-hashes/pull/273 44 | 45 | ## 0.1.0 (2022-01-22) 46 | - Initial release 47 | -------------------------------------------------------------------------------- /sha-crypt/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sha-crypt" 3 | version = "0.6.0-rc.0" 4 | description = """ 5 | Pure Rust implementation of the SHA-crypt password hash based on SHA-512 6 | as implemented by the POSIX crypt C library 7 | """ 8 | authors = ["RustCrypto Developers"] 9 | license = "MIT OR Apache-2.0" 10 | documentation = "https://docs.rs/sha-crypt" 11 | homepage = "https://github.com/RustCrypto/password-hashes/tree/master/sha-crypt" 12 | repository = "https://github.com/RustCrypto/password-hashes" 13 | keywords = ["crypto", "hashing", "password", "phf"] 14 | categories = ["authentication", "cryptography", "no-std"] 15 | readme = "README.md" 16 | edition = "2024" 17 | rust-version = "1.85" 18 | 19 | [dependencies] 20 | sha2 = { version = "0.11.0-rc.3", default-features = false } 21 | base64ct = { version = "1.8", default-features = false, features = ["alloc"] } 22 | 23 | # optional dependencies 24 | mcf = { version = "0.6.0-rc.0", optional = true, default-features = false, features = ["alloc", "base64"] } 25 | password-hash = { version = "0.6.0-rc.6", optional = true, default-features = false } 26 | subtle = { version = "2", optional = true, default-features = false } 27 | 28 | [features] 29 | default = ["password-hash"] 30 | getrandom = ["password-hash/getrandom", "password-hash"] 31 | rand_core = ["password-hash/rand_core"] 32 | password-hash = ["dep:mcf", "dep:password-hash", "dep:subtle"] 33 | 34 | [package.metadata.docs.rs] 35 | all-features = true 36 | -------------------------------------------------------------------------------- /scrypt/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scrypt" 3 | version = "0.12.0-rc.7" 4 | description = "Scrypt password-based key derivation function" 5 | authors = ["RustCrypto Developers"] 6 | license = "MIT OR Apache-2.0" 7 | readme = "README.md" 8 | documentation = "https://docs.rs/scrypt" 9 | homepage = "https://github.com/RustCrypto/password-hashes/tree/master/scrypt" 10 | repository = "https://github.com/RustCrypto/password-hashes" 11 | keywords = ["crypto", "hashing", "password", "phf"] 12 | categories = ["authentication", "cryptography", "no-std"] 13 | edition = "2024" 14 | rust-version = "1.85" 15 | 16 | [dependencies] 17 | pbkdf2 = { version = "0.13.0-rc.5", path = "../pbkdf2" } 18 | salsa20 = { version = "0.11.0-rc.2", default-features = false } 19 | sha2 = { version = "0.11.0-rc.3", default-features = false } 20 | rayon = { version = "1.11", optional = true } 21 | 22 | # optional dependencies 23 | mcf = { version = "0.6.0-rc.0", optional = true } 24 | password-hash = { version = "0.6.0-rc.6", optional = true, default-features = false } 25 | subtle = { version = "2", optional = true, default-features = false } 26 | 27 | [features] 28 | alloc = ["password-hash?/alloc"] 29 | 30 | getrandom = ["password-hash", "password-hash/getrandom"] 31 | mcf = ["alloc", "password-hash", "dep:mcf", "dep:subtle"] 32 | phc = ["password-hash/phc"] 33 | rand_core = ["password-hash/rand_core"] 34 | parallel = ["dep:rayon"] 35 | 36 | [package.metadata.docs.rs] 37 | all-features = true 38 | -------------------------------------------------------------------------------- /yescrypt/src/error.rs: -------------------------------------------------------------------------------- 1 | //! Error type. 2 | 3 | use core::{fmt, num::TryFromIntError}; 4 | 5 | /// Result type for the `yescrypt` crate with its [`Error`] type. 6 | pub type Result = core::result::Result; 7 | 8 | /// Error type. 9 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 10 | #[non_exhaustive] 11 | pub enum Error { 12 | /// Encoding error (i.e. Base64) 13 | Encoding, 14 | 15 | /// Internal error (bug in library) 16 | Internal, 17 | 18 | /// Invalid params 19 | Params, 20 | } 21 | 22 | impl fmt::Display for Error { 23 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 24 | match self { 25 | Error::Encoding => write!(f, "yescrypt encoding invalid"), 26 | Error::Internal => write!(f, "internal error"), 27 | Error::Params => write!(f, "yescrypt params invalid"), 28 | } 29 | } 30 | } 31 | 32 | impl core::error::Error for Error {} 33 | 34 | impl From for Error { 35 | fn from(_: TryFromIntError) -> Self { 36 | Error::Internal 37 | } 38 | } 39 | 40 | #[cfg(feature = "password-hash")] 41 | impl From for password_hash::Error { 42 | fn from(err: Error) -> Self { 43 | match err { 44 | Error::Encoding => password_hash::Error::EncodingInvalid, 45 | Error::Internal => password_hash::Error::Internal, 46 | Error::Params => password_hash::Error::ParamsInvalid, 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /pbkdf2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pbkdf2" 3 | version = "0.13.0-rc.5" 4 | authors = ["RustCrypto Developers"] 5 | license = "MIT OR Apache-2.0" 6 | description = "Generic implementation of PBKDF2" 7 | documentation = "https://docs.rs/pbkdf2" 8 | homepage = "https://github.com/RustCrypto/password-hashes/tree/master/pbkdf2" 9 | repository = "https://github.com/RustCrypto/password-hashes" 10 | keywords = ["crypto", "hashing", "password", "phf"] 11 | categories = ["authentication", "cryptography", "no-std"] 12 | readme = "README.md" 13 | edition = "2024" 14 | rust-version = "1.85" 15 | 16 | [dependencies] 17 | digest = { version = "0.11.0-rc.4", features = ["mac"] } 18 | 19 | # optional dependencies 20 | hmac = { version = "0.13.0-rc.3", default-features = false, optional = true } 21 | password-hash = { version = "0.6.0-rc.6", default-features = false, optional = true, features = ["phc"] } 22 | sha1 = { version = "0.11.0-rc.3", default-features = false, optional = true } 23 | sha2 = { version = "0.11.0-rc.3", default-features = false, optional = true } 24 | 25 | [dev-dependencies] 26 | hmac = "0.13.0-rc.3" 27 | hex-literal = "1" 28 | sha1 = "0.11.0-rc.3" 29 | sha2 = "0.11.0-rc.3" 30 | streebog = "0.11.0-rc.3" 31 | belt-hash = "0.2.0-rc.3" 32 | 33 | [features] 34 | default = ["hmac"] 35 | getrandom = ["password-hash", "password-hash/getrandom"] 36 | password-hash = ["hmac", "dep:password-hash", "sha2"] 37 | rand_core = ["password-hash/rand_core"] 38 | 39 | [package.metadata.docs.rs] 40 | all-features = true 41 | -------------------------------------------------------------------------------- /balloon-hash/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "balloon-hash" 3 | version = "0.5.0-rc.3" 4 | description = "Pure Rust implementation of the Balloon password hashing function" 5 | authors = ["RustCrypto Developers"] 6 | license = "MIT OR Apache-2.0" 7 | documentation = "https://docs.rs/balloon-hash" 8 | homepage = "https://github.com/RustCrypto/password-hashes/tree/master/balloon-hash" 9 | repository = "https://github.com/RustCrypto/password-hashes" 10 | keywords = ["crypto", "hashing", "password", "phf"] 11 | categories = ["authentication", "cryptography", "no-std"] 12 | readme = "README.md" 13 | edition = "2024" 14 | rust-version = "1.85" 15 | 16 | [dependencies] 17 | digest = { version = "0.11.0-rc.4", default-features = false } 18 | crypto-bigint = { version = "0.7.0-rc.9", default-features = false, features = ["hybrid-array"] } 19 | 20 | # optional dependencies 21 | password-hash = { version = "0.6.0-rc.6", optional = true, default-features = false, features = ["phc"] } 22 | rayon = { version = "1.7", optional = true } 23 | zeroize = { version = "1", default-features = false, optional = true } 24 | 25 | [dev-dependencies] 26 | hex-literal = "1" 27 | sha2 = "0.11.0-rc.3" 28 | 29 | [features] 30 | default = ["alloc", "getrandom", "password-hash"] 31 | alloc = ["password-hash/alloc"] 32 | 33 | getrandom = ["password-hash/getrandom"] 34 | parallel = ["dep:rayon"] 35 | password-hash = ["dep:password-hash"] 36 | rand_core = ["password-hash/rand_core"] 37 | zeroize = ["dep:zeroize"] 38 | 39 | [package.metadata.docs.rs] 40 | all-features = true 41 | -------------------------------------------------------------------------------- /yescrypt/src/salsa20.rs: -------------------------------------------------------------------------------- 1 | //! Wrapper functions for invoking the `salsa20` crate. 2 | 3 | use crate::util::{slice_as_chunks_mut, xor}; 4 | use salsa20::cipher::{ 5 | StreamCipherCore, 6 | consts::{U1, U4}, 7 | typenum::Unsigned, 8 | }; 9 | 10 | pub(crate) fn salsa20_2(b: &mut [u32; 16]) { 11 | salsa20::(b); 12 | } 13 | 14 | fn salsa20(b: &mut [u32; 16]) { 15 | let mut x = [0u32; 16]; 16 | 17 | for i in 0..16 { 18 | x[i * 5 % 16] = b[i]; 19 | } 20 | 21 | let mut block = [0u8; 64]; 22 | salsa20::SalsaCore::::from_raw_state(x).write_keystream_block((&mut block).into()); 23 | 24 | for (c, b) in block.chunks_exact(4).zip(x.iter_mut()) { 25 | *b = u32::from_le_bytes(c.try_into().expect("4 bytes is 1 u32")).wrapping_sub(*b); 26 | } 27 | 28 | for i in 0..16 { 29 | b[i] = b[i].wrapping_add(x[i * 5 % 16]); 30 | } 31 | } 32 | 33 | pub(crate) fn blockmix_salsa8(b: &mut [u32], y: &mut [u32], r: usize) { 34 | // TODO(tarcieri): use upstream `[T]::as_chunks_mut` when MSRV is 1.88 35 | let (b, _) = slice_as_chunks_mut::<_, 16>(b); 36 | let (y, _) = slice_as_chunks_mut::<_, 16>(y); 37 | let mut x = b[2 * r - 1]; 38 | 39 | for i in 0..(2 * r) { 40 | xor(&mut x, &b[i]); 41 | salsa20::(&mut x); 42 | y[i].copy_from_slice(&x); 43 | } 44 | 45 | for i in 0..r { 46 | b[i].copy_from_slice(&y[i * 2]); 47 | } 48 | 49 | for i in 0..r { 50 | b[i + r].copy_from_slice(&y[i * 2 + 1]); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /yescrypt/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "yescrypt" 3 | version = "0.1.0-rc.1" 4 | description = "Pure Rust implementation of the yescrypt password-based key derivation function" 5 | authors = ["RustCrypto Developers"] 6 | license = "MIT OR Apache-2.0" 7 | documentation = "https://docs.rs/yescrypt" 8 | homepage = "https://github.com/RustCrypto/password-hashes/tree/master/yescrypt" 9 | repository = "https://github.com/RustCrypto/password-hashes" 10 | keywords = ["crypto", "hashing", "password", "phf"] 11 | categories = ["authentication", "cryptography", "no-std"] 12 | readme = "README.md" 13 | edition = "2024" 14 | rust-version = "1.85" 15 | 16 | [dependencies] 17 | hmac = { version = "0.13.0-rc.3", default-features = false } 18 | pbkdf2 = { version = "0.13.0-rc.5", default-features = false, features = ["hmac"] } 19 | salsa20 = { version = "0.11.0-rc.2", default-features = false } 20 | sha2 = { version = "0.11.0-rc.3", default-features = false } 21 | subtle = { version = "2", default-features = false } 22 | 23 | # optional dependencies 24 | mcf = { version = "0.6.0-rc.0", optional = true, default-features = false, features = ["alloc", "base64"] } 25 | password-hash = { version = "0.6.0-rc.6", optional = true, default-features = false } 26 | 27 | [dev-dependencies] 28 | hex-literal = "1" 29 | 30 | [features] 31 | default = ["getrandom"] 32 | getrandom = ["password-hash", "password-hash/getrandom"] 33 | rand_core = ["password-hash/rand_core"] 34 | password-hash = ["dep:mcf", "dep:password-hash"] 35 | 36 | [package.metadata.docs.rs] 37 | all-features = true 38 | -------------------------------------------------------------------------------- /.github/workflows/bcrypt-pbkdf.yml: -------------------------------------------------------------------------------- 1 | name: bcrypt-pbkdf 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - "bcrypt-pbkdf/**" 7 | - "Cargo.*" 8 | push: 9 | branches: master 10 | 11 | defaults: 12 | run: 13 | working-directory: bcrypt-pbkdf 14 | 15 | env: 16 | CARGO_INCREMENTAL: 0 17 | RUSTFLAGS: "-Dwarnings" 18 | 19 | jobs: 20 | build: 21 | runs-on: ubuntu-latest 22 | strategy: 23 | matrix: 24 | rust: 25 | - 1.85.0 # MSRV 26 | - stable 27 | target: 28 | - thumbv7em-none-eabi 29 | - wasm32-unknown-unknown 30 | steps: 31 | - uses: actions/checkout@v6 32 | - uses: RustCrypto/actions/cargo-cache@master 33 | - uses: dtolnay/rust-toolchain@master 34 | with: 35 | toolchain: ${{ matrix.rust }} 36 | targets: ${{ matrix.target }} 37 | - run: cargo build --no-default-features --target ${{ matrix.target }} 38 | 39 | minimal-versions: 40 | # disabled until belt-block gets published 41 | if: false 42 | uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master 43 | with: 44 | working-directory: ${{ github.workflow }} 45 | 46 | test: 47 | runs-on: ubuntu-latest 48 | strategy: 49 | matrix: 50 | rust: 51 | - 1.85.0 # MSRV 52 | - stable 53 | steps: 54 | - uses: actions/checkout@v6 55 | - uses: RustCrypto/actions/cargo-cache@master 56 | - uses: dtolnay/rust-toolchain@master 57 | with: 58 | toolchain: ${{ matrix.rust }} 59 | - run: cargo test --no-default-features 60 | - run: cargo test 61 | -------------------------------------------------------------------------------- /.github/workflows/pbkdf2.yml: -------------------------------------------------------------------------------- 1 | name: pbkdf2 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - "pbkdf2/**" 7 | - "Cargo.*" 8 | push: 9 | branches: master 10 | 11 | defaults: 12 | run: 13 | working-directory: pbkdf2 14 | 15 | env: 16 | CARGO_INCREMENTAL: 0 17 | RUSTFLAGS: "-Dwarnings" 18 | 19 | jobs: 20 | build: 21 | runs-on: ubuntu-latest 22 | strategy: 23 | matrix: 24 | rust: 25 | - 1.85.0 # MSRV 26 | - stable 27 | target: 28 | - thumbv7em-none-eabi 29 | - wasm32-unknown-unknown 30 | steps: 31 | - uses: actions/checkout@v6 32 | - uses: RustCrypto/actions/cargo-cache@master 33 | - uses: dtolnay/rust-toolchain@master 34 | with: 35 | toolchain: ${{ matrix.rust }} 36 | targets: ${{ matrix.target }} 37 | - run: cargo build --target ${{ matrix.target }} --no-default-features 38 | 39 | minimal-versions: 40 | if: false # disabled while using pre-releases 41 | uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master 42 | with: 43 | working-directory: ${{ github.workflow }} 44 | 45 | test: 46 | runs-on: ubuntu-latest 47 | strategy: 48 | matrix: 49 | rust: 50 | - 1.85.0 # MSRV 51 | - stable 52 | steps: 53 | - uses: actions/checkout@v6 54 | - uses: RustCrypto/actions/cargo-cache@master 55 | - uses: dtolnay/rust-toolchain@master 56 | with: 57 | toolchain: ${{ matrix.rust }} 58 | - run: cargo test --no-default-features 59 | - run: cargo test 60 | - run: cargo test --all-features 61 | -------------------------------------------------------------------------------- /argon2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "argon2" 3 | version = "0.6.0-rc.5" 4 | description = """ 5 | Pure Rust implementation of the Argon2 password hashing function with support 6 | for the Argon2d, Argon2i, and Argon2id algorithmic variants 7 | """ 8 | authors = ["RustCrypto Developers"] 9 | license = "MIT OR Apache-2.0" 10 | documentation = "https://docs.rs/argon2" 11 | homepage = "https://github.com/RustCrypto/password-hashes/tree/master/argon2" 12 | repository = "https://github.com/RustCrypto/password-hashes" 13 | keywords = ["crypto", "hashing", "password", "phf"] 14 | categories = ["authentication", "cryptography", "no-std"] 15 | readme = "README.md" 16 | edition = "2024" 17 | rust-version = "1.85" 18 | 19 | [dependencies] 20 | base64ct = "1.7" 21 | blake2 = { version = "0.11.0-rc.3", default-features = false } 22 | 23 | # optional dependencies 24 | rayon = { version = "1.7", optional = true } 25 | password-hash = { version = "0.6.0-rc.6", optional = true, features = ["phc"] } 26 | zeroize = { version = "1", default-features = false, optional = true } 27 | 28 | [target.'cfg(any(target_arch = "x86", target_arch = "x86_64"))'.dependencies] 29 | cpufeatures = "0.2.17" 30 | 31 | [dev-dependencies] 32 | hex-literal = "1" 33 | 34 | [features] 35 | default = ["alloc", "getrandom", "password-hash"] 36 | alloc = ["password-hash?/alloc"] 37 | 38 | getrandom = ["password-hash/getrandom"] 39 | parallel = ["dep:rayon"] 40 | password-hash = ["dep:password-hash"] 41 | rand_core = ["password-hash/rand_core"] 42 | zeroize = ["dep:zeroize"] 43 | 44 | [lints.rust.unexpected_cfgs] 45 | level = "warn" 46 | check-cfg = ['cfg(test_large_ram)'] 47 | 48 | [package.metadata.docs.rs] 49 | all-features = true 50 | -------------------------------------------------------------------------------- /.github/workflows/sha-crypt.yml: -------------------------------------------------------------------------------- 1 | name: sha-crypt 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - "sha-crypt/**" 7 | - "Cargo.*" 8 | push: 9 | branches: master 10 | 11 | defaults: 12 | run: 13 | working-directory: sha-crypt 14 | 15 | env: 16 | CARGO_INCREMENTAL: 0 17 | RUSTFLAGS: "-Dwarnings" 18 | 19 | jobs: 20 | build: 21 | runs-on: ubuntu-latest 22 | strategy: 23 | matrix: 24 | rust: 25 | - 1.85.0 # MSRV 26 | - stable 27 | target: 28 | - thumbv7em-none-eabi 29 | - wasm32-unknown-unknown 30 | steps: 31 | - uses: actions/checkout@v6 32 | - uses: RustCrypto/actions/cargo-cache@master 33 | - uses: dtolnay/rust-toolchain@master 34 | with: 35 | toolchain: ${{ matrix.rust }} 36 | targets: ${{ matrix.target }} 37 | - run: cargo build --target ${{ matrix.target }} --no-default-features 38 | - run: cargo build --target ${{ matrix.target }} --no-default-features --features password-hash 39 | 40 | minimal-versions: 41 | if: false # disabled while using pre-releases 42 | uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master 43 | with: 44 | working-directory: ${{ github.workflow }} 45 | 46 | test: 47 | runs-on: ubuntu-latest 48 | strategy: 49 | matrix: 50 | rust: 51 | - 1.85.0 # MSRV 52 | - stable 53 | steps: 54 | - uses: actions/checkout@v6 55 | - uses: RustCrypto/actions/cargo-cache@master 56 | - uses: dtolnay/rust-toolchain@master 57 | with: 58 | toolchain: ${{ matrix.rust }} 59 | - run: cargo test --no-default-features 60 | - run: cargo test 61 | - run: cargo test --all-features 62 | -------------------------------------------------------------------------------- /scrypt/README.md: -------------------------------------------------------------------------------- 1 | # RustCrypto: scrypt 2 | 3 | [![crate][crate-image]][crate-link] 4 | [![Docs][docs-image]][docs-link] 5 | [![Build Status][build-image]][build-link] 6 | ![Apache2/MIT licensed][license-image] 7 | ![Rust Version][rustc-image] 8 | [![Project Chat][chat-image]][chat-link] 9 | 10 | Pure Rust implementation of the [scrypt key derivation function][1], a sequential memory hard 11 | function which can also be used for password hashing. 12 | 13 | ## License 14 | 15 | Licensed under either of: 16 | 17 | * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 18 | * [MIT license](http://opensource.org/licenses/MIT) 19 | 20 | at your option. 21 | 22 | ### Contribution 23 | 24 | Unless you explicitly state otherwise, any contribution intentionally submitted 25 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 26 | dual licensed as above, without any additional terms or conditions. 27 | 28 | [//]: # (badges) 29 | 30 | [crate-image]: https://img.shields.io/crates/v/scrypt 31 | [crate-link]: https://crates.io/crates/scrypt 32 | [docs-image]: https://docs.rs/scrypt/badge.svg 33 | [docs-link]: https://docs.rs/scrypt/ 34 | [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg 35 | [rustc-image]: https://img.shields.io/badge/rustc-1.85+-blue.svg 36 | [chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg 37 | [chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260046-password-hashes 38 | [build-image]: https://github.com/RustCrypto/password-hashes/workflows/scrypt/badge.svg?branch=master&event=push 39 | [build-link]: https://github.com/RustCrypto/password-hashes/actions?query=workflow%3Ascrypt 40 | 41 | [//]: # (general links) 42 | 43 | [1]: https://www.tarsnap.com/scrypt.html 44 | -------------------------------------------------------------------------------- /pbkdf2/README.md: -------------------------------------------------------------------------------- 1 | # RustCrypto: PBKDF2 2 | 3 | [![crate][crate-image]][crate-link] 4 | [![Docs][docs-image]][docs-link] 5 | ![Apache2/MIT licensed][license-image] 6 | ![Rust Version][rustc-image] 7 | [![Project Chat][chat-image]][chat-link] 8 | [![Build Status][build-image]][build-link] 9 | 10 | Pure Rust implementation of the [Password-Based Key Derivation Function v2 (PBKDF2)][1] as specified 11 | in [RFC 2898][2]. 12 | 13 | ## License 14 | 15 | Licensed under either of: 16 | 17 | * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 18 | * [MIT license](http://opensource.org/licenses/MIT) 19 | 20 | at your option. 21 | 22 | ### Contribution 23 | 24 | Unless you explicitly state otherwise, any contribution intentionally submitted 25 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 26 | dual licensed as above, without any additional terms or conditions. 27 | 28 | [//]: # (badges) 29 | 30 | [crate-image]: https://img.shields.io/crates/v/pbkdf2 31 | [crate-link]: https://crates.io/crates/pbkdf2 32 | [docs-image]: https://docs.rs/pbkdf2/badge.svg 33 | [docs-link]: https://docs.rs/pbkdf2/ 34 | [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg 35 | [rustc-image]: https://img.shields.io/badge/rustc-1.85+-blue.svg 36 | [chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg 37 | [chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260046-password-hashes 38 | [build-image]: https://github.com/RustCrypto/password-hashes/workflows/pbkdf2/badge.svg?branch=master&event=push 39 | [build-link]: https://github.com/RustCrypto/password-hashes/actions?query=workflow%3Apbkdf2 40 | 41 | [//]: # (general links) 42 | 43 | [1]: https://en.wikipedia.org/wiki/PBKDF2 44 | [2]: https://datatracker.ietf.org/doc/html/rfc2898 45 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/scrypt.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use libfuzzer_sys::arbitrary::{Arbitrary, Result, Unstructured}; 3 | use libfuzzer_sys::fuzz_target; 4 | use scrypt::password_hash::{ 5 | Ident, PasswordHash, PasswordHasher, PasswordVerifier, Salt, SaltString, 6 | }; 7 | use scrypt::{scrypt, Scrypt}; 8 | 9 | #[derive(Debug)] 10 | pub struct ScryptRandParams(pub scrypt::Params); 11 | 12 | impl<'a> Arbitrary<'a> for ScryptRandParams { 13 | fn arbitrary(u: &mut Unstructured<'a>) -> Result { 14 | let log_n = u.int_in_range(0..=15)?; 15 | let r = u.int_in_range(1..=16)?; 16 | let p = u.int_in_range(1..=8)?; 17 | let len = u.int_in_range(10..=64)?; 18 | 19 | let params = scrypt::Params::new(log_n, r, p, len).unwrap(); 20 | Ok(Self(params)) 21 | } 22 | } 23 | 24 | fuzz_target!(|data: (&[u8], &[u8], ScryptRandParams)| { 25 | let (password, salt, ScryptRandParams(params)) = data; 26 | 27 | if password.len() > 64 { 28 | return; 29 | } 30 | 31 | if salt.len() < Salt::MIN_LENGTH || salt.len() > (6 * Salt::MAX_LENGTH) / 8 { 32 | return; 33 | } 34 | 35 | // Check direct hashing 36 | let mut result = [0u8; 64]; 37 | scrypt(password, salt, ¶ms, &mut result).unwrap(); 38 | 39 | // Check PHC hashing 40 | let salt_string = SaltString::encode_b64(salt).unwrap(); 41 | let phc_hash = Scrypt 42 | .hash_password_customized( 43 | password, 44 | Some(Ident::new_unwrap("scrypt")), 45 | None, 46 | params, 47 | &salt_string, 48 | ) 49 | .unwrap() 50 | .to_string(); 51 | 52 | // Check PHC verification 53 | let hash = PasswordHash::new(&phc_hash).unwrap(); 54 | Scrypt.verify_password(password, &hash).unwrap(); 55 | }); 56 | -------------------------------------------------------------------------------- /.github/workflows/scrypt.yml: -------------------------------------------------------------------------------- 1 | name: scrypt 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - "scrypt/**" 7 | - "Cargo.*" 8 | push: 9 | branches: master 10 | 11 | defaults: 12 | run: 13 | working-directory: scrypt 14 | 15 | env: 16 | CARGO_INCREMENTAL: 0 17 | RUSTFLAGS: "-Dwarnings" 18 | 19 | jobs: 20 | build: 21 | runs-on: ubuntu-latest 22 | strategy: 23 | matrix: 24 | rust: 25 | - 1.85.0 # MSRV 26 | - stable 27 | target: 28 | - thumbv7em-none-eabi 29 | - wasm32-unknown-unknown 30 | steps: 31 | - uses: actions/checkout@v6 32 | - uses: RustCrypto/actions/cargo-cache@master 33 | - uses: dtolnay/rust-toolchain@master 34 | with: 35 | toolchain: ${{ matrix.rust }} 36 | targets: ${{ matrix.target }} 37 | - run: cargo build --target ${{ matrix.target }} --no-default-features 38 | - run: cargo build --target ${{ matrix.target }} --no-default-features --features password-hash 39 | 40 | minimal-versions: 41 | # disabled until belt-block gets published 42 | if: false 43 | uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master 44 | with: 45 | working-directory: ${{ github.workflow }} 46 | 47 | test: 48 | runs-on: ubuntu-latest 49 | strategy: 50 | matrix: 51 | rust: 52 | - 1.85.0 # MSRV 53 | - stable 54 | steps: 55 | - uses: actions/checkout@v6 56 | - uses: RustCrypto/actions/cargo-cache@master 57 | - uses: dtolnay/rust-toolchain@master 58 | with: 59 | toolchain: ${{ matrix.rust }} 60 | - run: cargo test --no-default-features 61 | - run: cargo test 62 | - run: cargo test --all-features 63 | - run: cargo doc --no-default-features 64 | -------------------------------------------------------------------------------- /pbkdf2/tests/phc.rs: -------------------------------------------------------------------------------- 1 | //! Tests for `password-hash` crate integration with Password Hashing Competition (PHC) string 2 | //! format as the password hash serialization. 3 | //! 4 | //! PHC PBKDF2-SHA256 vectors adapted from: https://stackoverflow.com/a/5136918 5 | 6 | #![cfg(feature = "password-hash")] 7 | 8 | use hex_literal::hex; 9 | use pbkdf2::{Algorithm, Params, Pbkdf2, password_hash::CustomizedPasswordHasher}; 10 | 11 | const PASSWORD: &[u8] = b"passwordPASSWORDpassword"; 12 | const SALT: &[u8] = b"saltSALTsaltSALTsaltSALTsaltSALTsalt"; 13 | const EXPECTED_HASH: &str = "$pbkdf2-sha256$i=4096,\ 14 | l=40$c2FsdFNBTFRzYWx0U0FMVHNhbHRTQUxUc2FsdFNBTFRzYWx0$NIyJ28vTKy8y2BS4EW6EzysXNH68GAAYHE4qH7jdU\ 15 | +HGNVGMfaxH6Q"; 16 | 17 | /// Test with `algorithm: None` - uses default PBKDF2-SHA256 18 | /// 19 | /// Input: 20 | /// - P = "passwordPASSWORDpassword" (24 octets) 21 | /// - S = "saltSALTsaltSALTsaltSALTsaltSALTsalt" (36 octets) 22 | /// c = 4096 23 | /// dkLen = 40 24 | #[test] 25 | fn hash_with_default_algorithm() { 26 | let params = Params { 27 | rounds: 4096, 28 | output_length: 40, 29 | }; 30 | 31 | let hash = Pbkdf2 32 | .hash_password_customized(PASSWORD, SALT, None, None, params) 33 | .unwrap(); 34 | 35 | assert_eq!(hash.algorithm, *Algorithm::Pbkdf2Sha256.ident()); 36 | assert_eq!(hash.salt.unwrap().as_ref(), SALT); 37 | assert_eq!(Params::try_from(&hash).unwrap(), params); 38 | 39 | let expected_output = hex!( 40 | "34 8c 89 db cb d3 2b 2f 41 | 32 d8 14 b8 11 6e 84 cf 42 | 2b 17 34 7e bc 18 00 18 43 | 1c 4e 2a 1f b8 dd 53 e1 44 | c6 35 51 8c 7d ac 47 e9 " 45 | ); 46 | 47 | assert_eq!(hash.hash.unwrap().as_ref(), expected_output); 48 | assert_eq!(hash, EXPECTED_HASH.parse().unwrap()); 49 | } 50 | -------------------------------------------------------------------------------- /bcrypt-pbkdf/README.md: -------------------------------------------------------------------------------- 1 | # RustCrypto: bcrypt-pbkdf 2 | 3 | [![crate][crate-image]][crate-link] 4 | [![Docs][docs-image]][docs-link] 5 | [![Build Status][build-image]][build-link] 6 | ![Apache2/MIT licensed][license-image] 7 | ![Rust Version][rustc-image] 8 | [![Project Chat][chat-image]][chat-link] 9 | 10 | Pure Rust implementation of the [`bcrypt_pbkdf`] password-based key derivation 11 | function, a custom derivative of PBKDF2 [used in OpenSSH]. 12 | 13 | ## License 14 | 15 | Licensed under either of: 16 | 17 | * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 18 | * [MIT license](http://opensource.org/licenses/MIT) 19 | 20 | at your option. 21 | 22 | ### Contribution 23 | 24 | Unless you explicitly state otherwise, any contribution intentionally submitted 25 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 26 | dual licensed as above, without any additional terms or conditions. 27 | 28 | [//]: # (badges) 29 | 30 | [crate-image]: https://img.shields.io/crates/v/bcrypt-pbkdf 31 | [crate-link]: https://crates.io/crates/bcrypt-pbkdf 32 | [docs-image]: https://docs.rs/bcrypt-pbkdf/badge.svg 33 | [docs-link]: https://docs.rs/bcrypt-pbkdf/ 34 | [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg 35 | [rustc-image]: https://img.shields.io/badge/rustc-1.85+-blue.svg 36 | [chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg 37 | [chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260046-password-hashes 38 | [build-image]: https://github.com/RustCrypto/password-hashes/workflows/bcrypt-pbkdf/badge.svg?branch=master&event=push 39 | [build-link]: https://github.com/RustCrypto/password-hashes/actions?query=workflow%3Abcrypt-pbkdf 40 | 41 | [//]: # (links) 42 | 43 | [`bcrypt_pbkdf`]: https://flak.tedunangst.com/post/bcrypt-pbkdf 44 | [used in OpenSSH]: https://flak.tedunangst.com/post/new-openssh-key-format-and-bcrypt-pbkdf 45 | -------------------------------------------------------------------------------- /.github/workflows/balloon-hash.yml: -------------------------------------------------------------------------------- 1 | name: balloon-hash 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - "balloon-hash/**" 7 | - "Cargo.*" 8 | push: 9 | branches: master 10 | 11 | defaults: 12 | run: 13 | working-directory: balloon-hash 14 | 15 | env: 16 | CARGO_INCREMENTAL: 0 17 | RUSTFLAGS: "-Dwarnings" 18 | 19 | jobs: 20 | build: 21 | runs-on: ubuntu-latest 22 | strategy: 23 | matrix: 24 | rust: 25 | - 1.85.0 # MSRV 26 | - stable 27 | target: 28 | - thumbv7em-none-eabi 29 | - wasm32-unknown-unknown 30 | steps: 31 | - uses: actions/checkout@v6 32 | - uses: RustCrypto/actions/cargo-cache@master 33 | - uses: dtolnay/rust-toolchain@master 34 | with: 35 | toolchain: ${{ matrix.rust }} 36 | targets: ${{ matrix.target }} 37 | - run: cargo build --target ${{ matrix.target }} --release --no-default-features 38 | - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features password-hash 39 | 40 | minimal-versions: 41 | if: false # disabled while using pre-releases 42 | uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master 43 | with: 44 | working-directory: ${{ github.workflow }} 45 | 46 | test: 47 | runs-on: ubuntu-latest 48 | strategy: 49 | matrix: 50 | rust: 51 | - 1.85.0 # MSRV 52 | - stable 53 | steps: 54 | - uses: actions/checkout@v6 55 | - uses: RustCrypto/actions/cargo-cache@master 56 | - uses: dtolnay/rust-toolchain@master 57 | with: 58 | toolchain: ${{ matrix.rust }} 59 | - run: cargo test --release 60 | - run: cargo test --release --no-default-features --features alloc 61 | - run: cargo test --release --no-default-features --features alloc,zeroize 62 | - run: cargo test --release --all-features 63 | -------------------------------------------------------------------------------- /.github/workflows/password-auth.yml: -------------------------------------------------------------------------------- 1 | name: password-auth 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - ".github/workflows/password-auth.yml" 7 | - "argon2/**" 8 | - "password-auth/**" 9 | - "pbkdf2/**" 10 | - "scrypt/**" 11 | - "Cargo.*" 12 | push: 13 | branches: master 14 | 15 | defaults: 16 | run: 17 | working-directory: password-auth 18 | 19 | env: 20 | CARGO_INCREMENTAL: 0 21 | RUSTFLAGS: "-Dwarnings" 22 | 23 | jobs: 24 | test: 25 | runs-on: ubuntu-latest 26 | strategy: 27 | matrix: 28 | rust: 29 | - 1.85.0 # MSRV 30 | - stable 31 | steps: 32 | - uses: actions/checkout@v6 33 | - uses: RustCrypto/actions/cargo-cache@master 34 | - uses: dtolnay/rust-toolchain@master 35 | with: 36 | toolchain: ${{ matrix.rust }} 37 | - run: cargo test 38 | - run: cargo test --no-default-features --features argon2 39 | - run: cargo test --no-default-features --features pbkdf2 40 | - run: cargo test --no-default-features --features scrypt 41 | - run: cargo test --all-features 42 | 43 | wasm: 44 | env: 45 | RUSTFLAGS: --cfg getrandom_backend="wasm_js" 46 | runs-on: ubuntu-latest 47 | strategy: 48 | matrix: 49 | rust: 50 | - 1.85.0 # MSRV 51 | - stable 52 | steps: 53 | - uses: actions/checkout@v6 54 | - uses: RustCrypto/actions/cargo-cache@master 55 | - uses: dtolnay/rust-toolchain@master 56 | with: 57 | toolchain: ${{ matrix.rust }} 58 | targets: wasm32-unknown-unknown 59 | - run: cargo build --target wasm32-unknown-unknown --no-default-features --features wasm_js,argon2 60 | - run: cargo build --target wasm32-unknown-unknown --no-default-features --features wasm_js,pbkdf2 61 | - run: cargo build --target wasm32-unknown-unknown --no-default-features --features wasm_js,scrypt 62 | - run: cargo build --target wasm32-unknown-unknown --no-default-features --features wasm_js,argon2,pbkdf2,scrypt 63 | -------------------------------------------------------------------------------- /sha-crypt/README.md: -------------------------------------------------------------------------------- 1 | # RustCrypto: SHA-crypt password hash 2 | 3 | [![crate][crate-image]][crate-link] 4 | [![Docs][docs-image]][docs-link] 5 | ![Apache2/MIT licensed][license-image] 6 | ![Rust Version][rustc-image] 7 | [![Project Chat][chat-image]][chat-link] 8 | [![Build Status][build-image]][build-link] 9 | 10 | Pure Rust implementation of the [SHA-crypt password hash based on SHA-512][1], 11 | a legacy password hashing scheme supported by the [POSIX crypt C library][2]. 12 | 13 | Password hashes using this algorithm start with `$6$` when encoded using the 14 | [PHC string format][3]. 15 | 16 | ## License 17 | 18 | Licensed under either of: 19 | 20 | * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 21 | * [MIT license](http://opensource.org/licenses/MIT) 22 | 23 | at your option. 24 | 25 | ### Contribution 26 | 27 | Unless you explicitly state otherwise, any contribution intentionally submitted 28 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 29 | dual licensed as above, without any additional terms or conditions. 30 | 31 | [//]: # (badges) 32 | 33 | [crate-image]: https://img.shields.io/crates/v/sha-crypt 34 | [crate-link]: https://crates.io/crates/sha-crypt 35 | [docs-image]: https://docs.rs/sha-crypt/badge.svg 36 | [docs-link]: https://docs.rs/sha-crypt/ 37 | [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg 38 | [rustc-image]: https://img.shields.io/badge/rustc-1.81+-blue.svg 39 | [chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg 40 | [chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260046-password-hashes 41 | [build-image]: https://github.com/RustCrypto/password-hashes/workflows/sha-crypt/badge.svg?branch=master&event=push 42 | [build-link]: https://github.com/RustCrypto/password-hashes/actions?query=workflow%3Asha-crypt 43 | 44 | [//]: # (general links) 45 | 46 | [1]: https://www.akkadia.org/drepper/SHA-crypt.txt 47 | [2]: https://en.wikipedia.org/wiki/Crypt_(C) 48 | [3]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md 49 | -------------------------------------------------------------------------------- /balloon-hash/src/error.rs: -------------------------------------------------------------------------------- 1 | //! Error type 2 | 3 | use core::fmt; 4 | 5 | /// Result with balloon's [`Error`] type. 6 | pub type Result = core::result::Result; 7 | 8 | /// Error type. 9 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 10 | #[non_exhaustive] 11 | pub enum Error { 12 | /// Algorithm identifier invalid. 13 | AlgorithmInvalid, 14 | /// Memory cost is too small. 15 | MemoryTooLittle, 16 | /// Not enough threads. 17 | ThreadsTooFew, 18 | /// Too many threads. 19 | ThreadsTooMany, 20 | /// Time cost is too small. 21 | TimeTooSmall, 22 | /// Output size not correct. 23 | OutputSize { 24 | /// Output size provided. 25 | actual: usize, 26 | /// Output size expected. 27 | expected: usize, 28 | }, 29 | } 30 | 31 | impl fmt::Display for Error { 32 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 33 | match self { 34 | Error::AlgorithmInvalid => f.write_str("algorithm identifier invalid"), 35 | Error::MemoryTooLittle => f.write_str("memory cost is too small"), 36 | Error::ThreadsTooFew => f.write_str("not enough threads"), 37 | Error::ThreadsTooMany => f.write_str("too many threads"), 38 | Error::TimeTooSmall => f.write_str("time cost is too small"), 39 | Error::OutputSize { expected, .. } => { 40 | write!(f, "unexpected output size, expected {expected} bytes") 41 | } 42 | } 43 | } 44 | } 45 | 46 | #[cfg(feature = "password-hash")] 47 | impl From for password_hash::Error { 48 | fn from(err: Error) -> password_hash::Error { 49 | match err { 50 | Error::AlgorithmInvalid => password_hash::Error::Algorithm, 51 | Error::MemoryTooLittle 52 | | Error::ThreadsTooFew 53 | | Error::ThreadsTooMany 54 | | Error::TimeTooSmall => password_hash::Error::ParamsInvalid, 55 | Error::OutputSize { .. } => password_hash::Error::OutputSize, 56 | } 57 | } 58 | } 59 | 60 | impl core::error::Error for Error {} 61 | -------------------------------------------------------------------------------- /password-auth/src/errors.rs: -------------------------------------------------------------------------------- 1 | //! Error types. 2 | 3 | use alloc::string::ToString; 4 | use core::fmt; 5 | use password_hash::phc; 6 | 7 | /// Password hash parse errors. 8 | // This type has no public constructor and deliberately keeps `phc::Error` out of the public API 9 | // so we can upgrade the `phc` version without it being a breaking change 10 | #[derive(Clone, Copy, Eq, PartialEq)] 11 | pub struct ParseError(phc::Error); 12 | 13 | impl ParseError { 14 | /// Create a new parse error. 15 | pub(crate) fn new(err: phc::Error) -> Self { 16 | Self(err) 17 | } 18 | } 19 | 20 | impl fmt::Debug for ParseError { 21 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 22 | f.debug_tuple("ParseError") 23 | .field(&self.0.to_string()) 24 | .finish() 25 | } 26 | } 27 | 28 | impl fmt::Display for ParseError { 29 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 30 | write!(f, "{}", &self.0) 31 | } 32 | } 33 | 34 | impl core::error::Error for ParseError { 35 | fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { 36 | Some(&self.0) 37 | } 38 | } 39 | 40 | /// Password verification errors. 41 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 42 | pub enum VerifyError { 43 | /// Password hash parsing errors. 44 | Parse(ParseError), 45 | 46 | /// Password is invalid. 47 | PasswordInvalid, 48 | } 49 | 50 | impl fmt::Display for VerifyError { 51 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 52 | match self { 53 | Self::Parse(err) => write!(f, "{err}"), 54 | Self::PasswordInvalid => write!(f, "password is invalid"), 55 | } 56 | } 57 | } 58 | 59 | impl From for VerifyError { 60 | fn from(err: ParseError) -> VerifyError { 61 | VerifyError::Parse(err) 62 | } 63 | } 64 | 65 | impl core::error::Error for VerifyError { 66 | fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { 67 | match self { 68 | Self::Parse(err) => Some(err), 69 | _ => None, 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /sha-crypt/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## 0.5.0 (2023-03-04) 9 | ### Added 10 | - `sha256_crypt` ([#350]) 11 | 12 | ### Changed 13 | - Use `base64ct` for Base64 encoding ([#350]) 14 | - MSRV 1.60 ([#377]) 15 | - Relax `subtle` dependency version requirements ([#390]) 16 | 17 | ### Fixed 18 | - Support passwords longer than 64-bytes in length ([#328]) 19 | 20 | [#328]: https://github.com/RustCrypto/password-hashes/pull/328 21 | [#350]: https://github.com/RustCrypto/password-hashes/pull/350 22 | [#377]: https://github.com/RustCrypto/password-hashes/pull/377 23 | [#390]: https://github.com/RustCrypto/password-hashes/pull/390 24 | 25 | ## 0.4.0 (2022-03-18) 26 | ### Changed 27 | - 2021 edition upgrade; MSRV 1.56 ([#284]) 28 | 29 | [#284]: https://github.com/RustCrypto/password-hashes/pull/284 30 | 31 | ## 0.3.2 (2021-11-25) 32 | ### Changed 33 | - Bump `sha2` dependency to v0.10 ([#254]) 34 | 35 | [#254]: https://github.com/RustCrypto/password-hashes/pull/254 36 | 37 | ## 0.3.1 (2021-09-17) 38 | ### Fixed 39 | - Handle B64 decoding errors ([#242]) 40 | 41 | [#242]: https://github.com/RustCrypto/password-hashes/pull/242 42 | 43 | ## 0.3.0 (2021-08-27) 44 | ### Changed 45 | - Use `resolver = "2"`; MSRV 1.51+ ([#220]) 46 | 47 | [#220]: https://github.com/RustCrypto/password-hashes/pull/220 48 | 49 | ## 0.2.1 (2021-07-20) 50 | ### Changed 51 | - Pin `subtle` dependency to v2.4 ([#190]) 52 | 53 | [#190]: https://github.com/RustCrypto/password-hashes/pull/190 54 | 55 | ## 0.2.0 (2021-01-29) 56 | ### Changed 57 | - Bump `rand` dependency to v0.8 ([#86]) 58 | - Rename `include_simple` feature to `simple` ([#99]) 59 | - Remove `Vec` from public API ([#113]) 60 | - MSRV 1.47+ ([#113]) 61 | 62 | [#86]: https://github.com/RustCrypto/password-hashing/pull/86 63 | [#99]: https://github.com/RustCrypto/password-hashing/pull/99 64 | [#113]: https://github.com/RustCrypto/password-hashing/pull/113 65 | 66 | ## 0.1.0 (2020-12-28) 67 | - Initial release 68 | -------------------------------------------------------------------------------- /balloon-hash/README.md: -------------------------------------------------------------------------------- 1 | # RustCrypto: Balloon Hash 2 | 3 | [![crate][crate-image]][crate-link] 4 | [![Docs][docs-image]][docs-link] 5 | [![Build Status][build-image]][build-link] 6 | ![Apache2/MIT licensed][license-image] 7 | ![Rust Version][rustc-image] 8 | [![Project Chat][chat-image]][chat-link] 9 | 10 | Pure Rust implementation of the [Balloon] password hashing function. 11 | 12 | ## About 13 | 14 | This crate contains an implementation of the Balloon password hashing 15 | function as specified in the paper 16 | [Balloon Hashing: A Memory-Hard Function Providing Provable Protection Against Sequential Attacks][paper]. 17 | 18 | This algorithm is first practical password hashing function that provides: 19 | 20 | - Memory hardness which is proven in the random-oracle model 21 | - Password-independent access 22 | - Performance which meets or exceeds the best heuristically secure 23 | password-hashing algorithms 24 | 25 | ## License 26 | 27 | Licensed under either of: 28 | 29 | * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 30 | * [MIT license](http://opensource.org/licenses/MIT) 31 | 32 | at your option. 33 | 34 | ### Contribution 35 | 36 | Unless you explicitly state otherwise, any contribution intentionally submitted 37 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 38 | dual licensed as above, without any additional terms or conditions. 39 | 40 | [//]: # (badges) 41 | 42 | [crate-image]: https://img.shields.io/crates/v/balloon-hash 43 | [crate-link]: https://crates.io/crates/balloon-hash 44 | [docs-image]: https://docs.rs/balloon-hash/badge.svg 45 | [docs-link]: https://docs.rs/balloon-hash/ 46 | [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg 47 | [rustc-image]: https://img.shields.io/badge/rustc-1.85+-blue.svg 48 | [chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg 49 | [chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260046-password-hashes 50 | [build-image]: https://github.com/RustCrypto/password-hashes/actions/workflows/balloon-hash.yml/badge.svg 51 | [build-link]: https://github.com/RustCrypto/password-hashes/actions/workflows/balloon-hash.yml 52 | 53 | [//]: # (general links) 54 | 55 | [Balloon]: https://en.wikipedia.org/wiki/Balloon_hashing 56 | [Paper]: https://eprint.iacr.org/2016/027.pdf 57 | -------------------------------------------------------------------------------- /argon2/README.md: -------------------------------------------------------------------------------- 1 | # RustCrypto: Argon2 2 | 3 | [![crate][crate-image]][crate-link] 4 | [![Docs][docs-image]][docs-link] 5 | [![Build Status][build-image]][build-link] 6 | ![Apache2/MIT licensed][license-image] 7 | ![Rust Version][rustc-image] 8 | [![Project Chat][chat-image]][chat-link] 9 | 10 | Pure Rust implementation of the [Argon2] password hashing function. 11 | 12 | # About 13 | 14 | Argon2 is a memory-hard [key derivation function] chosen as the winner of 15 | the [Password Hashing Competition] in July 2015. 16 | 17 | It implements the following three algorithmic variants: 18 | 19 | - **Argon2d**: maximizes resistance to GPU cracking attacks 20 | - **Argon2i**: optimized to resist side-channel attacks 21 | - **Argon2id**: (default) hybrid version combining both Argon2i and Argon2d 22 | 23 | Support is provided for embedded (i.e. `no_std`) environments, including 24 | ones without `alloc` support. 25 | 26 | ## License 27 | 28 | Licensed under either of: 29 | 30 | * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 31 | * [MIT license](http://opensource.org/licenses/MIT) 32 | 33 | at your option. 34 | 35 | ### Contribution 36 | 37 | Unless you explicitly state otherwise, any contribution intentionally submitted 38 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 39 | dual licensed as above, without any additional terms or conditions. 40 | 41 | [//]: # (badges) 42 | 43 | [crate-image]: https://img.shields.io/crates/v/argon2 44 | [crate-link]: https://crates.io/crates/argon2 45 | [docs-image]: https://docs.rs/argon2/badge.svg 46 | [docs-link]: https://docs.rs/argon2/ 47 | [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg 48 | [rustc-image]: https://img.shields.io/badge/rustc-1.85+-blue.svg 49 | [chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg 50 | [chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260046-password-hashes 51 | [build-image]: https://github.com/RustCrypto/password-hashes/workflows/argon2/badge.svg?branch=master&event=push 52 | [build-link]: https://github.com/RustCrypto/password-hashes/actions?query=workflow%3Aargon2 53 | 54 | [//]: # (general links) 55 | 56 | [Argon2]: https://en.wikipedia.org/wiki/Argon2 57 | [key derivation function]: https://en.wikipedia.org/wiki/Key_derivation_function 58 | [Password Hashing Competition]: https://www.password-hashing.net/ 59 | -------------------------------------------------------------------------------- /sha-crypt/src/params.rs: -------------------------------------------------------------------------------- 1 | //! Algorithm parameters. 2 | 3 | use crate::{Error, Result}; 4 | use core::{ 5 | default::Default, 6 | fmt::{self, Display}, 7 | str::FromStr, 8 | }; 9 | 10 | const ROUNDS_PARAM: &str = "rounds="; 11 | 12 | /// Algorithm parameters. 13 | #[derive(Debug, Clone)] 14 | pub struct Params { 15 | /// Number of times to apply the digest function 16 | pub(crate) rounds: u32, 17 | } 18 | 19 | impl Params { 20 | /// Default number of rounds. 21 | pub const ROUNDS_DEFAULT: u32 = 5_000; 22 | 23 | /// Minimum number of rounds allowed. 24 | pub const ROUNDS_MIN: u32 = 1_000; 25 | 26 | /// Maximum number of rounds allowed. 27 | pub const ROUNDS_MAX: u32 = 999_999_999; 28 | 29 | /// Create new algorithm parameters. 30 | pub fn new(rounds: u32) -> Result { 31 | match rounds { 32 | Self::ROUNDS_MIN..=Self::ROUNDS_MAX => Ok(Params { rounds }), 33 | _ => Err(Error::RoundsInvalid), 34 | } 35 | } 36 | } 37 | 38 | impl Default for Params { 39 | fn default() -> Self { 40 | Params { 41 | rounds: Self::ROUNDS_DEFAULT, 42 | } 43 | } 44 | } 45 | 46 | impl Display for Params { 47 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 48 | write!(f, "rounds={}", self.rounds) 49 | } 50 | } 51 | 52 | impl FromStr for Params { 53 | type Err = Error; 54 | 55 | fn from_str(s: &str) -> Result { 56 | if s.is_empty() { 57 | return Ok(Self::default()); 58 | } 59 | 60 | if let Some(rounds_str) = s.strip_prefix(ROUNDS_PARAM) { 61 | Self::new(rounds_str.parse().map_err(|_| Error::RoundsInvalid)?) 62 | } else { 63 | Err(Error::ParamsInvalid) 64 | } 65 | } 66 | } 67 | 68 | #[cfg(test)] 69 | mod tests { 70 | use super::Params; 71 | 72 | #[test] 73 | fn test_sha256_crypt_invalid_rounds() { 74 | let params = Params::new(Params::ROUNDS_MAX + 1); 75 | assert!(params.is_err()); 76 | 77 | let params = Params::new(Params::ROUNDS_MIN - 1); 78 | assert!(params.is_err()); 79 | } 80 | 81 | #[test] 82 | fn test_sha512_crypt_invalid_rounds() { 83 | let params = Params::new(Params::ROUNDS_MAX + 1); 84 | assert!(params.is_err()); 85 | 86 | let params = Params::new(Params::ROUNDS_MIN - 1); 87 | assert!(params.is_err()); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /yescrypt/src/mode.rs: -------------------------------------------------------------------------------- 1 | //! yescrypt modes. 2 | 3 | use crate::{Error, Result}; 4 | 5 | // Bits which represent the "flavor" of yescrypt when using `Mode::Rw` 6 | const ROUNDS_6: u32 = 0b000001; 7 | const GATHER_4: u32 = 0b000100; 8 | const SIMPLE_2: u32 = 0b001000; 9 | const SBOX_12K: u32 = 0b100000; 10 | 11 | // Bits representing the RW "flavor" 12 | // TODO(tarcieri): support other flavors of yescrypt? 13 | const RW_FLAVOR: u32 = 2 | ROUNDS_6 | GATHER_4 | SIMPLE_2 | SBOX_12K; 14 | 15 | /// yescrypt modes: various ways yescrypt can operate. 16 | /// 17 | /// [`Mode::default`] (`Rw`) is recommended. 18 | #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] 19 | #[repr(u32)] 20 | pub enum Mode { 21 | /// classic scrypt: yescrypt is a superset of scrypt. 22 | Classic = 0, 23 | 24 | /// write-once/read-many: conservative enhancement of classic scrypt. 25 | Worm = 1, 26 | 27 | /// yescrypt’s native mode: read-write (recommended/default). 28 | #[default] 29 | Rw = RW_FLAVOR, 30 | } 31 | 32 | impl Mode { 33 | /// Is the mode scrypt classic? 34 | pub fn is_classic(self) -> bool { 35 | self == Self::Classic 36 | } 37 | 38 | /// Is the mode write-once/read-many? 39 | pub fn is_worm(self) -> bool { 40 | self == Self::Worm 41 | } 42 | 43 | /// Is the mode the yescrypt native read-write mode? (default) 44 | pub fn is_rw(self) -> bool { 45 | self == Self::Rw 46 | } 47 | } 48 | 49 | impl TryFrom for Mode { 50 | type Error = Error; 51 | 52 | fn try_from(value: u32) -> Result { 53 | match value { 54 | 0 => Ok(Mode::Classic), 55 | 1 => Ok(Mode::Worm), 56 | RW_FLAVOR => Ok(Mode::Rw), 57 | _ => Err(Error::Params), 58 | } 59 | } 60 | } 61 | 62 | impl From for u32 { 63 | fn from(mode: Mode) -> u32 { 64 | mode as u32 65 | } 66 | } 67 | 68 | #[cfg(test)] 69 | mod tests { 70 | use super::Mode; 71 | 72 | #[test] 73 | fn flavor() { 74 | assert_eq!(0u32, Mode::Classic.into()); 75 | assert_eq!(1u32, Mode::Worm.into()); 76 | assert_eq!(0b101111u32, Mode::default().into()); 77 | } 78 | 79 | #[test] 80 | fn try_from() { 81 | assert_eq!(Mode::try_from(0).unwrap(), Mode::Classic); 82 | assert_eq!(Mode::try_from(1).unwrap(), Mode::Worm); 83 | assert_eq!(Mode::try_from(0b101111).unwrap(), Mode::Rw); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /scrypt/src/romix.rs: -------------------------------------------------------------------------------- 1 | /// Execute the ROMix operation in-place. 2 | /// b - the data to operate on 3 | /// v - a temporary variable to store the vector V 4 | /// t - a temporary variable to store the result of the xor 5 | /// n - the scrypt parameter N 6 | #[allow(clippy::many_single_char_names)] 7 | pub(crate) fn scrypt_ro_mix(b: &mut [u8], v: &mut [u8], t: &mut [u8], n: usize) { 8 | fn integerify(x: &[u8], n: usize) -> usize { 9 | // n is a power of 2, so n - 1 gives us a bitmask that we can use to perform a calculation 10 | // mod n using a simple bitwise and. 11 | let mask = n - 1; 12 | // This cast is safe since we're going to get the value mod n (which is a power of 2), so we 13 | // don't have to care about truncating any of the high bits off 14 | //let result = (LittleEndian::read_u32(&x[x.len() - 64..x.len() - 60]) as usize) & mask; 15 | let t = u32::from_le_bytes(x[x.len() - 64..x.len() - 60].try_into().unwrap()); 16 | (t as usize) & mask 17 | } 18 | 19 | let len = b.len(); 20 | 21 | for chunk in v.chunks_mut(len) { 22 | chunk.copy_from_slice(b); 23 | scrypt_block_mix(chunk, b); 24 | } 25 | 26 | for _ in 0..n { 27 | let j = integerify(b, n); 28 | xor(b, &v[j * len..(j + 1) * len], t); 29 | scrypt_block_mix(t, b); 30 | } 31 | } 32 | 33 | /// Execute the BlockMix operation 34 | /// input - the input vector. The length must be a multiple of 128. 35 | /// output - the output vector. Must be the same length as input. 36 | fn scrypt_block_mix(input: &[u8], output: &mut [u8]) { 37 | use salsa20::{ 38 | SalsaCore, 39 | cipher::{StreamCipherCore, typenum::U4}, 40 | }; 41 | 42 | type Salsa20_8 = SalsaCore; 43 | 44 | let mut x = [0u8; 64]; 45 | x.copy_from_slice(&input[input.len() - 64..]); 46 | 47 | let mut t = [0u8; 64]; 48 | 49 | for (i, chunk) in input.chunks(64).enumerate() { 50 | xor(&x, chunk, &mut t); 51 | 52 | let mut t2 = [0u32; 16]; 53 | 54 | for (c, b) in t.chunks_exact(4).zip(t2.iter_mut()) { 55 | *b = u32::from_le_bytes(c.try_into().unwrap()); 56 | } 57 | 58 | Salsa20_8::from_raw_state(t2).write_keystream_block((&mut x).into()); 59 | 60 | let pos = if i % 2 == 0 { 61 | (i / 2) * 64 62 | } else { 63 | (i / 2) * 64 + input.len() / 2 64 | }; 65 | 66 | output[pos..pos + 64].copy_from_slice(&x); 67 | } 68 | } 69 | 70 | fn xor(x: &[u8], y: &[u8], output: &mut [u8]) { 71 | for ((out, &x_i), &y_i) in output.iter_mut().zip(x.iter()).zip(y.iter()) { 72 | *out = x_i ^ y_i; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /benches/src/argon2.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeSet; 2 | 3 | use argon2::*; 4 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 5 | use pprof::criterion::{Output, PProfProfiler}; 6 | 7 | const BENCH_PASSWORD: &[u8] = b"hunter2"; 8 | const BENCH_SALT: &[u8] = b"pepper42"; 9 | 10 | fn bench_default_params(c: &mut Criterion) { 11 | for algorithm in [Algorithm::Argon2i, Algorithm::Argon2d, Algorithm::Argon2id] { 12 | for version in [Version::V0x10, Version::V0x13] { 13 | let test_name = format!("{algorithm} {version:?}"); 14 | c.bench_function(&test_name, |b| { 15 | let mut out = [0u8; 32]; 16 | let argon2 = Argon2::new(algorithm, version, Params::default()); 17 | b.iter(|| { 18 | argon2 19 | .hash_password_into( 20 | black_box(BENCH_PASSWORD), 21 | black_box(BENCH_SALT), 22 | &mut out, 23 | ) 24 | .unwrap() 25 | }) 26 | }); 27 | } 28 | } 29 | } 30 | 31 | fn bench_vary_params(c: &mut Criterion) { 32 | let mut tests = BTreeSet::new(); 33 | // Vary `m_cost`. 34 | for m_cost in [2 * 1024, 16 * 1024, 32 * 1024, 64 * 1024, 256 * 1024] { 35 | tests.insert((m_cost, 4, 4)); 36 | } 37 | // Vary `t_cost`. 38 | for t_cost in [1, 2, 4, 8, 16] { 39 | tests.insert((32 * 1024, t_cost, 4)); 40 | } 41 | // Vary `p_cost`. 42 | for p_cost in [1, 2, 4, 8, 16] { 43 | for m_mib in [256 * 1024, 1024 * 1024] { 44 | tests.insert((m_mib, 1, p_cost)); 45 | } 46 | for t_cost in [1, 2, 4] { 47 | tests.insert((32 * 1024, t_cost, p_cost)); 48 | } 49 | } 50 | for (m_cost, t_cost, p_cost) in tests { 51 | let test_name = format!("argon2id V0x13 m={m_cost} t={t_cost} p={p_cost}"); 52 | c.bench_function(&test_name, |b| { 53 | let mut out = [0u8; 32]; 54 | let params = Params::new(m_cost, t_cost, p_cost, Some(32)).unwrap(); 55 | let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params); 56 | b.iter(|| { 57 | argon2 58 | .hash_password_into(black_box(BENCH_PASSWORD), black_box(BENCH_SALT), &mut out) 59 | .unwrap() 60 | }) 61 | }); 62 | } 63 | } 64 | 65 | criterion_group!( 66 | name = benches; 67 | config = Criterion::default().with_profiler(PProfProfiler::new(300, Output::Flamegraph(None))); 68 | targets = 69 | bench_default_params, 70 | bench_vary_params, 71 | ); 72 | criterion_main!(benches); 73 | -------------------------------------------------------------------------------- /argon2/src/blake2b_long.rs: -------------------------------------------------------------------------------- 1 | //! The variable length hash function used in the Argon2 algorithm. 2 | 3 | use crate::{Error, Result}; 4 | 5 | use blake2::{ 6 | Blake2b512, Blake2bVarCore, 7 | digest::{ 8 | Digest, 9 | block_api::{UpdateCore, VariableOutputCore}, 10 | block_buffer::LazyBuffer, 11 | }, 12 | }; 13 | 14 | pub fn blake2b_long(inputs: &[&[u8]], out: &mut [u8]) -> Result<()> { 15 | if out.is_empty() { 16 | return Err(Error::OutputTooShort); 17 | } 18 | 19 | let len_bytes = u32::try_from(out.len()) 20 | .map(|v| v.to_le_bytes()) 21 | .map_err(|_| Error::OutputTooLong)?; 22 | 23 | // Use blake2b directly if the output is small enough. 24 | if let Ok(mut hasher) = Blake2bVarCore::new(out.len()) { 25 | let mut buf = LazyBuffer::new(&len_bytes); 26 | 27 | for input in inputs { 28 | buf.digest_blocks(input, |blocks| hasher.update_blocks(blocks)); 29 | } 30 | 31 | let mut full_out = Default::default(); 32 | hasher.finalize_variable_core(&mut buf, &mut full_out); 33 | let out_src = &full_out[..out.len()]; 34 | out.copy_from_slice(out_src); 35 | 36 | return Ok(()); 37 | } 38 | 39 | // Calculate longer hashes by first calculating a full 64 byte hash 40 | let half_hash_len = Blake2b512::output_size() / 2; 41 | let mut digest = Blake2b512::new(); 42 | 43 | digest.update(len_bytes); 44 | for input in inputs { 45 | digest.update(input); 46 | } 47 | let mut last_output = digest.finalize(); 48 | 49 | // Then we write the first 32 bytes of this hash to the output 50 | let (first_chunk, mut out) = out.split_at_mut(half_hash_len); 51 | first_chunk.copy_from_slice(&last_output[..half_hash_len]); 52 | 53 | // Next, we write a number of 32 byte blocks to the output. 54 | // Each block is the first 32 bytes of the hash of the last block. 55 | // The very last block of the output is excluded, and has a variable 56 | // length in range [1, 32]. 57 | while out.len() > 64 { 58 | let (chunk, tail) = out.split_at_mut(half_hash_len); 59 | out = tail; 60 | last_output = Blake2b512::digest(last_output); 61 | chunk.copy_from_slice(&last_output[..half_hash_len]); 62 | } 63 | 64 | // Calculate the last block with VarBlake2b. 65 | let mut hasher = Blake2bVarCore::new(out.len()) 66 | .expect("`out.len()` is guaranteed to be smaller or equal to 64"); 67 | let mut buf = LazyBuffer::new(&last_output); 68 | let mut full_out = Default::default(); 69 | hasher.finalize_variable_core(&mut buf, &mut full_out); 70 | let out_src = &full_out[..out.len()]; 71 | out.copy_from_slice(out_src); 72 | 73 | Ok(()) 74 | } 75 | -------------------------------------------------------------------------------- /yescrypt/README.md: -------------------------------------------------------------------------------- 1 | # RustCrypto: yescrypt 2 | 3 | [![crate][crate-image]][crate-link] 4 | [![Docs][docs-image]][docs-link] 5 | [![Build Status][build-image]][build-link] 6 | ![Apache2/MIT licensed][license-image] 7 | ![Rust Version][rustc-image] 8 | [![Project Chat][chat-image]][chat-link] 9 | 10 | Pure Rust implementation of the [yescrypt] password-based key derivation function. 11 | 12 | ## About 13 | 14 | yescrypt is a variant of the [scrypt] password-based key derivation function and finalist in the 15 | [Password Hashing Competition]. It has been adopted by several Linux distributions for the system 16 | password hashing function, including Fedora, Debian, Ubuntu, and Arch. 17 | 18 | The algorithm is described in [yescrypt - a Password Hashing Competition submission][paper]. 19 | 20 | ## ⚠️ Security Warning 21 | 22 | The implementation contained in this crate has never been independently audited! 23 | 24 | USE AT YOUR OWN RISK! 25 | 26 | Note that this crate is in an early stage of implementation and may contain bugs or features which 27 | do not work correctly. 28 | 29 | ## Minimum Supported Rust Version (MSRV) Policy 30 | 31 | MSRV increases are not considered breaking changes and can happen in patch releases. 32 | 33 | The crate MSRV accounts for all supported targets and crate feature combinations, excluding 34 | explicitly unstable features. 35 | 36 | ## License 37 | 38 | Licensed under either of: 39 | 40 | - [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) 41 | - [MIT license](https://opensource.org/licenses/MIT) 42 | 43 | at your option. 44 | 45 | ### Contribution 46 | 47 | Unless you explicitly state otherwise, any contribution intentionally submitted 48 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 49 | dual licensed as above, without any additional terms or conditions. 50 | 51 | [//]: # (badges) 52 | 53 | [crate-image]: https://img.shields.io/crates/v/yescrypt 54 | [crate-link]: https://crates.io/crates/yescrypt 55 | [docs-image]: https://docs.rs/yescrypt/badge.svg 56 | [docs-link]: https://docs.rs/yescrypt/ 57 | [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg 58 | [rustc-image]: https://img.shields.io/badge/rustc-1.85+-blue.svg 59 | [chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg 60 | [chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260046-password-hashes 61 | [build-image]: https://github.com/RustCrypto/password-hashes/actions/workflows/yescrypt.yml/badge.svg 62 | [build-link]: https://github.com/RustCrypto/password-hashes/actions/workflows/yescrypt.yml 63 | 64 | [//]: # (links) 65 | 66 | [yescrypt]: https://www.openwall.com/yescrypt/ 67 | [scrypt]: https://en.wikipedia.org/wiki/Scrypt 68 | [Password Hashing Competition]: https://www.password-hashing.net/ 69 | [paper]: https://www.password-hashing.net/submissions/specs/yescrypt-v2.pdf 70 | -------------------------------------------------------------------------------- /.github/workflows/yescrypt.yml: -------------------------------------------------------------------------------- 1 | name: yescrypt 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - ".github/workflows/yescrypt.yml" 7 | - "yescrypt/**" 8 | - "Cargo.*" 9 | push: 10 | branches: master 11 | 12 | defaults: 13 | run: 14 | working-directory: yescrypt 15 | 16 | env: 17 | CARGO_INCREMENTAL: 0 18 | RUSTFLAGS: "-Dwarnings" 19 | 20 | jobs: 21 | minimal-versions: 22 | if: false # disabled while using pre-releases 23 | uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master 24 | with: 25 | working-directory: ${{ github.workflow }} 26 | 27 | test: 28 | runs-on: ubuntu-latest 29 | strategy: 30 | matrix: 31 | include: 32 | # 32-bit Linux 33 | - target: i686-unknown-linux-gnu 34 | rust: 1.85.0 # MSRV 35 | deps: sudo apt update && sudo apt install gcc-multilib 36 | - target: i686-unknown-linux-gnu 37 | rust: stable 38 | deps: sudo apt update && sudo apt install gcc-multilib 39 | 40 | # 64-bit Linux 41 | - target: x86_64-unknown-linux-gnu 42 | rust: 1.85.0 # MSRV 43 | - target: x86_64-unknown-linux-gnu 44 | rust: stable 45 | steps: 46 | - uses: actions/checkout@v6 47 | - uses: RustCrypto/actions/cargo-cache@master 48 | - uses: dtolnay/rust-toolchain@master 49 | with: 50 | toolchain: ${{ matrix.rust }} 51 | targets: ${{ matrix.target }} 52 | - uses: RustCrypto/actions/cargo-hack-install@master 53 | - run: ${{ matrix.deps }} 54 | - run: cargo check --target ${{ matrix.target }} --all-features 55 | - run: cargo hack test --target ${{ matrix.target }} --feature-powerset 56 | - run: cargo test --target ${{ matrix.target }} --all-features --release 57 | 58 | careful: 59 | runs-on: ubuntu-latest 60 | steps: 61 | - uses: actions/checkout@v6 62 | - uses: dtolnay/rust-toolchain@master 63 | with: 64 | toolchain: nightly-2025-08-10 65 | - run: cargo install cargo-careful 66 | - run: cargo careful test --all-features 67 | - run: cargo careful test --all-features --release 68 | 69 | cross: 70 | strategy: 71 | matrix: 72 | include: 73 | - target: powerpc-unknown-linux-gnu 74 | rust: 1.85.0 # MSRV 75 | - target: powerpc-unknown-linux-gnu 76 | rust: stable 77 | runs-on: ubuntu-latest 78 | steps: 79 | - uses: actions/checkout@v6 80 | - run: ${{ matrix.deps }} 81 | - uses: dtolnay/rust-toolchain@master 82 | with: 83 | toolchain: ${{ matrix.rust }} 84 | targets: ${{ matrix.target }} 85 | - uses: RustCrypto/actions/cross-install@master 86 | - run: cross test --target ${{ matrix.target }} --all-features 87 | - run: cross test --target ${{ matrix.target }} --all-features --release 88 | -------------------------------------------------------------------------------- /scrypt/tests/mod.rs: -------------------------------------------------------------------------------- 1 | use scrypt::{Params, scrypt}; 2 | 3 | struct Test { 4 | password: &'static str, 5 | salt: &'static str, 6 | log_n: u8, 7 | r: u32, 8 | p: u32, 9 | expected: Vec, 10 | } 11 | 12 | // Test vectors from [1]. The last test vector is omitted because it takes 13 | // too long to run. 14 | fn tests() -> Vec { 15 | vec![ 16 | Test { 17 | password: "", 18 | salt: "", 19 | log_n: 4, 20 | r: 1, 21 | p: 1, 22 | expected: vec![ 23 | 0x77, 0xd6, 0x57, 0x62, 0x38, 0x65, 0x7b, 0x20, 0x3b, 0x19, 0xca, 0x42, 0xc1, 0x8a, 24 | 0x04, 0x97, 0xf1, 0x6b, 0x48, 0x44, 0xe3, 0x07, 0x4a, 0xe8, 0xdf, 0xdf, 0xfa, 0x3f, 25 | 0xed, 0xe2, 0x14, 0x42, 0xfc, 0xd0, 0x06, 0x9d, 0xed, 0x09, 0x48, 0xf8, 0x32, 0x6a, 26 | 0x75, 0x3a, 0x0f, 0xc8, 0x1f, 0x17, 0xe8, 0xd3, 0xe0, 0xfb, 0x2e, 0x0d, 0x36, 0x28, 27 | 0xcf, 0x35, 0xe2, 0x0c, 0x38, 0xd1, 0x89, 0x06, 28 | ], 29 | }, 30 | Test { 31 | password: "password", 32 | salt: "NaCl", 33 | log_n: 10, 34 | r: 8, 35 | p: 16, 36 | expected: vec![ 37 | 0xfd, 0xba, 0xbe, 0x1c, 0x9d, 0x34, 0x72, 0x00, 0x78, 0x56, 0xe7, 0x19, 0x0d, 0x01, 38 | 0xe9, 0xfe, 0x7c, 0x6a, 0xd7, 0xcb, 0xc8, 0x23, 0x78, 0x30, 0xe7, 0x73, 0x76, 0x63, 39 | 0x4b, 0x37, 0x31, 0x62, 0x2e, 0xaf, 0x30, 0xd9, 0x2e, 0x22, 0xa3, 0x88, 0x6f, 0xf1, 40 | 0x09, 0x27, 0x9d, 0x98, 0x30, 0xda, 0xc7, 0x27, 0xaf, 0xb9, 0x4a, 0x83, 0xee, 0x6d, 41 | 0x83, 0x60, 0xcb, 0xdf, 0xa2, 0xcc, 0x06, 0x40, 42 | ], 43 | }, 44 | Test { 45 | password: "pleaseletmein", 46 | salt: "SodiumChloride", 47 | log_n: 14, 48 | r: 8, 49 | p: 1, 50 | expected: vec![ 51 | 0x70, 0x23, 0xbd, 0xcb, 0x3a, 0xfd, 0x73, 0x48, 0x46, 0x1c, 0x06, 0xcd, 0x81, 0xfd, 52 | 0x38, 0xeb, 0xfd, 0xa8, 0xfb, 0xba, 0x90, 0x4f, 0x8e, 0x3e, 0xa9, 0xb5, 0x43, 0xf6, 53 | 0x54, 0x5d, 0xa1, 0xf2, 0xd5, 0x43, 0x29, 0x55, 0x61, 0x3f, 0x0f, 0xcf, 0x62, 0xd4, 54 | 0x97, 0x05, 0x24, 0x2a, 0x9a, 0xf9, 0xe6, 0x1e, 0x85, 0xdc, 0x0d, 0x65, 0x1e, 0x40, 55 | 0xdf, 0xcf, 0x01, 0x7b, 0x45, 0x57, 0x58, 0x87, 56 | ], 57 | }, 58 | ] 59 | } 60 | 61 | #[test] 62 | fn test_scrypt() { 63 | let tests = tests(); 64 | for t in tests.iter() { 65 | let mut result = vec![0u8; t.expected.len()]; 66 | let params = Params::new(t.log_n, t.r, t.p).unwrap(); 67 | scrypt( 68 | t.password.as_bytes(), 69 | t.salt.as_bytes(), 70 | ¶ms, 71 | &mut result, 72 | ) 73 | .unwrap(); 74 | assert!(result == t.expected); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /scrypt/src/phc.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of the `password-hash` crate API. 2 | 3 | pub use password_hash::phc::{Ident, Output, PasswordHash, Salt}; 4 | 5 | use crate::{Params, Scrypt, scrypt}; 6 | use password_hash::{CustomizedPasswordHasher, Error, PasswordHasher, Result, Version}; 7 | 8 | /// Algorithm name 9 | const ALG_NAME: &str = "scrypt"; 10 | 11 | /// Algorithm identifier 12 | pub const ALG_ID: Ident = Ident::new_unwrap(ALG_NAME); 13 | 14 | impl CustomizedPasswordHasher for Scrypt { 15 | type Params = Params; 16 | 17 | fn hash_password_customized( 18 | &self, 19 | password: &[u8], 20 | salt: &[u8], 21 | alg_id: Option<&str>, 22 | version: Option, 23 | params: Params, 24 | ) -> Result { 25 | match alg_id { 26 | Some(ALG_NAME) | None => (), 27 | Some(_) => return Err(Error::Algorithm), 28 | } 29 | 30 | // Versions unsupported 31 | if version.is_some() { 32 | return Err(Error::Version); 33 | } 34 | 35 | let salt = Salt::new(salt)?; 36 | let len = params.len.unwrap_or(Params::RECOMMENDED_LEN); 37 | 38 | let mut buffer = [0u8; Output::MAX_LENGTH]; 39 | let out = buffer.get_mut(..len).ok_or(Error::OutputSize)?; 40 | scrypt(password, &salt, ¶ms, out).map_err(|_| Error::OutputSize)?; 41 | let output = Output::new(out)?; 42 | 43 | Ok(PasswordHash { 44 | algorithm: ALG_ID, 45 | version: None, 46 | params: params.try_into()?, 47 | salt: Some(salt), 48 | hash: Some(output), 49 | }) 50 | } 51 | } 52 | 53 | impl PasswordHasher for Scrypt { 54 | fn hash_password_with_salt(&self, password: &[u8], salt: &[u8]) -> Result { 55 | self.hash_password_customized(password, salt, None, None, Params::default()) 56 | } 57 | } 58 | 59 | #[cfg(test)] 60 | mod tests { 61 | use super::{PasswordHash, Scrypt}; 62 | use password_hash::PasswordVerifier; 63 | 64 | /// Test vector from passlib: 65 | /// 66 | #[cfg(feature = "password-hash")] 67 | const EXAMPLE_PASSWORD_HASH: &str = 68 | "$scrypt$ln=16,r=8,p=1$aM15713r3Xsvxbi31lqr1Q$nFNh2CVHVjNldFVKDHDlm4CbdRSCdEBsjjJxD+iCs5E"; 69 | 70 | #[cfg(feature = "password-hash")] 71 | #[test] 72 | fn password_hash_verify_password() { 73 | let password = "password"; 74 | let hash = PasswordHash::new(EXAMPLE_PASSWORD_HASH).unwrap(); 75 | assert_eq!(Scrypt.verify_password(password.as_bytes(), &hash), Ok(())); 76 | } 77 | 78 | #[cfg(feature = "password-hash")] 79 | #[test] 80 | fn password_hash_reject_incorrect_password() { 81 | let hash = PasswordHash::new(EXAMPLE_PASSWORD_HASH).unwrap(); 82 | assert!(Scrypt.verify_password(b"invalid", &hash).is_err()); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /yescrypt/src/util.rs: -------------------------------------------------------------------------------- 1 | //! Utility functions. 2 | 3 | // TODO(tarcieri): find safe replacements for unsafe code if possible 4 | #![allow(unsafe_code)] 5 | 6 | use crate::{Error, Result}; 7 | use core::{ops::BitXorAssign, slice}; 8 | use sha2::Sha256; 9 | 10 | pub(crate) fn xor(dst: &mut [T], src: &[T]) 11 | where 12 | T: BitXorAssign + Copy, 13 | { 14 | assert_eq!(dst.len(), src.len()); 15 | for (dst, src) in core::iter::zip(dst, src) { 16 | *dst ^= *src 17 | } 18 | } 19 | 20 | pub(crate) fn cast_slice(input: &[u32]) -> Result<&[u8]> { 21 | let new_len = input 22 | .len() 23 | .checked_mul(size_of::() / size_of::()) 24 | .ok_or(Error::Internal)?; 25 | 26 | // SAFETY: `new_len` accounts for the size difference between the two types 27 | Ok(unsafe { slice::from_raw_parts(input.as_ptr().cast(), new_len) }) 28 | } 29 | 30 | pub(crate) fn cast_slice_mut(input: &mut [u32]) -> Result<&mut [u8]> { 31 | let new_len = input 32 | .len() 33 | .checked_mul(size_of::() / size_of::()) 34 | .ok_or(Error::Internal)?; 35 | 36 | // SAFETY: `new_len` accounts for the size difference between the two types 37 | Ok(unsafe { slice::from_raw_parts_mut(input.as_mut_ptr().cast(), new_len) }) 38 | } 39 | 40 | pub(crate) fn hmac_sha256(key: &[u8], in_0: &[u8]) -> Result<[u8; 32]> { 41 | use hmac::{KeyInit, Mac}; 42 | 43 | let mut hmac = hmac::Hmac::::new_from_slice(key).map_err(|_| Error::Internal)?; 44 | hmac.update(in_0); 45 | Ok(hmac.finalize().into_bytes().into()) 46 | } 47 | 48 | // TODO(tarcieri): use upstream `[T]::as_chunks_mut` when MSRV is 1.88 49 | #[inline] 50 | #[must_use] 51 | pub(crate) fn slice_as_chunks_mut(slice: &mut [T]) -> (&mut [[T; N]], &mut [T]) { 52 | assert!(N != 0, "chunk size must be non-zero"); 53 | let len_rounded_down = slice.len() / N * N; 54 | // SAFETY: The rounded-down value is always the same or smaller than the 55 | // original length, and thus must be in-bounds of the slice. 56 | let (multiple_of_n, remainder) = unsafe { slice.split_at_mut_unchecked(len_rounded_down) }; 57 | // SAFETY: We already panicked for zero, and ensured by construction 58 | // that the length of the subslice is a multiple of N. 59 | let array_slice = unsafe { slice_as_chunks_unchecked_mut(multiple_of_n) }; 60 | (array_slice, remainder) 61 | } 62 | 63 | #[inline] 64 | #[must_use] 65 | unsafe fn slice_as_chunks_unchecked_mut(slice: &mut [T]) -> &mut [[T; N]] { 66 | assert!( 67 | N != 0 && slice.len() % N == 0, 68 | "slice::as_chunks_unchecked requires `N != 0` and the slice to split exactly into `N`-element chunks" 69 | ); 70 | 71 | let new_len = slice.len() / N; 72 | // SAFETY: We cast a slice of `new_len * N` elements into 73 | // a slice of `new_len` many `N` elements chunks. 74 | unsafe { slice::from_raw_parts_mut(slice.as_mut_ptr().cast(), new_len) } 75 | } 76 | -------------------------------------------------------------------------------- /balloon-hash/src/algorithm.rs: -------------------------------------------------------------------------------- 1 | //! Balloon algorithms (e.g. Balloon, BalloonM). 2 | 3 | use crate::{Error, Result}; 4 | use core::{ 5 | fmt::{self, Display}, 6 | str::FromStr, 7 | }; 8 | 9 | #[cfg(feature = "password-hash")] 10 | use password_hash::phc::Ident; 11 | 12 | /// Balloon primitive type: variants of the algorithm. 13 | #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Default)] 14 | pub enum Algorithm { 15 | /// Standard Balloon hashing algorithm. 16 | Balloon, 17 | 18 | /// M-core variant of the Balloon hashing algorithm. 19 | /// 20 | /// Supports parallelism by computing M instances of the 21 | /// single-core Balloon function and XORing all the outputs. 22 | #[default] 23 | BalloonM, 24 | } 25 | 26 | impl Algorithm { 27 | /// Balloon algorithm identifier 28 | #[cfg(feature = "password-hash")] 29 | pub const BALLOON_IDENT: Ident = Ident::new_unwrap("balloon"); 30 | 31 | /// BalloonM algorithm identifier 32 | #[cfg(feature = "password-hash")] 33 | pub const BALLOON_M_IDENT: Ident = Ident::new_unwrap("balloon-m"); 34 | 35 | /// Parse an [`Algorithm`] from the provided string. 36 | pub fn new(id: impl AsRef) -> Result { 37 | id.as_ref().parse() 38 | } 39 | 40 | /// Get the identifier string for this Balloon [`Algorithm`]. 41 | pub fn as_str(&self) -> &str { 42 | match self { 43 | Algorithm::Balloon => "balloon", 44 | Algorithm::BalloonM => "balloon-m", 45 | } 46 | } 47 | 48 | /// Get the [`Ident`] that corresponds to this Balloon [`Algorithm`]. 49 | #[cfg(feature = "password-hash")] 50 | pub fn ident(&self) -> Ident { 51 | match self { 52 | Algorithm::Balloon => Self::BALLOON_IDENT, 53 | Algorithm::BalloonM => Self::BALLOON_M_IDENT, 54 | } 55 | } 56 | } 57 | 58 | impl AsRef for Algorithm { 59 | fn as_ref(&self) -> &str { 60 | self.as_str() 61 | } 62 | } 63 | 64 | impl Display for Algorithm { 65 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 66 | f.write_str(self.as_str()) 67 | } 68 | } 69 | 70 | impl FromStr for Algorithm { 71 | type Err = Error; 72 | 73 | fn from_str(s: &str) -> Result { 74 | match s { 75 | "balloon" => Ok(Algorithm::Balloon), 76 | "balloon-m" => Ok(Algorithm::BalloonM), 77 | _ => Err(Error::AlgorithmInvalid), 78 | } 79 | } 80 | } 81 | 82 | #[cfg(feature = "password-hash")] 83 | impl From for Ident { 84 | fn from(alg: Algorithm) -> Ident { 85 | alg.ident() 86 | } 87 | } 88 | 89 | #[cfg(feature = "password-hash")] 90 | impl<'a> TryFrom<&'a str> for Algorithm { 91 | type Error = password_hash::Error; 92 | 93 | fn try_from(name: &'a str) -> password_hash::Result { 94 | match name.try_into() { 95 | Ok(Self::BALLOON_IDENT) => Ok(Algorithm::Balloon), 96 | Ok(Self::BALLOON_M_IDENT) => Ok(Algorithm::BalloonM), 97 | _ => Err(password_hash::Error::Algorithm), 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /bcrypt-pbkdf/tests/test_vectors.rs: -------------------------------------------------------------------------------- 1 | extern crate bcrypt_pbkdf; 2 | 3 | use bcrypt_pbkdf::bcrypt_pbkdf_with_memory; 4 | use hex_literal::hex; 5 | 6 | #[test] 7 | fn test_openbsd_vectors() { 8 | struct Test { 9 | password: &'static str, 10 | salt: Vec, 11 | rounds: u32, 12 | out: Vec, 13 | } 14 | 15 | let tests = vec![ 16 | Test { 17 | password: "password", 18 | salt: b"salt".to_vec(), 19 | rounds: 4, 20 | out: hex!("5bbf0cc293587f1c3635555c27796598d47e579071bf427e9d8fbe842aba34d9").to_vec(), 21 | }, 22 | Test { 23 | password: "password", 24 | salt: vec![0], 25 | rounds: 4, 26 | out: hex!("c12b566235eee04c212598970a579a67").to_vec(), 27 | }, 28 | Test { 29 | password: "\x00", 30 | salt: b"salt".to_vec(), 31 | rounds: 4, 32 | out: hex!("6051be18c2f4f82cbf0efee5471b4bb9").to_vec(), 33 | }, 34 | Test { 35 | password: "password\x00", 36 | salt: b"salt\x00".to_vec(), 37 | rounds: 4, 38 | out: hex!("7410e44cf4fa07bfaac8a928b1727fac001375e7bf7384370f48efd121743050").to_vec(), 39 | }, 40 | Test { 41 | password: "pass\x00wor", 42 | salt: b"sa\x00l".to_vec(), 43 | rounds: 4, 44 | out: hex!("c2bffd9db38f6569efef4372f4de83c0").to_vec(), 45 | }, 46 | Test { 47 | password: "pass\x00word", 48 | salt: b"sa\x00lt".to_vec(), 49 | rounds: 4, 50 | out: hex!("4ba4ac3925c0e8d7f0cdb6bb1684a56f").to_vec(), 51 | }, 52 | Test { 53 | password: "password", 54 | salt: b"salt".to_vec(), 55 | rounds: 8, 56 | out: hex!( 57 | "e1367ec5151a33faac4cc1c144cd" 58 | "23fa15d5548493ecc99b9b5d9c0d" 59 | "3b27bec76227ea66088b849b20ab" 60 | "7aa478010246e74bba51723fefa9" 61 | "f9474d6508845e8d") 62 | .to_vec(), 63 | }, 64 | Test { 65 | password: "password", 66 | salt: b"salt".to_vec(), 67 | rounds: 42, 68 | out: hex!("833cf0dcf56db65608e8f0dc0ce882bd").to_vec(), 69 | }, 70 | Test { 71 | password: concat!( 72 | "Lorem ipsum dolor sit amet, consectetur adipisicing elit, ", 73 | "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ", 74 | "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris ", 75 | "nisi ut aliquip ex ea commodo consequat. ", 76 | "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum ", 77 | "dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non ", 78 | "proident, sunt in culpa qui officia deserunt mollit anim id est laborum." 79 | ), 80 | salt: b"salis\x00".to_vec(), 81 | rounds: 8, 82 | out: hex!("10978b07253df57f71a162eb0e8ad30a").to_vec(), 83 | }, 84 | ]; 85 | 86 | for t in tests.iter() { 87 | let mut out = vec![0; t.out.len()]; 88 | let len = t.out.len().div_ceil(32) * 32; 89 | let mut memory = vec![0; len]; 90 | bcrypt_pbkdf_with_memory(t.password, &t.salt[..], t.rounds, &mut out, &mut memory).unwrap(); 91 | assert_eq!(out, t.out); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /password-auth/README.md: -------------------------------------------------------------------------------- 1 | # [RustCrypto]: Password Authentication 2 | 3 | [![crate][crate-image]][crate-link] 4 | [![Docs][docs-image]][docs-link] 5 | [![Build Status][build-image]][build-link] 6 | ![Apache2/MIT licensed][license-image] 7 | ![Rust Version][rustc-image] 8 | [![Project Chat][chat-image]][chat-link] 9 | 10 | Password authentication library with a focus on simplicity and ease-of-use, 11 | with support for [Argon2], [PBKDF2], and [scrypt] password hashing algorithms. 12 | 13 | ## About 14 | 15 | `password-auth` is a high-level password authentication library with a simple 16 | interface which eliminates as much complexity and user choice as possible. 17 | 18 | It wraps pure Rust implementations of multiple password hashing algorithms 19 | maintained by the [RustCrypto] organization, with the goal of providing a 20 | stable interface while allowing the password hashing algorithm implementations 21 | to evolve at a faster pace. 22 | 23 | ## Usage 24 | 25 | The core API consists of two functions: 26 | 27 | - [`generate_hash`]: generates a password hash from the provided password. The 28 | - [`verify_password`]: verifies the provided password against a password hash, 29 | returning an error if the password is incorrect. 30 | 31 | Behind the scenes the crate uses the multi-algorithm support in the 32 | [`password-hash`] crate to support multiple password hashing algorithms 33 | simultaneously. By default it supports Argon2 (using the latest OWASP 34 | recommended parameters 8), but it can also optionally support PBKDF2 and scrypt 35 | by enabling crate features. 36 | 37 | When multiple algorithms are enabled, it will still default to Argon2 for 38 | `generate_hash`, but will be able to verify password hashes from PBKDF2 and 39 | scrypt as well, if you have them in your password database. 40 | 41 | ## License 42 | 43 | Licensed under either of: 44 | 45 | * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 46 | * [MIT license](http://opensource.org/licenses/MIT) 47 | 48 | at your option. 49 | 50 | ### Contribution 51 | 52 | Unless you explicitly state otherwise, any contribution intentionally submitted 53 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 54 | dual licensed as above, without any additional terms or conditions. 55 | 56 | [//]: # (badges) 57 | 58 | [crate-image]: https://img.shields.io/crates/v/password-auth 59 | [crate-link]: https://crates.io/crates/password-auth 60 | [docs-image]: https://docs.rs/password-auth/badge.svg 61 | [docs-link]: https://docs.rs/password-auth/ 62 | [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg 63 | [rustc-image]: https://img.shields.io/badge/rustc-1.81+-blue.svg 64 | [chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg 65 | [chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260046-password-hashes 66 | [build-image]: https://github.com/RustCrypto/password-hashes/workflows/password-auth/badge.svg?branch=master&event=push 67 | [build-link]: https://github.com/RustCrypto/password-hashes/actions?query=workflow%3Apassword-auth 68 | 69 | [//]: # (general links) 70 | 71 | [RustCrypto]: https://github.com/RustCrypto/ 72 | [Argon2]: https://en.wikipedia.org/wiki/Argon2 73 | [PBKDF2]: https://en.wikipedia.org/wiki/PBKDF2 74 | [scrypt]: https://en.wikipedia.org/wiki/Scrypt 75 | [`generate_hash`]: https://docs.rs/password-auth/latest/password_auth/fn.generate_hash.html 76 | [`verify_password`]: https://docs.rs/password-auth/latest/password_auth/fn.verify_password.html 77 | [`password-hash`]: https://docs.rs/password-hash/latest/password_hash/ 78 | -------------------------------------------------------------------------------- /balloon-hash/tests/balloon_m.rs: -------------------------------------------------------------------------------- 1 | use balloon_hash::{Algorithm, Balloon, Params}; 2 | use digest::array::Array; 3 | use hex_literal::hex; 4 | 5 | struct TestVector { 6 | password: &'static [u8], 7 | salt: &'static [u8], 8 | s_cost: u32, 9 | t_cost: u32, 10 | p_cost: u32, 11 | output: [u8; 32], 12 | } 13 | 14 | /// Tested with the following implementations: 15 | /// - 16 | /// - 17 | const TEST_VECTORS: &[TestVector] = &[ 18 | TestVector { 19 | password: b"hunter42", 20 | salt: b"examplesalt", 21 | s_cost: 1024, 22 | t_cost: 3, 23 | p_cost: 4, 24 | output: hex!("1832bd8e5cbeba1cb174a13838095e7e66508e9bf04c40178990adbc8ba9eb6f"), 25 | }, 26 | TestVector { 27 | password: b"", 28 | salt: b"salt", 29 | s_cost: 3, 30 | t_cost: 3, 31 | p_cost: 2, 32 | output: hex!("f8767fe04059cef67b4427cda99bf8bcdd983959dbd399a5e63ea04523716c23"), 33 | }, 34 | TestVector { 35 | password: b"password", 36 | salt: b"", 37 | s_cost: 3, 38 | t_cost: 3, 39 | p_cost: 3, 40 | output: hex!("bcad257eff3d1090b50276514857e60db5d0ec484129013ef3c88f7d36e438d6"), 41 | }, 42 | TestVector { 43 | password: b"password", 44 | salt: b"", 45 | s_cost: 3, 46 | t_cost: 3, 47 | p_cost: 1, 48 | output: hex!("498344ee9d31baf82cc93ebb3874fe0b76e164302c1cefa1b63a90a69afb9b4d"), 49 | }, 50 | TestVector { 51 | password: b"\0", 52 | salt: b"\0", 53 | s_cost: 3, 54 | t_cost: 3, 55 | p_cost: 4, 56 | output: hex!("8a665611e40710ba1fd78c181549c750f17c12e423c11930ce997f04c7153e0c"), 57 | }, 58 | TestVector { 59 | password: b"\0", 60 | salt: b"\0", 61 | s_cost: 3, 62 | t_cost: 3, 63 | p_cost: 1, 64 | output: hex!("d9e33c683451b21fb3720afbd78bf12518c1d4401fa39f054b052a145c968bb1"), 65 | }, 66 | TestVector { 67 | password: b"password", 68 | salt: b"salt", 69 | s_cost: 1, 70 | t_cost: 1, 71 | p_cost: 16, 72 | output: hex!("a67b383bb88a282aef595d98697f90820adf64582a4b3627c76b7da3d8bae915"), 73 | }, 74 | TestVector { 75 | password: b"password", 76 | salt: b"salt", 77 | s_cost: 1, 78 | t_cost: 1, 79 | p_cost: 1, 80 | output: hex!("97a11df9382a788c781929831d409d3599e0b67ab452ef834718114efdcd1c6d"), 81 | }, 82 | ]; 83 | 84 | #[test] 85 | fn test_vectors() { 86 | for test_vector in TEST_VECTORS { 87 | let balloon = Balloon::::new( 88 | Algorithm::BalloonM, 89 | Params::new(test_vector.s_cost, test_vector.t_cost, test_vector.p_cost).unwrap(), 90 | None, 91 | ); 92 | 93 | #[cfg(not(feature = "parallel"))] 94 | let mut memory = vec![Array::default(); balloon.params.s_cost.get() as usize]; 95 | #[cfg(feature = "parallel")] 96 | let mut memory = vec![ 97 | Array::default(); 98 | (balloon.params.s_cost.get() * balloon.params.p_cost.get()) as usize 99 | ]; 100 | 101 | assert_eq!( 102 | balloon 103 | .hash_with_memory(test_vector.password, test_vector.salt, &mut memory) 104 | .unwrap() 105 | .as_slice(), 106 | test_vector.output, 107 | ); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /balloon-hash/tests/balloon.rs: -------------------------------------------------------------------------------- 1 | use balloon_hash::{Algorithm, Balloon, Params}; 2 | use digest::array::Array; 3 | use hex_literal::hex; 4 | 5 | struct TestVector { 6 | password: &'static [u8], 7 | salt: &'static [u8], 8 | s_cost: u32, 9 | t_cost: u32, 10 | output: [u8; 32], 11 | } 12 | 13 | /// Tested with the following implementations: 14 | /// - 15 | /// - 16 | const TEST_VECTORS: &[TestVector] = &[ 17 | TestVector { 18 | password: b"hunter42", 19 | salt: b"examplesalt", 20 | s_cost: 1024, 21 | t_cost: 3, 22 | output: hex!("716043dff777b44aa7b88dcbab12c078abecfac9d289c5b5195967aa63440dfb"), 23 | }, 24 | TestVector { 25 | password: b"", 26 | salt: b"salt", 27 | s_cost: 3, 28 | t_cost: 3, 29 | output: hex!("5f02f8206f9cd212485c6bdf85527b698956701ad0852106f94b94ee94577378"), 30 | }, 31 | TestVector { 32 | password: b"password", 33 | salt: b"", 34 | s_cost: 3, 35 | t_cost: 3, 36 | output: hex!("20aa99d7fe3f4df4bd98c655c5480ec98b143107a331fd491deda885c4d6a6cc"), 37 | }, 38 | TestVector { 39 | password: b"\0", 40 | salt: b"\0", 41 | s_cost: 3, 42 | t_cost: 3, 43 | output: hex!("4fc7e302ffa29ae0eac31166cee7a552d1d71135f4e0da66486fb68a749b73a4"), 44 | }, 45 | TestVector { 46 | password: b"password", 47 | salt: b"salt", 48 | s_cost: 1, 49 | t_cost: 1, 50 | output: hex!("eefda4a8a75b461fa389c1dcfaf3e9dfacbc26f81f22e6f280d15cc18c417545"), 51 | }, 52 | ]; 53 | 54 | #[test] 55 | fn test_vectors() { 56 | for test_vector in TEST_VECTORS { 57 | let balloon = Balloon::::new( 58 | Algorithm::Balloon, 59 | Params::new(test_vector.s_cost, test_vector.t_cost, 1).unwrap(), 60 | None, 61 | ); 62 | 63 | let mut memory = vec![Array::default(); balloon.params.s_cost.get() as usize]; 64 | 65 | assert_eq!( 66 | balloon 67 | .hash_with_memory(test_vector.password, test_vector.salt, &mut memory) 68 | .unwrap() 69 | .as_slice(), 70 | test_vector.output, 71 | ); 72 | } 73 | } 74 | 75 | #[cfg(all(feature = "password-hash", feature = "alloc"))] 76 | #[test] 77 | fn password_hash_retains_configured_params() { 78 | use balloon_hash::PasswordHasher; 79 | use sha2::Sha256; 80 | 81 | /// Example password only: don't use this as a real password!!! 82 | const EXAMPLE_PASSWORD: &[u8] = b"hunter42"; 83 | 84 | /// Example salt value. Don't use a static salt value!!! 85 | const EXAMPLE_SALT: &[u8] = b"example-salt"; 86 | 87 | // Non-default but valid parameters 88 | let t_cost = 4; 89 | let s_cost = 2048; 90 | let p_cost = 2; 91 | 92 | let params = Params::new(s_cost, t_cost, p_cost).unwrap(); 93 | let hasher = Balloon::::new(Algorithm::default(), params, None); 94 | let hash = hasher 95 | .hash_password_with_salt(EXAMPLE_PASSWORD, EXAMPLE_SALT) 96 | .unwrap(); 97 | 98 | assert_eq!(hash.version.unwrap(), 1); 99 | 100 | for &(param, value) in &[("t", t_cost), ("s", s_cost), ("p", p_cost)] { 101 | assert_eq!( 102 | hash.params 103 | .get(param) 104 | .and_then(|p| p.decimal().ok()) 105 | .unwrap(), 106 | value 107 | ); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /bcrypt-pbkdf/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## 0.10.0 (2023-03-04) 9 | ### Added 10 | - Support for `alloc`-free usage ([#372]) 11 | 12 | ### Changed 13 | - Bump `pbkdf2` dependency to v0.12; MSRV 1.60 ([#393]) 14 | 15 | [#372]: https://github.com/RustCrypto/password-hashes/pull/372 16 | [#393]: https://github.com/RustCrypto/password-hashes/pull/393 17 | 18 | ## 0.9.0 (2022-03-18) 19 | ### Changed 20 | - 2021 edition upgrade ([#284]) 21 | - Bump `pbkdf2` dependency to v0.11; MSRV 1.57 ([#291]) 22 | 23 | [#284]: https://github.com/RustCrypto/password-hashes/pull/284 24 | [#291]: https://github.com/RustCrypto/password-hashes/pull/291 25 | 26 | ## 0.8.1 (2022-02-20) 27 | ### Changed 28 | - Change `passphrase` to be `impl AsRef<[u8]>` allowing non-UTF8 passphrases ([#277]) 29 | 30 | [#277]: https://github.com/RustCrypto/password-hashes/pull/277 31 | 32 | ## 0.8.0 (2022-02-17) 33 | ### Changed 34 | - Bump `blowfish` dependency to v0.9, edition to 2021, and MSRV to 1.56 ([#273]) 35 | 36 | [#273]: https://github.com/RustCrypto/password-hashes/pull/273 37 | 38 | ## 0.7.2 (2021-11-25) 39 | ### Changed 40 | - Bump `sha2` and `pbkdf2` dependencies to v0.10 ([#254]) 41 | 42 | [#254]: https://github.com/RustCrypto/password-hashes/pull/254 43 | 44 | ## 0.7.1 (2021-08-27) 45 | ### Changed 46 | - Bump `pbkdf2` dependency to v0.9 ([#223]) 47 | 48 | [#223]: https://github.com/RustCrypto/password-hashes/pull/223 49 | 50 | ## 0.7.0 (2021-08-27) [YANKED] 51 | ### Changed 52 | - Relax `zeroize` requirements ([#195]) 53 | - Use `resolver = "2"`; MSRV 1.51+ ([#220]) 54 | 55 | [#195]: https://github.com/RustCrypto/password-hashes/pull/195 56 | [#220]: https://github.com/RustCrypto/password-hashes/pull/220 57 | 58 | ## 0.6.2 (2021-07-20) 59 | ### Changed 60 | - Pin `zeroize` dependency to v1.3 ([#190]) 61 | 62 | [#190]: https://github.com/RustCrypto/password-hashes/pull/190 63 | 64 | ## 0.6.1 (2021-05-04) 65 | ### Changed 66 | - Bump `blowfish` dependency to v0.8 ([#171]) 67 | 68 | [#171]: https://github.com/RustCrypto/password-hashing/pull/171 69 | 70 | ## 0.6.0 (2021-04-29) [YANKED] 71 | ### Changed 72 | - Bump `crypto-mac` dependency to v0.11 ([#165]) 73 | - Bump `pbkdf2` to v0.8 ([#167]) 74 | 75 | [#165]: https://github.com/RustCrypto/password-hashing/pull/165 76 | [#167]: https://github.com/RustCrypto/password-hashing/pull/167 77 | 78 | ## 0.5.0 (2021-01-29) 79 | ### Changed 80 | - Bump `pbkdf2` dependency to v0.7 ([#102]) 81 | 82 | [#102]: https://github.com/RustCrypto/password-hashing/pull/102 83 | 84 | ## 0.4.0 (2020-10-18) 85 | ### Changed 86 | - Bump `crypto-mac` dependency to v0.10 ([#58]) 87 | - Bump `pbkdf2` dependency to v0.10 ([#61]) 88 | 89 | [#61]: https://github.com/RustCrypto/password-hashing/pull/61 90 | [#58]: https://github.com/RustCrypto/password-hashing/pull/58 91 | 92 | ## 0.3.0 (2020-08-18) 93 | ### Changed 94 | - Bump `crypto-mac` dependency to v0.9, `blowfish` to v0.6, and `pbkdf2` to v0.5 ([#46]) 95 | 96 | [#46]: https://github.com/RustCrypto/password-hashing/pull/46 97 | 98 | ## 0.2.1 (2020-06-03) 99 | ### Added 100 | - `no_std` support ([#41]) 101 | 102 | [#41]: https://github.com/RustCrypto/password-hashing/pull/41 103 | 104 | ## 0.2.0 (2020-05-13) 105 | ### Changed 106 | - Update dependencies to `sha2 v0.9`, `pbkdf2 v0.4`, `blowfish v0.5`, 107 | and `crypto-mac v0.8` 108 | 109 | ## 0.1.0 (2020-01-03) 110 | - Initial release 111 | -------------------------------------------------------------------------------- /yescrypt/tests/mcf.rs: -------------------------------------------------------------------------------- 1 | //! Tests for encoding password hash strings in Modular Crypt Format (MCF). 2 | 3 | #![cfg(feature = "password-hash")] 4 | #![allow(non_snake_case)] 5 | 6 | use yescrypt::{ 7 | CustomizedPasswordHasher, Mode, Params, PasswordHashRef, PasswordVerifier, Yescrypt, 8 | password_hash::Error, 9 | }; 10 | 11 | const YESCRYPT_P: u32 = 11; 12 | 13 | // Don't use this as a real password!!! 14 | const EXAMPLE_PASSWD: &[u8] = b"pleaseletmein"; 15 | const EXAMPLE_SALT: &[u8] = b"WZaPV7LSUEKMo34."; 16 | 17 | /// Adapted from `TESTS-OK` in the yescrypt reference C implementation 18 | /// https://github.com/openwall/yescrypt/blob/caa931d/TESTS-OK#L31-L66 19 | const EXAMPLE_HASHES: &[&str] = &[ 20 | "$y$jD5.7$LdJMENpBABJJ3hIHjB1Bi.$HboGM6qPrsK.StKYGt6KErmUYtioHreJd98oIugoNB6", 21 | "$y$jC4$LdJMENpBABJJ3hIHjB1B$jVg4HoqqpbmQv/NCpin.QCMagJ8o4QX7lXdzvVV0xFC", // TODO 22 | "$y$/B3.6$LdJMENpBABJJ3hIHjB1$h8sE4hJo.BsdlfJr0.d8bNJNPZymH7Y3kLj4aY1Rfc8", 23 | "$y$/A2$LdJMENpBABJJ3hIHj/$5IEld1eWdmh5lylrqHLF5dvA3ISpimEM9J1Dd05n/.3", 24 | "$y$j91.5$LdJMENpBABJJ3hIH$ebKnn23URD5vyLgF9cP2EvVosrUXf7UErGRV0KmC6e6", 25 | "$y$j80$LdJMENpBABJJ3h2$ysXVVJwuaVlI1BWoEKt/Bz3WNDDmdOWz/8KTQaHL1cC", 26 | "$y$/7/.4$LdJMENpBABJJ3/$lXHleh7bIZMGNtJVxGVrsIWkEIXfBedlfPui/PITflC", 27 | "$y$/6.$LdJMENpBABJJ$zQITmYSih5.CTY47x0IuE4wl.b3HzYGKKCSggakaQ22", 28 | "$y$j5..3$LdJMENpBAB3$xi27PTUNd8NsChHeLOz85JFnUOyibRHkWzprowRlR5/", 29 | "$y$j4/$LdJMENpBA/$tHlkpTQ8V/eEnTVau1uW36T97LIXlfPrEzdeV5SE5K7", 30 | "$y$/3..2$LdJMENpB$tNczXFuNUd3HMqypStCRsEaL4e4KF7ZYLBe8Hbeg0B7", 31 | "$y$/2/$LdJMEN3$RRorHhfsw1/P/WR6Aurg4U72e9Q7qt9vFPURdyfiqK8", 32 | "$y$j2..1$LdJME/$iLEt6kuTwHch6XdCxtTHfsQzYwWFmpUwgl6Ax8RH4d1", 33 | "$y$j0/$LdJM$k7BXzSDuoGHW56SY3HxROCiA0gWRscZe2aA0q5oHPM0", 34 | "$y$//..0$Ld3$6BJXezMFxaMiO5wsuoEmztvtCs/79085dZO56ADlV5B", 35 | "$y$///$L/$Rrrkp6OVljrIk0kcwkCDhAiHJiSthh3cKeIGHUW7Z0C", 36 | "$y$j1../$LdJMENpBABJJ3hIHjB1Bi.$L8OQFc8mxJPd7CpUFgkS7KqJM2I9jGXu3BdqX2D.647", 37 | "$y$j//$LdJMENpBABJJ3hIHjB1B$U8a2MaK.yesqWySK8Owk6PWeWmp/XuagMbpP45q1/q1", 38 | ]; 39 | 40 | /// `yescrypt()` tests 41 | #[test] 42 | fn compute_reference_strings() { 43 | for (i, &expected_hash) in EXAMPLE_HASHES.iter().enumerate() { 44 | let i = i as u32; 45 | 46 | // Test case logic adapted from the yescrypt C reference implementation (tests.c) 47 | let mut N_log2 = if i < 14 { 16 - i } else { 2 }; 48 | let r = if i < 8 { 8 - i } else { 1 + (i & 1) }; 49 | let mut p = if i & 1 == 1 { 1 } else { YESCRYPT_P }; 50 | let mut flags = Mode::default(); 51 | 52 | if p.saturating_sub(i / 2) > 1 { 53 | p -= i / 2; 54 | } 55 | 56 | if i & 2 != 0 { 57 | flags = Mode::Worm; 58 | } else { 59 | while (1 << N_log2) / p <= 3 { 60 | N_log2 += 1; 61 | } 62 | } 63 | 64 | let params = Params::new(flags, 1 << N_log2, r, p).unwrap(); 65 | let salt = &EXAMPLE_SALT[..(16 - (i as usize & 15))]; 66 | 67 | let actual_hash = Yescrypt 68 | .hash_password_with_params(EXAMPLE_PASSWD, salt, params) 69 | .unwrap(); 70 | 71 | assert_eq!(expected_hash, actual_hash.as_str()); 72 | } 73 | } 74 | 75 | /// `yescrypt_verify()` tests 76 | #[test] 77 | fn verify_reference_strings() { 78 | for &hash in EXAMPLE_HASHES { 79 | let hash = PasswordHashRef::new(hash).unwrap(); 80 | assert_eq!(Yescrypt.verify_password(EXAMPLE_PASSWD, hash), Ok(())); 81 | 82 | assert_eq!( 83 | Yescrypt.verify_password(b"bogus", hash), 84 | Err(Error::PasswordInvalid) 85 | ); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /.github/workflows/argon2.yml: -------------------------------------------------------------------------------- 1 | name: argon2 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - ".github/workflows/argon2.yml" 7 | - "argon2/**" 8 | - "Cargo.*" 9 | push: 10 | branches: master 11 | 12 | defaults: 13 | run: 14 | working-directory: argon2 15 | 16 | env: 17 | CARGO_INCREMENTAL: 0 18 | RUSTFLAGS: "-Dwarnings" 19 | 20 | jobs: 21 | build: 22 | runs-on: ubuntu-latest 23 | strategy: 24 | matrix: 25 | rust: 26 | - 1.85.0 # MSRV 27 | - stable 28 | target: 29 | - thumbv7em-none-eabi 30 | - wasm32-unknown-unknown 31 | steps: 32 | - uses: actions/checkout@v6 33 | - uses: RustCrypto/actions/cargo-cache@master 34 | - uses: dtolnay/rust-toolchain@master 35 | with: 36 | toolchain: ${{ matrix.rust }} 37 | targets: ${{ matrix.target }} 38 | - run: cargo build --target ${{ matrix.target }} --no-default-features 39 | - run: cargo build --target ${{ matrix.target }} --no-default-features --features password-hash 40 | - run: cargo build --target ${{ matrix.target }} --no-default-features --features password-hash 41 | - run: cargo build --target ${{ matrix.target }} --no-default-features --features zeroize 42 | 43 | minimal-versions: 44 | if: false # disabled while using pre-releases 45 | uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master 46 | with: 47 | working-directory: ${{ github.workflow }} 48 | 49 | test: 50 | runs-on: ubuntu-latest 51 | strategy: 52 | matrix: 53 | include: 54 | # 32-bit Linux 55 | - target: i686-unknown-linux-gnu 56 | rust: 1.85.0 # MSRV 57 | deps: sudo apt update && sudo apt install gcc-multilib 58 | - target: i686-unknown-linux-gnu 59 | rust: stable 60 | deps: sudo apt update && sudo apt install gcc-multilib 61 | 62 | # 64-bit Linux 63 | - target: x86_64-unknown-linux-gnu 64 | rust: 1.85.0 # MSRV 65 | - target: x86_64-unknown-linux-gnu 66 | rust: stable 67 | steps: 68 | - uses: actions/checkout@v6 69 | - uses: RustCrypto/actions/cargo-cache@master 70 | - uses: dtolnay/rust-toolchain@master 71 | with: 72 | toolchain: ${{ matrix.rust }} 73 | targets: ${{ matrix.target }} 74 | - run: ${{ matrix.deps }} 75 | - run: cargo test --no-default-features 76 | - run: cargo test --no-default-features --features password-hash 77 | - run: cargo test 78 | - run: cargo test --all-features 79 | 80 | careful: 81 | runs-on: ubuntu-latest 82 | steps: 83 | - uses: actions/checkout@v6 84 | - uses: dtolnay/rust-toolchain@nightly 85 | - run: cargo install cargo-careful 86 | - run: cargo careful test --all-features 87 | 88 | cross: 89 | # Temporarily disabled because of the compiler bug: 90 | # https://github.com/rust-lang/rust/issues/145693 91 | if: false 92 | strategy: 93 | matrix: 94 | include: 95 | - target: powerpc-unknown-linux-gnu 96 | rust: 1.85.0 # MSRV 97 | - target: powerpc-unknown-linux-gnu 98 | rust: stable 99 | runs-on: ubuntu-latest 100 | steps: 101 | - uses: actions/checkout@v6 102 | - run: ${{ matrix.deps }} 103 | - uses: dtolnay/rust-toolchain@master 104 | with: 105 | toolchain: ${{ matrix.rust }} 106 | targets: ${{ matrix.target }} 107 | - uses: RustCrypto/actions/cross-install@master 108 | - run: cross test --release --target ${{ matrix.target }} --all-features 109 | -------------------------------------------------------------------------------- /pbkdf2/tests/mod.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "hmac")] 2 | use belt_hash::BeltHash; 3 | use hex_literal::hex; 4 | use sha1::Sha1; 5 | use streebog::Streebog512; 6 | 7 | macro_rules! test { 8 | ( 9 | $hash:ty; 10 | $($password:expr, $salt:expr, $rounds:expr, $($expected_hash:literal)*;)* 11 | ) => { 12 | $({ 13 | const EXPECTED_HASH: &[u8] = &hex_literal::hex!($($expected_hash)*); 14 | const N: usize = EXPECTED_HASH.len(); 15 | 16 | let hash = pbkdf2::pbkdf2_hmac_array::<$hash, N>($password, $salt, $rounds); 17 | assert_eq!(hash[..], EXPECTED_HASH[..]); 18 | })* 19 | }; 20 | } 21 | 22 | /// Test vectors from RFC 6070: 23 | /// https://www.rfc-editor.org/rfc/rfc6070 24 | #[test] 25 | fn pbkdf2_rfc6070() { 26 | test!( 27 | Sha1; 28 | b"password", b"salt", 1, "0c60c80f961f0e71f3a9b524af6012062fe037a6"; 29 | b"password", b"salt", 2, "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"; 30 | b"password", b"salt", 4096, "4b007901b765489abead49d926f721d065a429c1"; 31 | // this test passes, but takes a long time to execute 32 | // b"password", b"salt", 16777216, "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984"; 33 | b"passwordPASSWORDpassword", b"saltSALTsaltSALTsaltSALTsaltSALTsalt", 4096, 34 | "3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038"; 35 | b"pass\0word", b"sa\0lt", 4096, "56fa6aa75548099dcc37d7f03425e0c3"; 36 | ); 37 | } 38 | 39 | /// Test vectors from R 50.1.111-2016: 40 | /// https://tc26.ru/standard/rs/Р%2050.1.111-2016.pdf 41 | #[test] 42 | fn pbkdf2_streebog() { 43 | test!( 44 | Streebog512; 45 | b"password", b"salt", 1, 46 | "64770af7f748c3b1c9ac831dbcfd85c2" 47 | "6111b30a8a657ddc3056b80ca73e040d" 48 | "2854fd36811f6d825cc4ab66ec0a68a4" 49 | "90a9e5cf5156b3a2b7eecddbf9a16b47"; 50 | b"password", b"salt", 2, 51 | "5a585bafdfbb6e8830d6d68aa3b43ac0" 52 | "0d2e4aebce01c9b31c2caed56f0236d4" 53 | "d34b2b8fbd2c4e89d54d46f50e47d45b" 54 | "bac301571743119e8d3c42ba66d348de"; 55 | b"password", b"salt", 4096, 56 | "e52deb9a2d2aaff4e2ac9d47a41f34c2" 57 | "0376591c67807f0477e32549dc341bc7" 58 | "867c09841b6d58e29d0347c996301d55" 59 | "df0d34e47cf68f4e3c2cdaf1d9ab86c3"; 60 | // this test passes, but takes a long time to execute 61 | // b"password", b"salt", 16777216, 62 | // "49e4843bba76e300afe24c4d23dc7392" 63 | // "def12f2c0e244172367cd70a8982ac36" 64 | // "1adb601c7e2a314e8cb7b1e9df840e36" 65 | // "ab5615be5d742b6cf203fb55fdc48071"; 66 | b"passwordPASSWORDpassword", b"saltSALTsaltSALTsaltSALTsaltSALTsalt", 4096, 67 | "b2d8f1245fc4d29274802057e4b54e0a" 68 | "0753aa22fc53760b301cf008679e58fe" 69 | "4bee9addcae99ba2b0b20f431a9c5e50" 70 | "f395c89387d0945aedeca6eb4015dfc2" 71 | "bd2421ee9bb71183ba882ceebfef259f" 72 | "33f9e27dc6178cb89dc37428cf9cc52a" 73 | "2baa2d3a"; 74 | b"pass\0word", b"sa\0lt", 4096, 75 | "50df062885b69801a3c10248eb0a27ab" 76 | "6e522ffeb20c991c660f001475d73a4e" 77 | "167f782c18e97e92976d9c1d970831ea" 78 | "78ccb879f67068cdac1910740844e830"; 79 | ); 80 | } 81 | 82 | /// Test vector from STB 4.101.45-2013 (page 33): 83 | /// https://apmi.bsu.by/assets/files/std/bign-spec294.pdf 84 | #[test] 85 | fn pbkdf2_belt() { 86 | test!( 87 | BeltHash; 88 | &hex!("42313934 42414338 30413038 46353342"), 89 | &hex!("BE329713 43FC9A48"), 90 | 10_000, 91 | "3D331BBB B1FBBB40 E4BF22F6 CB9A689E F13A77DC 09ECF932 91BFE424 39A72E7D"; 92 | ); 93 | } 94 | -------------------------------------------------------------------------------- /argon2/src/algorithm.rs: -------------------------------------------------------------------------------- 1 | //! Argon2 algorithms (e.g. Argon2d, Argon2i, Argon2id). 2 | 3 | use crate::{Error, Result}; 4 | use core::{ 5 | fmt::{self, Display}, 6 | str::FromStr, 7 | }; 8 | 9 | #[cfg(feature = "password-hash")] 10 | use password_hash::phc::Ident; 11 | 12 | /// Argon2d algorithm identifier 13 | #[cfg(feature = "password-hash")] 14 | pub const ARGON2D_IDENT: Ident = Ident::new_unwrap("argon2d"); 15 | 16 | /// Argon2i algorithm identifier 17 | #[cfg(feature = "password-hash")] 18 | pub const ARGON2I_IDENT: Ident = Ident::new_unwrap("argon2i"); 19 | 20 | /// Argon2id algorithm identifier 21 | #[cfg(feature = "password-hash")] 22 | pub const ARGON2ID_IDENT: Ident = Ident::new_unwrap("argon2id"); 23 | 24 | /// Argon2 primitive type: variants of the algorithm. 25 | #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Default, Ord)] 26 | pub enum Algorithm { 27 | /// Optimizes against GPU cracking attacks but vulnerable to side-channels. 28 | /// 29 | /// Accesses the memory array in a password dependent order, reducing the 30 | /// possibility of time–memory tradeoff (TMTO) attacks. 31 | Argon2d = 0, 32 | 33 | /// Optimized to resist side-channel attacks. 34 | /// 35 | /// Accesses the memory array in a password independent order, increasing the 36 | /// possibility of time-memory tradeoff (TMTO) attacks. 37 | Argon2i = 1, 38 | 39 | /// Hybrid that mixes Argon2i and Argon2d passes (*default*). 40 | /// 41 | /// Uses the Argon2i approach for the first half pass over memory and 42 | /// Argon2d approach for subsequent passes. This effectively places it in 43 | /// the "middle" between the other two: it doesn't provide as good 44 | /// TMTO/GPU cracking resistance as Argon2d, nor as good of side-channel 45 | /// resistance as Argon2i, but overall provides the most well-rounded 46 | /// approach to both classes of attacks. 47 | #[default] 48 | Argon2id = 2, 49 | } 50 | 51 | impl Algorithm { 52 | /// Parse an [`Algorithm`] from the provided string. 53 | pub fn new(id: impl AsRef) -> Result { 54 | id.as_ref().parse() 55 | } 56 | 57 | /// Get the identifier string for this PBKDF2 [`Algorithm`]. 58 | pub const fn as_str(&self) -> &'static str { 59 | match self { 60 | Algorithm::Argon2d => "argon2d", 61 | Algorithm::Argon2i => "argon2i", 62 | Algorithm::Argon2id => "argon2id", 63 | } 64 | } 65 | 66 | /// Get the [`Ident`] that corresponds to this Argon2 [`Algorithm`]. 67 | #[cfg(feature = "password-hash")] 68 | pub const fn ident(&self) -> Ident { 69 | match self { 70 | Algorithm::Argon2d => ARGON2D_IDENT, 71 | Algorithm::Argon2i => ARGON2I_IDENT, 72 | Algorithm::Argon2id => ARGON2ID_IDENT, 73 | } 74 | } 75 | 76 | /// Serialize primitive type as little endian bytes 77 | pub(crate) const fn to_le_bytes(self) -> [u8; 4] { 78 | (self as u32).to_le_bytes() 79 | } 80 | } 81 | 82 | impl AsRef for Algorithm { 83 | fn as_ref(&self) -> &str { 84 | self.as_str() 85 | } 86 | } 87 | 88 | impl Display for Algorithm { 89 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 90 | f.write_str(self.as_str()) 91 | } 92 | } 93 | 94 | impl FromStr for Algorithm { 95 | type Err = Error; 96 | 97 | fn from_str(name: &str) -> Result { 98 | match name { 99 | "argon2d" => Ok(Algorithm::Argon2d), 100 | "argon2i" => Ok(Algorithm::Argon2i), 101 | "argon2id" => Ok(Algorithm::Argon2id), 102 | _ => Err(Error::AlgorithmInvalid), 103 | } 104 | } 105 | } 106 | 107 | #[cfg(feature = "password-hash")] 108 | impl From for Ident { 109 | fn from(alg: Algorithm) -> Ident { 110 | alg.ident() 111 | } 112 | } 113 | 114 | #[cfg(feature = "password-hash")] 115 | impl TryFrom<&str> for Algorithm { 116 | type Error = password_hash::Error; 117 | 118 | fn try_from(name: &str) -> password_hash::Result { 119 | name.parse().map_err(|_| password_hash::Error::Algorithm) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /argon2/src/error.rs: -------------------------------------------------------------------------------- 1 | //! Error type 2 | 3 | use core::fmt; 4 | 5 | /// Result with argon2's [`Error`] type. 6 | pub type Result = core::result::Result; 7 | 8 | /// Error type. 9 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 10 | pub enum Error { 11 | /// Associated data is too long. 12 | AdTooLong, 13 | 14 | /// Algorithm identifier invalid. 15 | AlgorithmInvalid, 16 | 17 | /// "B64" encoding is invalid. 18 | B64Encoding(base64ct::Error), 19 | 20 | /// Key ID is too long. 21 | KeyIdTooLong, 22 | 23 | /// Memory cost is too small. 24 | MemoryTooLittle, 25 | 26 | /// Memory cost is too large. 27 | MemoryTooMuch, 28 | 29 | /// Output is too short. 30 | OutputTooShort, 31 | 32 | /// Output is too long. 33 | OutputTooLong, 34 | 35 | /// Password is too long. 36 | PwdTooLong, 37 | 38 | /// Salt is too short. 39 | SaltTooShort, 40 | 41 | /// Salt is too long. 42 | SaltTooLong, 43 | 44 | /// Secret is too long. 45 | SecretTooLong, 46 | 47 | /// Not enough threads. 48 | ThreadsTooFew, 49 | 50 | /// Too many threads. 51 | ThreadsTooMany, 52 | 53 | /// Time cost is too small. 54 | TimeTooSmall, 55 | 56 | /// Invalid version. 57 | VersionInvalid, 58 | 59 | /// Out of memory (heap allocation failure). 60 | OutOfMemory, 61 | } 62 | 63 | impl fmt::Display for Error { 64 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 65 | f.write_str(match self { 66 | Error::AdTooLong => "associated data is too long", 67 | Error::AlgorithmInvalid => "algorithm identifier invalid", 68 | Error::B64Encoding(inner) => return write!(f, "B64 encoding invalid: {inner}"), 69 | Error::KeyIdTooLong => "key ID is too long", 70 | Error::MemoryTooLittle => "memory cost is too small", 71 | Error::MemoryTooMuch => "memory cost is too large", 72 | Error::OutputTooShort => "output is too short", 73 | Error::OutputTooLong => "output is too long", 74 | Error::PwdTooLong => "password is too long", 75 | Error::SaltTooShort => "salt is too short", 76 | Error::SaltTooLong => "salt is too long", 77 | Error::SecretTooLong => "secret is too long", 78 | Error::ThreadsTooFew => "not enough threads", 79 | Error::ThreadsTooMany => "too many threads", 80 | Error::TimeTooSmall => "time cost is too small", 81 | Error::VersionInvalid => "invalid version", 82 | Error::OutOfMemory => "out of memory", 83 | }) 84 | } 85 | } 86 | 87 | impl From for Error { 88 | fn from(err: base64ct::Error) -> Error { 89 | Error::B64Encoding(err) 90 | } 91 | } 92 | 93 | #[cfg(feature = "password-hash")] 94 | impl From for password_hash::Error { 95 | fn from(err: Error) -> password_hash::Error { 96 | match err { 97 | Error::AdTooLong => password_hash::Error::ParamInvalid { name: "data" }, 98 | Error::AlgorithmInvalid => password_hash::Error::Algorithm, 99 | Error::B64Encoding(_) => password_hash::Error::EncodingInvalid, 100 | Error::KeyIdTooLong => password_hash::Error::ParamInvalid { name: "keyid" }, 101 | Error::MemoryTooLittle | Error::MemoryTooMuch => { 102 | password_hash::Error::ParamInvalid { name: "m" } 103 | } 104 | Error::OutOfMemory => password_hash::Error::OutOfMemory, 105 | Error::OutputTooShort | Error::OutputTooLong => password_hash::Error::OutputSize, 106 | Error::PwdTooLong => password_hash::Error::PasswordInvalid, 107 | Error::SaltTooShort | Error::SaltTooLong => password_hash::Error::SaltInvalid, 108 | Error::SecretTooLong => password_hash::Error::ParamsInvalid, 109 | Error::ThreadsTooFew | Error::ThreadsTooMany => { 110 | password_hash::Error::ParamInvalid { name: "p" } 111 | } 112 | Error::TimeTooSmall => password_hash::Error::ParamInvalid { name: "t" }, 113 | Error::VersionInvalid => password_hash::Error::Version, 114 | } 115 | } 116 | } 117 | 118 | impl core::error::Error for Error { 119 | fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { 120 | match self { 121 | Self::B64Encoding(err) => Some(err), 122 | _ => None, 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /balloon-hash/src/params.rs: -------------------------------------------------------------------------------- 1 | //! Balloon password hash parameters. 2 | 3 | use crate::{Error, Result}; 4 | use core::num::NonZeroU32; 5 | 6 | #[cfg(feature = "password-hash")] 7 | use { 8 | core::{ 9 | fmt::{self, Display}, 10 | str::FromStr, 11 | }, 12 | password_hash::phc::{ParamsString, PasswordHash}, 13 | }; 14 | 15 | /// Balloon password hash parameters. 16 | /// 17 | /// These are parameters which can be encoded into a PHC hash string. 18 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 19 | pub struct Params { 20 | /// Space cost, expressed in number of blocks. 21 | pub s_cost: NonZeroU32, 22 | /// Time cost, expressed in number of rounds. 23 | pub t_cost: NonZeroU32, 24 | /// Degree of parallelism, expressed in number of threads. 25 | /// Only allowed to be higher than 1 when used in combination 26 | /// with [`Algorithm::BalloonM`](crate::Algorithm::BalloonM). 27 | pub p_cost: NonZeroU32, 28 | } 29 | 30 | impl Params { 31 | /// Default memory cost. 32 | pub const DEFAULT_S_COST: u32 = 1024; 33 | 34 | /// Default number of iterations (i.e. "time"). 35 | pub const DEFAULT_T_COST: u32 = 3; 36 | 37 | /// Default degree of parallelism. 38 | pub const DEFAULT_P_COST: u32 = 1; 39 | 40 | /// Create new parameters. 41 | pub fn new(s_cost: u32, t_cost: u32, p_cost: u32) -> Result { 42 | Ok(Self { 43 | s_cost: NonZeroU32::new(s_cost).ok_or(Error::MemoryTooLittle)?, 44 | t_cost: NonZeroU32::new(t_cost).ok_or(Error::TimeTooSmall)?, 45 | p_cost: NonZeroU32::new(p_cost).ok_or(Error::ThreadsTooFew)?, 46 | }) 47 | } 48 | } 49 | 50 | impl Default for Params { 51 | fn default() -> Self { 52 | Self::new( 53 | Self::DEFAULT_S_COST, 54 | Self::DEFAULT_T_COST, 55 | Self::DEFAULT_P_COST, 56 | ) 57 | .unwrap() 58 | } 59 | } 60 | 61 | #[cfg(feature = "password-hash")] 62 | impl Display for Params { 63 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 64 | ParamsString::try_from(self).map_err(|_| fmt::Error)?.fmt(f) 65 | } 66 | } 67 | 68 | #[cfg(feature = "password-hash")] 69 | impl FromStr for Params { 70 | type Err = password_hash::Error; 71 | 72 | fn from_str(s: &str) -> password_hash::Result { 73 | let params_string = 74 | ParamsString::from_str(s).map_err(|_| password_hash::Error::ParamsInvalid)?; 75 | 76 | Self::try_from(¶ms_string) 77 | } 78 | } 79 | 80 | #[cfg(feature = "password-hash")] 81 | impl TryFrom<&ParamsString> for Params { 82 | type Error = password_hash::Error; 83 | 84 | fn try_from(params_string: &ParamsString) -> password_hash::Result { 85 | let mut params = Self::default(); 86 | 87 | for (ident, value) in params_string.iter() { 88 | let dec = value.decimal().ok().and_then(NonZeroU32::new); 89 | 90 | match ident.as_str() { 91 | "s" => { 92 | params.s_cost = dec.ok_or(password_hash::Error::ParamInvalid { name: "s" })?; 93 | } 94 | "t" => { 95 | params.t_cost = dec.ok_or(password_hash::Error::ParamInvalid { name: "t" })?; 96 | } 97 | "p" => { 98 | params.p_cost = dec.ok_or(password_hash::Error::ParamInvalid { name: "p" })?; 99 | } 100 | _ => return Err(password_hash::Error::ParamsInvalid), 101 | } 102 | } 103 | 104 | Ok(params) 105 | } 106 | } 107 | 108 | #[cfg(feature = "password-hash")] 109 | impl TryFrom<&PasswordHash> for Params { 110 | type Error = password_hash::Error; 111 | 112 | fn try_from(hash: &PasswordHash) -> password_hash::Result { 113 | Self::try_from(&hash.params) 114 | } 115 | } 116 | 117 | #[cfg(feature = "password-hash")] 118 | impl TryFrom for ParamsString { 119 | type Error = password_hash::Error; 120 | 121 | fn try_from(params: Params) -> password_hash::Result { 122 | ParamsString::try_from(¶ms) 123 | } 124 | } 125 | 126 | #[cfg(feature = "password-hash")] 127 | impl TryFrom<&Params> for ParamsString { 128 | type Error = password_hash::Error; 129 | 130 | fn try_from(params: &Params) -> password_hash::Result { 131 | let mut output = ParamsString::new(); 132 | 133 | for (name, value) in [ 134 | ("s", params.s_cost), 135 | ("t", params.t_cost), 136 | ("p", params.p_cost), 137 | ] { 138 | output 139 | .add_decimal(name, value.get()) 140 | .map_err(|_| password_hash::Error::ParamInvalid { name })?; 141 | } 142 | 143 | Ok(output) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /yescrypt/src/mcf.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of the `password-hash` crate API. 2 | 3 | pub use mcf::{PasswordHash, PasswordHashRef}; 4 | 5 | use crate::{Params, yescrypt}; 6 | use alloc::vec; 7 | use mcf::Base64; 8 | use password_hash::{ 9 | CustomizedPasswordHasher, Error, PasswordHasher, PasswordVerifier, Result, Version, 10 | }; 11 | 12 | /// Identifier for yescrypt when encoding to the Modular Crypt Format, i.e. `$y$` 13 | #[cfg(feature = "password-hash")] 14 | const YESCRYPT_MCF_ID: &str = "y"; 15 | 16 | /// Base64 variant used by yescrypt. 17 | const YESCRYPT_BASE64: Base64 = Base64::ShaCrypt; 18 | 19 | /// yescrypt password hashing type which can produce and verify strings in Modular Crypt Format 20 | /// (MCF) which begin with `$y$` 21 | /// 22 | /// This is a ZST which impls traits from the [`password-hash`][`password_hash`] crate, notably 23 | /// the [`PasswordHasher`], [`PasswordVerifier`], and [`CustomizedPasswordHasher`] traits. 24 | /// 25 | /// See the toplevel documentation for a code example. 26 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 27 | pub struct Yescrypt; 28 | 29 | impl CustomizedPasswordHasher for Yescrypt { 30 | type Params = Params; 31 | 32 | fn hash_password_customized( 33 | &self, 34 | password: &[u8], 35 | salt: &[u8], 36 | alg_id: Option<&str>, 37 | version: Option, 38 | params: Params, 39 | ) -> Result { 40 | // TODO(tarcieri): tunable hash output size? 41 | const HASH_SIZE: usize = 32; 42 | 43 | match alg_id { 44 | Some(YESCRYPT_MCF_ID) | None => (), 45 | _ => return Err(Error::Algorithm), 46 | } 47 | 48 | if version.is_some() { 49 | return Err(Error::Version); 50 | } 51 | 52 | let mut out = [0u8; HASH_SIZE]; 53 | yescrypt(password, salt, ¶ms, &mut out)?; 54 | 55 | // Begin building the Modular Crypt Format hash. 56 | let mut mcf_hash = PasswordHash::from_id(YESCRYPT_MCF_ID).expect("should be valid"); 57 | 58 | // Add params string to the hash 59 | mcf_hash 60 | .push_displayable(params) 61 | .map_err(|_| Error::EncodingInvalid)?; 62 | 63 | // Add salt 64 | mcf_hash.push_base64(salt, YESCRYPT_BASE64); 65 | 66 | // Add yescrypt password hashing function output 67 | mcf_hash.push_base64(&out, YESCRYPT_BASE64); 68 | 69 | Ok(mcf_hash) 70 | } 71 | } 72 | 73 | impl PasswordHasher for Yescrypt { 74 | fn hash_password_with_salt(&self, password: &[u8], salt: &[u8]) -> Result { 75 | self.hash_password_customized(password, salt, None, None, Params::default()) 76 | } 77 | } 78 | 79 | impl PasswordVerifier for Yescrypt { 80 | fn verify_password(&self, password: &[u8], hash: &PasswordHash) -> Result<()> { 81 | self.verify_password(password, hash.as_password_hash_ref()) 82 | } 83 | } 84 | 85 | impl PasswordVerifier for Yescrypt { 86 | fn verify_password(&self, password: &[u8], hash: &PasswordHashRef) -> Result<()> { 87 | // verify id matches `$y` 88 | if hash.id() != YESCRYPT_MCF_ID { 89 | return Err(Error::Algorithm); 90 | } 91 | 92 | let mut fields = hash.fields(); 93 | 94 | // decode params 95 | let params: Params = fields 96 | .next() 97 | .ok_or(Error::EncodingInvalid)? 98 | .as_str() 99 | .parse()?; 100 | 101 | // decode salt 102 | let salt = fields 103 | .next() 104 | .ok_or(Error::EncodingInvalid)? 105 | .decode_base64(YESCRYPT_BASE64) 106 | .map_err(|_| Error::EncodingInvalid)?; 107 | 108 | // decode expected password hash 109 | let expected = fields 110 | .next() 111 | .ok_or(Error::EncodingInvalid)? 112 | .decode_base64(YESCRYPT_BASE64) 113 | .map_err(|_| Error::EncodingInvalid)?; 114 | 115 | // should be the last field 116 | if fields.next().is_some() { 117 | return Err(Error::EncodingInvalid); 118 | } 119 | 120 | let mut actual = vec![0u8; expected.len()]; 121 | yescrypt(password, &salt, ¶ms, &mut actual)?; 122 | 123 | if subtle::ConstantTimeEq::ct_ne(actual.as_slice(), &expected).into() { 124 | return Err(Error::PasswordInvalid); 125 | } 126 | 127 | Ok(()) 128 | } 129 | } 130 | 131 | impl PasswordVerifier for Yescrypt { 132 | fn verify_password(&self, password: &[u8], hash: &str) -> Result<()> { 133 | // TODO(tarcieri): better mapping from `mcf::Error` and `password_hash::Error`? 134 | let hash = PasswordHashRef::new(hash).map_err(|_| Error::EncodingInvalid)?; 135 | self.verify_password(password, hash) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RustCrypto: Password Hashes 2 | 3 | [![Project Chat][chat-image]][chat-link] 4 | ![Apache2/MIT licensed][license-image] 5 | [![Dependency Status][deps-image]][deps-link] 6 | 7 | Collection of password hashing algorithms, otherwise known as password-based key derivation functions, written in pure Rust. 8 | 9 | ## Supported Algorithms 10 | 11 | | Algorithm | Crate | Crates.io | Documentation | MSRV | 12 | |----------------|------------------|--------------------------------------------------------------------------------------------------------|---------------|-------------------------| 13 | | [Argon2] | [`argon2`] | [![crates.io](https://img.shields.io/crates/v/argon2.svg)](https://crates.io/crates/argon2) | [![Documentation](https://docs.rs/argon2/badge.svg)](https://docs.rs/argon2) | ![MSRV 1.81][msrv-1.81] | 14 | | [Balloon] | [`balloon‑hash`] | [![crates.io](https://img.shields.io/crates/v/balloon-hash.svg)](https://crates.io/crates/balloon-hash) | [![Documentation](https://docs.rs/balloon-hash/badge.svg)](https://docs.rs/balloon-hash) | ![MSRV 1.81][msrv-1.81] | 15 | | [bcrypt‑pbkdf] | [`bcrypt‑pbkdf`] | [![crates.io](https://img.shields.io/crates/v/bcrypt-pbkdf.svg)](https://crates.io/crates/bcrypt-pbkdf) | [![Documentation](https://docs.rs/bcrypt-pbkdf/badge.svg)](https://docs.rs/bcrypt-pbkdf) | ![MSRV 1.81][msrv-1.81] | 16 | | [PBKDF2] | [`pbkdf2`] | [![crates.io](https://img.shields.io/crates/v/pbkdf2.svg)](https://crates.io/crates/pbkdf2) | [![Documentation](https://docs.rs/pbkdf2/badge.svg)](https://docs.rs/pbkdf2) | ![MSRV 1.81][msrv-1.81] | 17 | | [scrypt] | [`scrypt`] | [![crates.io](https://img.shields.io/crates/v/scrypt.svg)](https://crates.io/crates/scrypt) | [![Documentation](https://docs.rs/scrypt/badge.svg)](https://docs.rs/scrypt) | ![MSRV 1.81][msrv-1.81] | 18 | | [SHA-crypt] | [`sha‑crypt`] | [![crates.io](https://img.shields.io/crates/v/sha-crypt.svg)](https://crates.io/crates/sha-crypt) | [![Documentation](https://docs.rs/sha-crypt/badge.svg)](https://docs.rs/sha-crypt) | ![MSRV 1.81][msrv-1.81] | 19 | 20 | Please see the [OWASP Password Storage Cheat Sheet] for assistance in selecting an appropriate algorithm for your use case. 21 | 22 | ## Usage 23 | 24 | The following code example shows how to verify a password when stored using one 25 | of many possible password hashing algorithms implemented in this repository. 26 | 27 | ```rust 28 | # fn main() -> Result<(), Box> { 29 | use password_hash::{phc, PasswordVerifier}; 30 | 31 | use argon2::Argon2; 32 | use pbkdf2::Pbkdf2; 33 | use scrypt::Scrypt; 34 | 35 | // Can be: `$argon2`, `$pbkdf2`, or `$scrypt` 36 | let hash_string = "$argon2i$v=19$m=65536,t=1,p=1$c29tZXNhbHQAAAAAAAAAAA$+r0d29hqEB0yasKr55ZgICsQGSkl0v0kgwhd+U3wyRo"; 37 | let input_password = "password"; 38 | 39 | let password_hash = phc::PasswordHash::new(&hash_string)?; 40 | 41 | // Trait objects for algorithms to support 42 | let algs: &[&dyn PasswordVerifier] = &[&Argon2::default(), &Pbkdf2, &Scrypt]; 43 | 44 | for alg in algs { 45 | if alg.verify_password(input_password.as_ref(), &password_hash).is_ok() { 46 | return Ok(()); 47 | } 48 | } 49 | 50 | // If we get here, the password is invalid 51 | # Err(Box::new(password_hash::Error::PasswordInvalid)) 52 | # } 53 | ``` 54 | 55 | ## Minimum Supported Rust Version (MSRV) Policy 56 | 57 | MSRV bumps are considered breaking changes and will be performed only with minor version bump. 58 | 59 | ## License 60 | 61 | All crates licensed under either of 62 | 63 | * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 64 | * [MIT license](http://opensource.org/licenses/MIT) 65 | 66 | at your option. 67 | 68 | ### Contribution 69 | 70 | 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. 71 | 72 | [//]: # (badges) 73 | 74 | [chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg 75 | [chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260046-password-hashes 76 | [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg 77 | [deps-image]: https://deps.rs/repo/github/RustCrypto/password-hashes/status.svg 78 | [deps-link]: https://deps.rs/repo/github/RustCrypto/password-hashes 79 | [msrv-1.81]: https://img.shields.io/badge/rustc-1.81.0+-blue.svg 80 | 81 | [//]: # (crates) 82 | 83 | [`argon2`]: ./argon2 84 | [`balloon‑hash`]: ./balloon-hash 85 | [`bcrypt‑pbkdf`]: ./bcrypt-pbkdf 86 | [`pbkdf2`]: ./pbkdf2 87 | [`scrypt`]: ./scrypt 88 | [`sha‑crypt`]: ./sha-crypt 89 | 90 | [//]: # (general links) 91 | 92 | [Argon2]: https://en.wikipedia.org/wiki/Argon2 93 | [Balloon]: https://en.wikipedia.org/wiki/Balloon_hashing 94 | [bcrypt‑pbkdf]: https://flak.tedunangst.com/post/bcrypt-pbkdf 95 | [PBKDF2]: https://en.wikipedia.org/wiki/PBKDF2 96 | [scrypt]: https://en.wikipedia.org/wiki/Scrypt 97 | [SHA-crypt]: https://www.akkadia.org/drepper/SHA-crypt.txt 98 | [OWASP Password Storage Cheat Sheet]: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html 99 | -------------------------------------------------------------------------------- /scrypt/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![doc = include_str!("../README.md")] 3 | #![cfg_attr(docsrs, feature(doc_cfg))] 4 | #![doc( 5 | html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", 6 | html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg" 7 | )] 8 | 9 | //! If you are only using the low-level [`scrypt`] function instead of the 10 | //! higher-level [`Scrypt`] struct to produce/verify hash strings, 11 | //! it's recommended to disable default features in your `Cargo.toml`: 12 | //! 13 | //! ```toml 14 | //! [dependencies] 15 | //! scrypt = { version = "0.12", default-features = false } 16 | //! ``` 17 | //! 18 | //! # Usage (simple PHC password hash usage with default params) 19 | //! 20 | #![cfg_attr(all(feature = "alloc", feature = "getrandom"), doc = "```")] 21 | #![cfg_attr(not(all(feature = "alloc", feature = "getrandom")), doc = "```ignore")] 22 | //! # fn main() -> Result<(), Box> { 23 | //! // NOTE: example requires `getrandom` feature is enabled 24 | //! 25 | //! use scrypt::{ 26 | //! password_hash::{ 27 | //! PasswordHasher, PasswordVerifier, phc::{PasswordHash, Salt} 28 | //! }, 29 | //! Scrypt 30 | //! }; 31 | //! 32 | //! let password = b"hunter42"; // Bad password; don't actually use! 33 | //! 34 | //! // Hash password to PHC string ($scrypt$...) 35 | //! let hash: PasswordHash = Scrypt.hash_password(password)?; 36 | //! let hash_string = hash.to_string(); 37 | //! 38 | //! // Verify password against PHC string 39 | //! let parsed_hash = PasswordHash::new(&hash_string)?; 40 | //! assert!(Scrypt.verify_password(password, &parsed_hash).is_ok()); 41 | //! # Ok(()) 42 | //! # } 43 | //! ``` 44 | 45 | #[macro_use] 46 | extern crate alloc; 47 | 48 | use pbkdf2::pbkdf2_hmac; 49 | use sha2::Sha256; 50 | 51 | /// Errors for `scrypt` operations. 52 | pub mod errors; 53 | mod params; 54 | mod romix; 55 | 56 | #[cfg(feature = "mcf")] 57 | pub mod mcf; 58 | #[cfg(feature = "phc")] 59 | pub mod phc; 60 | 61 | pub use crate::params::Params; 62 | 63 | #[cfg(feature = "password-hash")] 64 | pub use password_hash; 65 | 66 | #[cfg(all(doc, feature = "password-hash"))] 67 | use password_hash::{CustomizedPasswordHasher, PasswordHasher, PasswordVerifier}; 68 | 69 | /// The scrypt key derivation function. 70 | /// 71 | /// # Arguments 72 | /// - `password` - The password to process as a byte vector 73 | /// - `salt` - The salt value to use as a byte vector 74 | /// - `params` - The ScryptParams to use 75 | /// - `output` - The resulting derived key is returned in this byte vector. 76 | /// **WARNING: Make sure to compare this value in constant time!** 77 | /// 78 | /// # Return 79 | /// `Ok(())` if calculation is successful and `Err(InvalidOutputLen)` if 80 | /// `output` does not satisfy the following condition: 81 | /// `output.len() > 0 && output.len() <= (2^32 - 1) * 32`. 82 | /// 83 | /// # Note about output lengths 84 | /// The output size is determined entirely by size of the `output` parameter. 85 | /// 86 | /// If the length of the [`Params`] have been customized using the [`Params::new_with_output_len`] 87 | /// constructor, that length is ignored and the length of `output` is used instead. 88 | pub fn scrypt( 89 | password: &[u8], 90 | salt: &[u8], 91 | params: &Params, 92 | output: &mut [u8], 93 | ) -> Result<(), errors::InvalidOutputLen> { 94 | // This check required by Scrypt: 95 | // check output.len() > 0 && output.len() <= (2^32 - 1) * 32 96 | if output.is_empty() || output.len() / 32 > 0xffff_ffff { 97 | return Err(errors::InvalidOutputLen); 98 | } 99 | 100 | // The checks in the ScryptParams constructor guarantee 101 | // that the following is safe: 102 | let n = 1 << params.log_n; 103 | let r128 = (params.r as usize) * 128; 104 | let pr128 = (params.p as usize) * r128; 105 | let nr128 = n * r128; 106 | 107 | let mut b = vec![0u8; pr128]; 108 | pbkdf2_hmac::(password, salt, 1, &mut b); 109 | 110 | #[cfg(not(feature = "parallel"))] 111 | romix_sequential(nr128, r128, n, &mut b); 112 | #[cfg(feature = "parallel")] 113 | romix_parallel(nr128, r128, n, &mut b); 114 | 115 | pbkdf2_hmac::(password, &b, 1, output); 116 | Ok(()) 117 | } 118 | 119 | #[cfg(not(feature = "parallel"))] 120 | fn romix_sequential(nr128: usize, r128: usize, n: usize, b: &mut [u8]) { 121 | let mut v = vec![0u8; nr128]; 122 | let mut t = vec![0u8; r128]; 123 | 124 | b.chunks_mut(r128).for_each(|chunk| { 125 | romix::scrypt_ro_mix(chunk, &mut v, &mut t, n); 126 | }); 127 | } 128 | 129 | #[cfg(feature = "parallel")] 130 | fn romix_parallel(nr128: usize, r128: usize, n: usize, b: &mut [u8]) { 131 | use rayon::{iter::ParallelIterator as _, slice::ParallelSliceMut as _}; 132 | 133 | b.par_chunks_mut(r128).for_each(|chunk| { 134 | let mut v = vec![0u8; nr128]; 135 | let mut t = vec![0u8; r128]; 136 | romix::scrypt_ro_mix(chunk, &mut v, &mut t, n); 137 | }); 138 | } 139 | 140 | /// scrypt password hashing type which can produce and verify strings in either the Password Hashing 141 | /// Competition (PHC) string format which begin with `$scrypt$`, or in Modular Crypt Format (MCF) 142 | /// which begin with `$7$`. 143 | /// 144 | /// This is a ZST which impls traits from the [`password-hash`][`password_hash`] crate, notably 145 | /// the [`PasswordHasher`], [`PasswordVerifier`], and [`CustomizedPasswordHasher`] traits. 146 | /// 147 | /// See the toplevel documentation for a code example. 148 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 149 | pub struct Scrypt; 150 | -------------------------------------------------------------------------------- /pbkdf2/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## 0.13.0 (UNRELEASED) 9 | ### Removed 10 | - The `parallel` crate feature ([#702]) 11 | 12 | [#702]: https://github.com/RustCrypto/password-hashes/pull/702 13 | 14 | ## 0.12.2 (2023-07-08) 15 | ### Fixed 16 | - Use `RECOMMENDED_ROUNDS` in `Default` impl for `Params` ([#442]) 17 | 18 | [#442]: https://github.com/RustCrypto/password-hashes/pull/442 19 | 20 | ## 0.12.1 (2023-03-04) 21 | ### Changed 22 | - Re-export `hmac` ([#397]) 23 | 24 | [#397]: https://github.com/RustCrypto/password-hashes/pull/397 25 | 26 | ## 0.12.0 (2023-03-04) 27 | ### Changed 28 | - Add new wrapper functions: `pbkdf2_array`, `pbkdf2_hmac`, and 29 | `pbkdf2_hmac_array`. `pbkdf2` and `pbkdf2_array` now return 30 | `Result<(), InvalidLength>` instead of unwrapping it internally. ([#337]) 31 | - Use `sha1` dependency directly ([#363]) 32 | - Make `Ident` values inherent constants of `Algorithm` ([#375]) 33 | - Bump `password-hash` to v0.5 ([#383]) 34 | - Adopt OWASP recommended default `Params` ([#389]) 35 | 36 | ### Removed 37 | - `simple` feature is no longer enabled by default ([#336]) 38 | 39 | [#336]: https://github.com/RustCrypto/password-hashes/pull/336 40 | [#337]: https://github.com/RustCrypto/password-hashes/pull/337 41 | [#363]: https://github.com/RustCrypto/password-hashes/pull/363 42 | [#375]: https://github.com/RustCrypto/password-hashes/pull/375 43 | [#383]: https://github.com/RustCrypto/password-hashes/pull/383 44 | [#389]: https://github.com/RustCrypto/password-hashes/pull/389 45 | 46 | ## 0.11.0 (2022-03-28) 47 | ### Changed 48 | - Bump `password-hash` dependency to v0.4; MSRV 1.57 ([#283]) 49 | - 2021 edition upgrade ([#284]) 50 | 51 | [#283]: https://github.com/RustCrypto/password-hashes/pull/283 52 | [#284]: https://github.com/RustCrypto/password-hashes/pull/284 53 | 54 | ## 0.10.1 (2022-02-17) 55 | ### Fixed 56 | - Minimal versions build ([#273]) 57 | 58 | [#273]: https://github.com/RustCrypto/password-hashes/pull/273 59 | 60 | ## 0.10.0 (2021-11-25) 61 | ### Changed 62 | - Migrate from `crypto-mac` to `digest` v0.10 ([#254]) 63 | 64 | [#254]: https://github.com/RustCrypto/password-hashes/pull/254 65 | 66 | ## 0.9.0 (2021-08-27) 67 | ### Added 68 | - GOST test vectors ([#191]) 69 | 70 | ### Changed 71 | - Bump `password-hash` to v0.3 ([#217], [RustCrypto/traits#724]) 72 | - Use `resolver = "2"`; MSRV 1.51+ ([#220]) 73 | 74 | ### Removed 75 | - `McfHasher` impl on `Pbkdf2` ([#219]) 76 | 77 | [#191]: https://github.com/RustCrypto/password-hashing/pull/191 78 | [#217]: https://github.com/RustCrypto/password-hashing/pull/217 79 | [#219]: https://github.com/RustCrypto/password-hashing/pull/219 80 | [#220]: https://github.com/RustCrypto/password-hashing/pull/220 81 | [RustCrypto/traits#724]: https://github.com/RustCrypto/traits/pull/724 82 | 83 | ## 0.8.0 (2021-04-29) 84 | ### Changed 85 | - Bump `password-hash` crate dependency to v0.2 ([#164]) 86 | - Bump `hmac` and `crypto-mac` crate deps to v0.11 ([#165]) 87 | 88 | [#164]: https://github.com/RustCrypto/password-hashing/pull/164 89 | [#165]: https://github.com/RustCrypto/password-hashing/pull/165 90 | 91 | ## 0.7.5 (2021-03-27) 92 | ### Fixed 93 | - Pin `password-hash` to v0.1.2 or newer ([#151]) 94 | 95 | [#151]: https://github.com/RustCrypto/password-hashing/pull/151 96 | 97 | ## 0.7.4 (2021-03-17) 98 | ### Changed 99 | - Bump `base64ct` dependency to v1.0 ([#144]) 100 | 101 | [#144]: https://github.com/RustCrypto/password-hashing/pull/144 102 | 103 | ## 0.7.3 (2021-02-08) 104 | ### Changed 105 | - Enable `rand_core` feature of `password-hash` ([#130]) 106 | 107 | [#130]: https://github.com/RustCrypto/password-hashing/pull/130 108 | 109 | ## 0.7.2 (2021-02-01) 110 | ### Changed 111 | - Bump `base64ct` dependency to v0.2 ([#119]) 112 | 113 | [#119]: https://github.com/RustCrypto/password-hashing/pull/119 114 | 115 | ## 0.7.1 (2021-01-29) 116 | ### Removed 117 | - `alloc` dependencies for `simple` feature ([#107]) 118 | 119 | [#107]: https://github.com/RustCrypto/password-hashing/pull/107 120 | 121 | ## 0.7.0 (2021-01-29) 122 | ### Added 123 | - PHC hash format support using `password-hash` crate ([#82]) 124 | 125 | ### Changed 126 | - Rename `include_simple` features to `simple` ([#99]) 127 | 128 | ### Removed 129 | - Legacy `simple` API ([#98]) 130 | 131 | [#82]: https://github.com/RustCrypto/password-hashing/pull/82 132 | [#98]: https://github.com/RustCrypto/password-hashing/pull/98 133 | [#99]: https://github.com/RustCrypto/password-hashing/pull/99 134 | 135 | ## 0.6.0 (2020-10-18) 136 | ### Changed 137 | - Bump `crypto-mac` dependency to v0.10 ([#58]) 138 | - Bump `hmac` dependency to v0.10 ([#58]) 139 | 140 | [#58]: https://github.com/RustCrypto/password-hashing/pull/58 141 | 142 | ## 0.5.0 (2020-08-18) 143 | ### Changed 144 | - Bump `crypto-mac` dependency to v0.9 ([#44]) 145 | 146 | [#44]: https://github.com/RustCrypto/password-hashing/pull/44 147 | 148 | ## 0.4.0 (2020-06-10) 149 | ### Changed 150 | - Code improvements ([#33]) 151 | - Bump `rand` dependency to v0.7 ([#31]) 152 | - Bump `hmac` to v0.8 ([#30]) 153 | - Bump `sha2` to v0.9 ([#30]) 154 | - Bump `subtle` to v2 ([#13]) 155 | - MSRV 1.41+ ([#30]) 156 | - Upgrade to Rust 2018 edition ([#24]) 157 | 158 | [#33]: https://github.com/RustCrypto/password-hashing/pull/33 159 | [#31]: https://github.com/RustCrypto/password-hashing/pull/31 160 | [#30]: https://github.com/RustCrypto/password-hashing/pull/30 161 | [#24]: https://github.com/RustCrypto/password-hashing/pull/24 162 | [#13]: https://github.com/RustCrypto/password-hashing/pull/13 163 | 164 | ## 0.3.0 (2018-10-08) 165 | 166 | ## 0.2.3 (2018-08-30) 167 | 168 | ## 0.2.2 (2018-08-15) 169 | 170 | ## 0.2.1 (2018-08-06) 171 | 172 | ## 0.2.0 (2018-03-30) 173 | 174 | ## 0.1.0 (2017-08-16) 175 | -------------------------------------------------------------------------------- /scrypt/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## 0.11.0 (2023-03-04) 9 | ### Added 10 | - Ability to use custom output key length ([#255]) 11 | - Inherent constants for `Params` recommendations ([#387]) 12 | 13 | ### Changed 14 | - Bump `password-hash` to v0.5; MSRV 1.60 ([#383]) 15 | - Adopt OWASP recommendations ([#388]) 16 | - Bump `pbkdf2` to v0.12 ([#393]) 17 | 18 | [#255]: https://github.com/RustCrypto/password-hashes/pull/255 19 | [#383]: https://github.com/RustCrypto/password-hashes/pull/383 20 | [#387]: https://github.com/RustCrypto/password-hashes/pull/387 21 | [#388]: https://github.com/RustCrypto/password-hashes/pull/388 22 | [#393]: https://github.com/RustCrypto/password-hashes/pull/393 23 | 24 | ## 0.10.0 (2022-03-18) 25 | ### Changed 26 | - Bump `password-hash` dependency to v0.4; MSRV 1.57 ([#283]) 27 | - Bump `pbkdf2` dependency to v0.11 ([#291]) 28 | 29 | [#283]: https://github.com/RustCrypto/password-hashes/pull/283 30 | [#291]: https://github.com/RustCrypto/password-hashes/pull/291 31 | 32 | ## 0.9.0 (2022-02-17) 33 | ### Changed 34 | - Bump `salsa20` dependency to v0.10, edition to 2021, and MSRV to 1.56 ([#273]) 35 | 36 | [#273]: https://github.com/RustCrypto/password-hashes/pull/273 37 | 38 | ## 0.8.1 (2021-11-25) 39 | ### Changed 40 | - Bump `sha2` dependency to v0.10, `pbkdf2` to v0.10, `hmac` to v0.12 ([#254]) 41 | 42 | [#254]: https://github.com/RustCrypto/password-hashes/pull/254 43 | 44 | ## 0.8.0 (2021-08-27) 45 | ### Changed 46 | - Bump `password-hash` to v0.3 ([#217]) 47 | - Use `resolver = "2"`; MSRV 1.51+ ([#220]) 48 | - Bump `pbkdf2` dependency to v0.9 ([#233]) 49 | 50 | ### Removed 51 | - `McfHasher` impls for `Scrypt` ([#219]) 52 | 53 | [#217]: https://github.com/RustCrypto/password-hashing/pull/217 54 | [#219]: https://github.com/RustCrypto/password-hashing/pull/219 55 | [#220]: https://github.com/RustCrypto/password-hashing/pull/220 56 | [#233]: https://github.com/RustCrypto/password-hashing/pull/233 57 | 58 | ## 0.7.0 (2021-04-29) 59 | ### Changed 60 | - Bump `password-hash` crate dependency to v0.2 ([#164]) 61 | - Bump `hmac` and `crypto-mac` crate deps to v0.11 ([#165]) 62 | - Bump `salsa20` crate dependency to v0.8 ([#166]) 63 | - Bump `pbkdf2` crate dependency to v0.8 ([#167]) 64 | 65 | [#164]: https://github.com/RustCrypto/password-hashing/pull/164 66 | [#165]: https://github.com/RustCrypto/password-hashing/pull/165 67 | [#166]: https://github.com/RustCrypto/password-hashing/pull/166 68 | [#167]: https://github.com/RustCrypto/password-hashing/pull/167 69 | 70 | ## 0.6.5 (2021-03-27) 71 | ### Fixed 72 | - Pin `password-hash` to v0.1.2 or newer ([#151]) 73 | 74 | [#151]: https://github.com/RustCrypto/password-hashing/pull/151 75 | 76 | ## 0.6.4 (2021-03-17) 77 | ### Changed 78 | - Bump `base64ct` dependency to v1.0 ([#144]) 79 | 80 | [#144]: https://github.com/RustCrypto/password-hashing/pull/144 81 | 82 | ## 0.6.3 (2021-02-20) 83 | ### Changed 84 | - Enable `rand_core` feature of `password-hash` ([#139]) 85 | 86 | [#139]: https://github.com/RustCrypto/password-hashing/pull/139 87 | 88 | ## 0.6.2 (2021-02-06) 89 | ### Added 90 | - `Params` accessor methods ([#123]) 91 | 92 | [#123]: https://github.com/RustCrypto/password-hashing/pull/123 93 | 94 | ## 0.6.1 (2021-02-01) 95 | ### Changed 96 | - Bump `base64ct` dependency to v0.2 ([#119]) 97 | 98 | [#119]: https://github.com/RustCrypto/password-hashing/pull/119 99 | 100 | ## 0.6.0 (2021-01-29) 101 | ### Added 102 | - PHC hash support using `password-hash` crate ([#111]) 103 | 104 | ### Changed 105 | - Rename `include_simple` features to `simple` ([#99]) 106 | - Rename `ScryptParams` => `Params` ([#112]) 107 | 108 | [#99]: https://github.com/RustCrypto/password-hashing/pull/99 109 | [#111]: https://github.com/RustCrypto/password-hashing/pull/111 110 | [#112]: https://github.com/RustCrypto/password-hashing/pull/112 111 | 112 | ## 0.5.0 (2020-10-18) 113 | ### Changed 114 | - Bump `crypto-mac` dependency to v0.10 ([#58]) 115 | - Use `salsa20`crate to implement Salsa20/8 ([#60]) 116 | 117 | [#60]: https://github.com/RustCrypto/password-hashing/pull/60 118 | [#58]: https://github.com/RustCrypto/password-hashing/pull/58 119 | 120 | ## 0.4.1 (2020-08-24) 121 | ### Changed 122 | - Minor documentation update ([#50]) 123 | 124 | [#50]: https://github.com/RustCrypto/password-hashing/pull/50 125 | 126 | ## 0.4.0 (2020-08-18) 127 | ### Changed 128 | - Bump `pbkdf2` dependency to v0.5 ([#45]) 129 | 130 | [#45]: https://github.com/RustCrypto/password-hashing/pull/45 131 | 132 | ## 0.3.1 (2020-07-03) 133 | ### Fixed 134 | - Enable `alloc` feature for `base64`. ([#38]) 135 | - Remove superfluous `main()` in documentation ([#40]) 136 | 137 | [#38]: https://github.com/RustCrypto/password-hashing/pull/38 138 | [#40]: https://github.com/RustCrypto/password-hashing/pull/40 139 | 140 | ## 0.3.0 (2020-06-10) 141 | ### Added 142 | - `recommended` method for easy creation of recommended ScryptParam ([#28]) 143 | - `std` feature ([#32]) 144 | - `thread_rng` feature ([#33]) 145 | 146 | ### Changed 147 | - Code improvements ([#33]) 148 | - Bump `rand` to v0.7 ([#33]) 149 | - Bump `hmac` to v0.8 ([#30]) 150 | - Bump `sha2` to v0.9 ([#30]) 151 | - Bump `pbkdf2` to v0.4 ([#36]) 152 | - Bump `subtle` to v2 ([#13]) 153 | - MSRV 1.41+ ([#30]) 154 | - Upgrade to Rust 2018 edition ([#24]) 155 | 156 | [#36]: https://github.com/RustCrypto/password-hashing/pull/36 157 | [#33]: https://github.com/RustCrypto/password-hashing/pull/33 158 | [#32]: https://github.com/RustCrypto/password-hashing/pull/32 159 | [#30]: https://github.com/RustCrypto/password-hashing/pull/30 160 | [#28]: https://github.com/RustCrypto/password-hashing/pull/28 161 | [#24]: https://github.com/RustCrypto/password-hashing/pull/24 162 | [#13]: https://github.com/RustCrypto/password-hashing/pull/13 163 | 164 | ## 0.2.0 (2018-10-08) 165 | 166 | ## 0.1.2 (2018-08-30) 167 | 168 | ## 0.1.1 (2018-07-15) 169 | 170 | ## 0.1.0 (2018-06-30) 171 | -------------------------------------------------------------------------------- /argon2/src/block.rs: -------------------------------------------------------------------------------- 1 | //! Argon2 memory block functions 2 | 3 | use core::{ 4 | convert::{AsMut, AsRef}, 5 | num::Wrapping, 6 | ops::{BitXor, BitXorAssign}, 7 | slice, 8 | }; 9 | 10 | #[cfg(feature = "zeroize")] 11 | use zeroize::Zeroize; 12 | 13 | const TRUNC: u64 = u32::MAX as u64; 14 | 15 | #[rustfmt::skip] 16 | macro_rules! permute_step { 17 | ($a:expr, $b:expr, $c:expr, $d:expr) => { 18 | $a = (Wrapping($a) + Wrapping($b) + (Wrapping(2) * Wrapping(($a & TRUNC) * ($b & TRUNC)))).0; 19 | $d = ($d ^ $a).rotate_right(32); 20 | $c = (Wrapping($c) + Wrapping($d) + (Wrapping(2) * Wrapping(($c & TRUNC) * ($d & TRUNC)))).0; 21 | $b = ($b ^ $c).rotate_right(24); 22 | 23 | $a = (Wrapping($a) + Wrapping($b) + (Wrapping(2) * Wrapping(($a & TRUNC) * ($b & TRUNC)))).0; 24 | $d = ($d ^ $a).rotate_right(16); 25 | $c = (Wrapping($c) + Wrapping($d) + (Wrapping(2) * Wrapping(($c & TRUNC) * ($d & TRUNC)))).0; 26 | $b = ($b ^ $c).rotate_right(63); 27 | }; 28 | } 29 | 30 | macro_rules! permute { 31 | ( 32 | $v0:expr, $v1:expr, $v2:expr, $v3:expr, 33 | $v4:expr, $v5:expr, $v6:expr, $v7:expr, 34 | $v8:expr, $v9:expr, $v10:expr, $v11:expr, 35 | $v12:expr, $v13:expr, $v14:expr, $v15:expr, 36 | ) => { 37 | permute_step!($v0, $v4, $v8, $v12); 38 | permute_step!($v1, $v5, $v9, $v13); 39 | permute_step!($v2, $v6, $v10, $v14); 40 | permute_step!($v3, $v7, $v11, $v15); 41 | permute_step!($v0, $v5, $v10, $v15); 42 | permute_step!($v1, $v6, $v11, $v12); 43 | permute_step!($v2, $v7, $v8, $v13); 44 | permute_step!($v3, $v4, $v9, $v14); 45 | }; 46 | } 47 | 48 | /// Structure for the (1 KiB) memory block implemented as 128 64-bit words. 49 | #[derive(Copy, Clone, Debug)] 50 | #[repr(align(64))] 51 | pub struct Block([u64; Self::SIZE / 8]); 52 | 53 | impl Block { 54 | /// Memory block size in bytes 55 | pub const SIZE: usize = 1024; 56 | 57 | /// Returns a Block initialized with zeros. 58 | pub const fn new() -> Self { 59 | Self([0u64; Self::SIZE / 8]) 60 | } 61 | 62 | /// Load a block from a block-sized byte slice 63 | #[inline(always)] 64 | pub(crate) fn load(&mut self, input: &[u8; Block::SIZE]) { 65 | for (i, chunk) in input.chunks(8).enumerate() { 66 | self.0[i] = u64::from_le_bytes(chunk.try_into().expect("should be 8 bytes")); 67 | } 68 | } 69 | 70 | /// Iterate over the `u64` values contained in this block 71 | #[inline(always)] 72 | pub(crate) fn iter(&self) -> slice::Iter<'_, u64> { 73 | self.0.iter() 74 | } 75 | 76 | /// NOTE: do not call this directly. It should only be called via 77 | /// `Argon2::compress`. 78 | #[inline(always)] 79 | pub(crate) fn compress(rhs: &Self, lhs: &Self) -> Self { 80 | let r = *rhs ^ lhs; 81 | 82 | // Apply permutations rowwise 83 | let mut q = r; 84 | for chunk in q.0.chunks_exact_mut(16) { 85 | #[rustfmt::skip] 86 | permute!( 87 | chunk[0], chunk[1], chunk[2], chunk[3], 88 | chunk[4], chunk[5], chunk[6], chunk[7], 89 | chunk[8], chunk[9], chunk[10], chunk[11], 90 | chunk[12], chunk[13], chunk[14], chunk[15], 91 | ); 92 | } 93 | 94 | // Apply permutations columnwise 95 | for i in 0..8 { 96 | let b = i * 2; 97 | 98 | #[rustfmt::skip] 99 | permute!( 100 | q.0[b], q.0[b + 1], 101 | q.0[b + 16], q.0[b + 17], 102 | q.0[b + 32], q.0[b + 33], 103 | q.0[b + 48], q.0[b + 49], 104 | q.0[b + 64], q.0[b + 65], 105 | q.0[b + 80], q.0[b + 81], 106 | q.0[b + 96], q.0[b + 97], 107 | q.0[b + 112], q.0[b + 113], 108 | ); 109 | } 110 | 111 | q ^= &r; 112 | q 113 | } 114 | } 115 | 116 | impl Default for Block { 117 | fn default() -> Self { 118 | Self([0u64; Self::SIZE / 8]) 119 | } 120 | } 121 | 122 | impl AsRef<[u64]> for Block { 123 | fn as_ref(&self) -> &[u64] { 124 | &self.0 125 | } 126 | } 127 | 128 | impl AsMut<[u64]> for Block { 129 | fn as_mut(&mut self) -> &mut [u64] { 130 | &mut self.0 131 | } 132 | } 133 | 134 | impl BitXor<&Block> for Block { 135 | type Output = Block; 136 | 137 | fn bitxor(mut self, rhs: &Block) -> Self::Output { 138 | self ^= rhs; 139 | self 140 | } 141 | } 142 | 143 | impl BitXorAssign<&Block> for Block { 144 | fn bitxor_assign(&mut self, rhs: &Block) { 145 | for (dst, src) in self.0.iter_mut().zip(rhs.0.iter()) { 146 | *dst ^= src; 147 | } 148 | } 149 | } 150 | 151 | #[cfg(feature = "zeroize")] 152 | impl Zeroize for Block { 153 | fn zeroize(&mut self) { 154 | self.0.zeroize(); 155 | } 156 | } 157 | 158 | /// Custom implementation of `Box<[Block]>` until `Box::try_new_zeroed_slice` is stabilized. 159 | #[cfg(feature = "alloc")] 160 | pub(crate) struct Blocks { 161 | p: core::ptr::NonNull, 162 | len: usize, 163 | } 164 | 165 | #[cfg(feature = "alloc")] 166 | impl Blocks { 167 | pub fn new(len: usize) -> Option { 168 | use alloc::alloc::{Layout, alloc_zeroed}; 169 | use core::ptr::NonNull; 170 | 171 | if len == 0 { 172 | return None; 173 | } 174 | 175 | let layout = Layout::array::(len).ok()?; 176 | // SAFETY: `alloc_zeroed` is used correctly with non-zero layout 177 | let p = unsafe { alloc_zeroed(layout) }; 178 | 179 | let p = NonNull::new(p.cast())?; 180 | Some(Self { p, len }) 181 | } 182 | 183 | pub fn as_slice(&mut self) -> &mut [Block] { 184 | // SAFETY: `self.p` is a valid non-zero pointer that points to memory of the necessary size 185 | unsafe { slice::from_raw_parts_mut(self.p.as_ptr(), self.len) } 186 | } 187 | } 188 | 189 | #[cfg(feature = "alloc")] 190 | impl Drop for Blocks { 191 | fn drop(&mut self) { 192 | use alloc::alloc::{Layout, dealloc}; 193 | // SAFETY: layout was checked during construction 194 | let layout = unsafe { Layout::array::(self.len).unwrap_unchecked() }; 195 | // SAFETY: we use `dealloc` correctly with the previously allocated pointer 196 | unsafe { 197 | dealloc(self.p.as_ptr().cast(), layout); 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /argon2/src/memory.rs: -------------------------------------------------------------------------------- 1 | //! Views into Argon2 memory that can be processed in parallel. 2 | //! 3 | //! This module implements, with a combination of compile-time borrowing and runtime checking, the 4 | //! cooperative contract described in section 3.4 (Indexing) of RFC 9106: 5 | //! 6 | //! > To enable parallel block computation, we further partition the memory matrix into SL = 4 7 | //! > vertical slices. The intersection of a slice and a lane is called a segment, which has a 8 | //! > length of q/SL. Segments of the same slice can be computed in parallel and do not reference 9 | //! > blocks from each other. All other blocks can be referenced. 10 | 11 | use core::marker::PhantomData; 12 | use core::ptr::NonNull; 13 | 14 | #[cfg(feature = "parallel")] 15 | use rayon::iter::{IntoParallelIterator, ParallelIterator}; 16 | 17 | use crate::{Block, SYNC_POINTS}; 18 | 19 | /// Extension trait for Argon2 memory blocks. 20 | pub(crate) trait Memory<'a> { 21 | /// Compute each Argon2 segment. 22 | /// 23 | /// By default computation is single threaded. Parallel computation can be enabled with the 24 | /// `parallel` feature, in which case [rayon] is used to compute as many lanes in parallel as 25 | /// possible. 26 | fn for_each_segment(&mut self, lanes: usize, f: F) 27 | where 28 | F: Fn(SegmentView<'_>, usize, usize) + Sync + Send; 29 | } 30 | 31 | impl Memory<'_> for &mut [Block] { 32 | #[cfg(not(feature = "parallel"))] 33 | fn for_each_segment(&mut self, lanes: usize, f: F) 34 | where 35 | F: Fn(SegmentView<'_>, usize, usize) + Sync + Send, 36 | { 37 | let inner = MemoryInner::new(self, lanes); 38 | for slice in 0..SYNC_POINTS { 39 | for lane in 0..lanes { 40 | // SAFETY: `self` exclusively borrows the blocks, and we sequentially process 41 | // slices and segments. 42 | let segment = unsafe { SegmentView::new(inner, slice, lane) }; 43 | f(segment, slice, lane); 44 | } 45 | } 46 | } 47 | 48 | #[cfg(feature = "parallel")] 49 | fn for_each_segment(&mut self, lanes: usize, f: F) 50 | where 51 | F: Fn(SegmentView<'_>, usize, usize) + Sync + Send, 52 | { 53 | let inner = MemoryInner::new(self, lanes); 54 | for slice in 0..SYNC_POINTS { 55 | (0..lanes).into_par_iter().for_each(|lane| { 56 | // SAFETY: `self` exclusively borrows the blocks, we sequentially process slices, 57 | // and we create exactly one segment view per lane in a slice. 58 | let segment = unsafe { SegmentView::new(inner, slice, lane) }; 59 | f(segment, slice, lane); 60 | }); 61 | } 62 | } 63 | } 64 | 65 | /// Low-level pointer and metadata for an Argon2 memory region. 66 | #[derive(Clone, Copy)] 67 | struct MemoryInner<'a> { 68 | blocks: NonNull, 69 | block_count: usize, 70 | lane_length: usize, 71 | phantom: PhantomData<&'a mut Block>, 72 | } 73 | 74 | impl MemoryInner<'_> { 75 | fn new(memory_blocks: &mut [Block], lanes: usize) -> Self { 76 | let block_count = memory_blocks.len(); 77 | let lane_length = block_count / lanes; 78 | 79 | // SAFETY: the pointer needs to be derived from a mutable reference because (later) 80 | // mutating the blocks through a pointer derived from a shared reference would be UB. 81 | let blocks = NonNull::from(memory_blocks); 82 | 83 | MemoryInner { 84 | blocks: blocks.cast(), 85 | block_count, 86 | lane_length, 87 | phantom: PhantomData, 88 | } 89 | } 90 | 91 | fn lane_of(&self, index: usize) -> usize { 92 | index / self.lane_length 93 | } 94 | 95 | fn slice_of(&self, index: usize) -> usize { 96 | index / (self.lane_length / SYNC_POINTS) % SYNC_POINTS 97 | } 98 | } 99 | 100 | // SAFETY: private type, and just a pointer with some metadata. 101 | unsafe impl Send for MemoryInner<'_> {} 102 | 103 | // SAFETY: private type, and just a pointer with some metadata. 104 | unsafe impl Sync for MemoryInner<'_> {} 105 | 106 | /// A view into Argon2 memory for a particular segment (i.e. slice × lane). 107 | pub(crate) struct SegmentView<'a> { 108 | inner: MemoryInner<'a>, 109 | slice: usize, 110 | lane: usize, 111 | } 112 | 113 | impl<'a> SegmentView<'a> { 114 | /// Create a view into Argon2 memory for a particular segment (i.e. slice × lane). 115 | /// 116 | /// # Safety 117 | /// 118 | /// At any time, there can be at most one view for a given Argon2 segment. Additionally, all 119 | /// concurrent segment views must be for the same slice. 120 | unsafe fn new(inner: MemoryInner<'a>, slice: usize, lane: usize) -> Self { 121 | SegmentView { inner, slice, lane } 122 | } 123 | 124 | /// Get a shared reference to a block. 125 | /// 126 | /// # Panics 127 | /// 128 | /// Panics if the index is out of bounds or if the desired block *could* be mutably aliased (if 129 | /// it is on the current slice but on a different lane/segment). 130 | pub fn get_block(&self, index: usize) -> &Block { 131 | assert!(index < self.inner.block_count); 132 | assert!(self.inner.lane_of(index) == self.lane || self.inner.slice_of(index) != self.slice); 133 | 134 | // SAFETY: by construction, the base pointer is valid for reads, and we assert that the 135 | // index is in bounds. We also assert that the index either lies on this lane, or is on 136 | // another slice. Finally, we're the only view into this segment, and mutating through it 137 | // requires `&mut self` and is restricted to blocks within the segment. 138 | unsafe { self.inner.blocks.add(index).as_ref() } 139 | } 140 | 141 | /// Get a mutable reference to a block. 142 | /// 143 | /// # Panics 144 | /// 145 | /// Panics if the index is out of bounds or if the desired block lies outside this segment. 146 | pub fn get_block_mut(&mut self, index: usize) -> &mut Block { 147 | assert!(index < self.inner.block_count); 148 | assert_eq!(self.inner.lane_of(index), self.lane); 149 | assert_eq!(self.inner.slice_of(index), self.slice); 150 | 151 | // SAFETY: by construction, the base pointer is valid for reads and writes, and we assert 152 | // that the index is in bounds. We also assert that the index lies on this segment, and 153 | // we're the only view for it, taking `&mut self`. 154 | unsafe { self.inner.blocks.add(index).as_mut() } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /yescrypt/src/pwxform.rs: -------------------------------------------------------------------------------- 1 | //! pwxform stands for "parallel wide transformation", although it can as well be tuned to be as 2 | //! narrow as one 64-bit lane. 3 | //! 4 | //! It operates on 64-bit lanes which are designed to be grouped into wider "simple SIMD" lanes, 5 | //! which are in turn possibly grouped into an even wider "gather SIMD" vector. 6 | 7 | use crate::{ 8 | salsa20, 9 | util::{slice_as_chunks_mut, xor}, 10 | }; 11 | use core::mem; 12 | 13 | /// Number of 64-bit lanes per "simple SIMD" lane (requiring only arithmetic and bitwise operations 14 | /// on its 64-bit elements). Must be a power of 2. 15 | const PWXSIMPLE: usize = 2; 16 | 17 | /// Number of parallel "simple SIMD" lanes per "gather SIMD" vector (requiring "S-box lookups" of 18 | /// values as wide as a "simple SIMD" lane from PWXgather typically non-contiguous memory 19 | /// locations). Must be a power of 2. 20 | const PWXGATHER: usize = 4; 21 | 22 | /// Number of sequential rounds of pwxform’s basic transformation. Must be a power of 2, plus 2 23 | /// (e.g. 3, 4, 6, 10). 24 | const PWXROUNDS: usize = 6; 25 | 26 | /// Number of S-box index bits, thereby controlling the size of each of pwxform’s two S-boxes 27 | /// (in "simple SIMD" wide elements). 28 | const SWIDTH: usize = 8; 29 | 30 | const PWXBYTES: usize = PWXGATHER * PWXSIMPLE * 8; 31 | const PWXWORDS: usize = PWXBYTES / size_of::(); 32 | const SMASK: usize = ((1 << SWIDTH) - 1) * PWXSIMPLE * 8; 33 | pub(crate) const SBYTES: usize = 3 * (1 << SWIDTH) * PWXSIMPLE * 8; 34 | pub(crate) const SWORDS: usize = SBYTES / size_of::(); 35 | 36 | #[allow(clippy::cast_possible_truncation)] 37 | pub(crate) const RMIN: u32 = PWXBYTES.div_ceil(128) as u32; 38 | 39 | /// Parallel wide transformation (pwxform) context. 40 | pub(crate) struct PwxformCtx<'a> { 41 | pub(crate) s0: &'a mut [[u32; 2]], 42 | pub(crate) s1: &'a mut [[u32; 2]], 43 | pub(crate) s2: &'a mut [[u32; 2]], 44 | pub(crate) w: usize, 45 | } 46 | 47 | impl PwxformCtx<'_> { 48 | /// Compute `B = BlockMix_pwxform{salsa20/2, ctx, r}(B)`. Input `B` must be 128 bytes in length. 49 | /// 50 | /// `BlockMix_pwxform` differs from scrypt’s `BlockMix` in that it doesn’t shuffle output 51 | /// sub-blocks, uses pwxform in place of Salsa20/8 for as long as sub-blocks processed with 52 | /// pwxform fit in the provided block B, and finally uses Salsa20/2 (that is, Salsa20 with only 53 | /// one double-round) to post-process the last sub-block output by pwxform (thereby finally 54 | /// mixing pwxform’s parallel lanes). 55 | pub(crate) fn blockmix_pwxform(&mut self, b: &mut [u32], r: usize) { 56 | // Convert 128-byte blocks to PWXbytes blocks 57 | // TODO(tarcieri): use upstream `[T]::as_chunks_mut` when MSRV is 1.88 58 | let (b, _b) = slice_as_chunks_mut::<_, PWXWORDS>(b); 59 | assert_eq!(b.len(), 2 * r); 60 | assert!(_b.is_empty()); 61 | 62 | // 1: r_1 <-- 128r / PWXbytes 63 | let r1 = (128 * r) / PWXBYTES; 64 | 65 | // 2: X <-- B'_{r_1 - 1} 66 | let mut x = b[r1 - 1]; 67 | 68 | // 3: for i = 0 to r_1 - 1 do 69 | #[allow(clippy::needless_range_loop)] 70 | for i in 0..r1 { 71 | // 4: if r_1 > 1 72 | if r1 > 1 { 73 | // 5: X <-- X xor B'_i 74 | xor(&mut x, &b[i]); 75 | } 76 | 77 | // 7: X <-- pwxform(X) 78 | self.pwxform(&mut x); 79 | 80 | // 8: B'_i <-- X 81 | b[i] = x; 82 | } 83 | 84 | // 10: i <-- floor((r_1 - 1) * PWXbytes / 64) 85 | let i = (r1 - 1) * PWXBYTES / 64; 86 | 87 | // 11: B_i <-- H(B_i) 88 | salsa20::salsa20_2(&mut b[i]); 89 | 90 | // 12: for i = i + 1 to 2r - 1 do 91 | for i in (i + 1)..(2 * r) { 92 | // TODO(tarcieri): use `get_disjoint_mut` when MSRV is 1.86 93 | let (bim1, bi) = b[(i - 1)..i].split_at_mut(1); 94 | let (bim1, bi) = (&bim1[0], &mut bi[0]); 95 | 96 | /* 13: B_i <-- H(B_i xor B_{i-1}) */ 97 | xor(bi, bim1); 98 | salsa20::salsa20_2(bi); 99 | } 100 | } 101 | 102 | /// Transform the provided block using the provided S-boxes. 103 | fn pwxform(&mut self, b: &mut [u32; 16]) { 104 | // TODO(tarcieri): use upstream `[T]::as_chunks_mut` when MSRV is 1.88 105 | let b = slice_as_chunks_mut::<_, 2>(slice_as_chunks_mut::<_, PWXSIMPLE>(b).0).0; 106 | let mut w = self.w; 107 | 108 | // 1: for i = 0 to PWXrounds - 1 do 109 | for i in 0..PWXROUNDS { 110 | // 2: for j = 0 to PWXgather - 1 do 111 | #[allow(clippy::needless_range_loop)] 112 | for j in 0..PWXGATHER { 113 | let mut xl: u32 = b[j][0][0]; 114 | let mut xh: u32 = b[j][0][1]; 115 | 116 | // 3: p0 <-- (lo(B_{j,0}) & Smask) / (PWXsimple * 8) 117 | let p0 = &self.s0[(xl as usize & SMASK) / 8..]; 118 | 119 | // 4: p1 <-- (hi(B_{j,0}) & Smask) / (PWXsimple * 8) 120 | let p1 = &self.s1[(xh as usize & SMASK) / 8..]; 121 | 122 | // 5: for k = 0 to PWXsimple - 1 do 123 | for k in 0..PWXSIMPLE { 124 | // 6: B_{j,k} <-- (hi(B_{j,k}) * lo(B_{j,k}) + S0_{p0,k}) xor S1_{p1,k} 125 | let s0 = (u64::from(p0[k][1]) << 32).wrapping_add(u64::from(p0[k][0])); 126 | let s1 = (u64::from(p1[k][1]) << 32).wrapping_add(u64::from(p1[k][0])); 127 | 128 | xl = b[j][k][0]; 129 | xh = b[j][k][1]; 130 | 131 | let mut x = u64::from(xh).wrapping_mul(u64::from(xl)); 132 | x = x.wrapping_add(s0); 133 | x ^= s1; 134 | 135 | let x_lo = (x & 0xFFFF_FFFF) as u32; 136 | let x_hi = ((x >> 32) & 0xFFFF_FFFF) as u32; 137 | b[j][k][0] = x_lo; 138 | b[j][k][1] = x_hi; 139 | 140 | // 8: if (i != 0) and (i != PWXrounds - 1) 141 | if i != 0 && i != (PWXROUNDS - 1) { 142 | // 9: S2_w <-- B_j 143 | self.s2[w][0] = x_lo; 144 | self.s2[w][1] = x_hi; 145 | w += 1; 146 | } 147 | } 148 | } 149 | } 150 | 151 | // 14: (S0, S1, S2) <-- (S2, S0, S1) 152 | mem::swap(&mut self.s0, &mut self.s2); 153 | mem::swap(&mut self.s1, &mut self.s2); 154 | 155 | // 15: w <-- w mod 2^Swidth 156 | self.w = w & ((1 << SWIDTH) * PWXSIMPLE - 1); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /sha-crypt/src/mcf.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of the `password-hash` crate API. 2 | 3 | pub use mcf::{PasswordHash, PasswordHashRef}; 4 | 5 | use crate::{BLOCK_SIZE_SHA256, BLOCK_SIZE_SHA512, Params, sha256_crypt, sha512_crypt}; 6 | use base64ct::{Base64ShaCrypt, Encoding}; 7 | use core::{marker::PhantomData, str::FromStr}; 8 | use mcf::Base64; 9 | use password_hash::{ 10 | CustomizedPasswordHasher, Error, PasswordHasher, PasswordVerifier, Result, Version, 11 | }; 12 | use sha2::{Sha256, Sha512}; 13 | 14 | /// SHA-crypt uses digest-specific parameters. 15 | pub trait ShaCryptCore { 16 | /// Modular Crypt Format ID. 17 | const MCF_ID: &'static str; 18 | 19 | /// Output data 20 | type Output: AsRef<[u8]>; 21 | 22 | /// Core function 23 | fn sha_crypt_core(password: &[u8], salt: &[u8], params: &Params) -> Self::Output; 24 | } 25 | 26 | /// sha-crypt type for use with [`PasswordHasher`]. 27 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 28 | pub struct ShaCrypt { 29 | phantom: PhantomData, 30 | } 31 | 32 | /// SHA-crypt initialized using SHA-256 33 | pub const SHA256_CRYPT: ShaCrypt = ShaCrypt { 34 | phantom: PhantomData, 35 | }; 36 | 37 | /// SHA-crypt initialized using SHA-512 38 | pub const SHA512_CRYPT: ShaCrypt = ShaCrypt { 39 | phantom: PhantomData, 40 | }; 41 | 42 | impl CustomizedPasswordHasher for ShaCrypt 43 | where 44 | Self: ShaCryptCore, 45 | { 46 | type Params = Params; 47 | 48 | fn hash_password_customized( 49 | &self, 50 | password: &[u8], 51 | salt: &[u8], 52 | alg_id: Option<&str>, 53 | version: Option, 54 | params: Params, 55 | ) -> Result { 56 | if alg_id.is_some() && alg_id != Some(Self::MCF_ID) { 57 | return Err(Error::Algorithm); 58 | } 59 | 60 | if version.is_some() { 61 | return Err(Error::Version); 62 | } 63 | 64 | // We compute the function over the Base64-encoded salt 65 | let salt = Base64ShaCrypt::encode_string(salt); 66 | let out = Self::sha_crypt_core(password, salt.as_bytes(), ¶ms); 67 | 68 | let mut mcf_hash = PasswordHash::from_id(Self::MCF_ID).expect("should have valid ID"); 69 | 70 | mcf_hash 71 | .push_displayable(¶ms) 72 | .expect("should be valid field"); 73 | 74 | mcf_hash 75 | .push_str(&salt) 76 | .map_err(|_| Error::EncodingInvalid)?; 77 | 78 | mcf_hash.push_base64(out.as_ref(), Base64::ShaCrypt); 79 | 80 | Ok(mcf_hash) 81 | } 82 | } 83 | 84 | impl PasswordHasher for ShaCrypt 85 | where 86 | Self: ShaCryptCore, 87 | { 88 | fn hash_password_with_salt(&self, password: &[u8], salt: &[u8]) -> Result { 89 | self.hash_password_customized(password, salt, None, None, Params::default()) 90 | } 91 | } 92 | 93 | impl PasswordVerifier for ShaCrypt 94 | where 95 | Self: ShaCryptCore, 96 | { 97 | fn verify_password(&self, password: &[u8], hash: &PasswordHash) -> Result<()> { 98 | self.verify_password(password, hash.as_password_hash_ref()) 99 | } 100 | } 101 | 102 | impl PasswordVerifier for ShaCrypt 103 | where 104 | Self: ShaCryptCore, 105 | { 106 | fn verify_password(&self, password: &[u8], hash: &PasswordHashRef) -> Result<()> { 107 | if hash.id() != Self::MCF_ID { 108 | return Err(Error::Algorithm); 109 | } 110 | 111 | let mut fields = hash.fields(); 112 | let mut next = fields.next().ok_or(Error::EncodingInvalid)?; 113 | 114 | let mut params = Params::default(); 115 | 116 | // decode params 117 | // TODO(tarcieri): `mcf::Field` helper methods for parsing params? 118 | if let Ok(p) = Params::from_str(next.as_str()) { 119 | params = p; 120 | next = fields.next().ok_or(Error::EncodingInvalid)?; 121 | } 122 | 123 | let salt = next.as_str().as_bytes(); 124 | 125 | // decode expected password hash 126 | let expected = fields 127 | .next() 128 | .ok_or(Error::EncodingInvalid)? 129 | .decode_base64(Base64::ShaCrypt) 130 | .map_err(|_| Error::EncodingInvalid)?; 131 | 132 | // should be the last field 133 | if fields.next().is_some() { 134 | return Err(Error::EncodingInvalid); 135 | } 136 | 137 | let actual = Self::sha_crypt_core(password, salt, ¶ms); 138 | 139 | if subtle::ConstantTimeEq::ct_ne(actual.as_ref(), &expected).into() { 140 | return Err(Error::PasswordInvalid); 141 | } 142 | 143 | Ok(()) 144 | } 145 | } 146 | 147 | impl PasswordVerifier for ShaCrypt 148 | where 149 | Self: ShaCryptCore, 150 | { 151 | fn verify_password(&self, password: &[u8], hash: &str) -> password_hash::Result<()> { 152 | // TODO(tarcieri): better mapping from `mcf::Error` and `password_hash::Error`? 153 | let hash = PasswordHashRef::new(hash).map_err(|_| Error::EncodingInvalid)?; 154 | self.verify_password(password, hash) 155 | } 156 | } 157 | 158 | impl ShaCryptCore for ShaCrypt { 159 | const MCF_ID: &'static str = "5"; 160 | type Output = [u8; BLOCK_SIZE_SHA256]; 161 | 162 | /// Core function 163 | fn sha_crypt_core(password: &[u8], salt: &[u8], params: &Params) -> Self::Output { 164 | let output = sha256_crypt(password, salt, params); 165 | let transposition_table = [ 166 | 20, 10, 0, 11, 1, 21, 2, 22, 12, 23, 13, 3, 14, 4, 24, 5, 25, 15, 26, 16, 6, 17, 7, 27, 167 | 8, 28, 18, 29, 19, 9, 30, 31, 168 | ]; 169 | 170 | let mut transposed = [0u8; BLOCK_SIZE_SHA256]; 171 | for (i, &ti) in transposition_table.iter().enumerate() { 172 | transposed[i] = output[ti as usize]; 173 | } 174 | 175 | transposed 176 | } 177 | } 178 | 179 | impl ShaCryptCore for ShaCrypt { 180 | const MCF_ID: &'static str = "6"; 181 | type Output = [u8; BLOCK_SIZE_SHA512]; 182 | 183 | /// Core function 184 | fn sha_crypt_core(password: &[u8], salt: &[u8], params: &Params) -> Self::Output { 185 | let output = sha512_crypt(password, salt, params); 186 | let transposition_table = [ 187 | 42, 21, 0, 1, 43, 22, 23, 2, 44, 45, 24, 3, 4, 46, 25, 26, 5, 47, 48, 27, 6, 7, 49, 28, 188 | 29, 8, 50, 51, 30, 9, 10, 52, 31, 32, 11, 53, 54, 33, 12, 13, 55, 34, 35, 14, 56, 57, 189 | 36, 15, 16, 58, 37, 38, 17, 59, 60, 39, 18, 19, 61, 40, 41, 20, 62, 63, 190 | ]; 191 | 192 | let mut transposed = [0u8; BLOCK_SIZE_SHA512]; 193 | for (i, &ti) in transposition_table.iter().enumerate() { 194 | transposed[i] = output[ti as usize]; 195 | } 196 | 197 | transposed 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /password-auth/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![cfg_attr(docsrs, feature(doc_cfg))] 3 | #![doc = include_str!("../README.md")] 4 | #![doc( 5 | html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", 6 | html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg" 7 | )] 8 | #![warn( 9 | clippy::checked_conversions, 10 | clippy::arithmetic_side_effects, 11 | clippy::panic, 12 | clippy::panic_in_result_fn, 13 | clippy::unwrap_used, 14 | missing_docs, 15 | rust_2018_idioms, 16 | unused_lifetimes, 17 | unused_qualifications 18 | )] 19 | 20 | extern crate alloc; 21 | #[cfg(feature = "std")] 22 | extern crate std; 23 | 24 | mod errors; 25 | 26 | pub use crate::errors::{ParseError, VerifyError}; 27 | 28 | use alloc::string::{String, ToString}; 29 | use password_hash::{ 30 | PasswordHasher, PasswordVerifier, 31 | phc::{ParamsString, PasswordHash}, 32 | }; 33 | 34 | #[cfg(not(any(feature = "argon2", feature = "pbkdf2", feature = "scrypt")))] 35 | compile_error!( 36 | "please enable at least one password hash crate feature, e.g. argon2, pbkdf2, scrypt" 37 | ); 38 | 39 | #[cfg(feature = "argon2")] 40 | use argon2::Argon2; 41 | #[cfg(feature = "pbkdf2")] 42 | use pbkdf2::Pbkdf2; 43 | #[cfg(feature = "scrypt")] 44 | use scrypt::Scrypt; 45 | 46 | /// Generate a password hash for the given password. 47 | /// 48 | /// Uses the best available password hashing algorithm given the enabled 49 | /// crate features (typically Argon2 unless explicitly disabled). 50 | pub fn generate_hash(password: impl AsRef<[u8]>) -> String { 51 | generate_phc_hash(password.as_ref()) 52 | .as_ref() 53 | .map(ToString::to_string) 54 | .expect("password hashing error") 55 | } 56 | 57 | /// Generate a PHC hash using the preferred algorithm. 58 | #[allow(unreachable_code)] 59 | fn generate_phc_hash(password: &[u8]) -> password_hash::Result { 60 | // 61 | // Algorithms below are in order of preference 62 | // 63 | #[cfg(feature = "argon2")] 64 | return Argon2::default().hash_password(password); 65 | 66 | #[cfg(feature = "scrypt")] 67 | return Scrypt.hash_password(password); 68 | 69 | #[cfg(feature = "pbkdf2")] 70 | return Pbkdf2.hash_password(password); 71 | } 72 | 73 | /// Verify the provided password against the provided password hash. 74 | /// 75 | /// # Returns 76 | /// 77 | /// - `Ok(())` if the password hash verified successfully 78 | /// - `Err(VerifyError)` if the hash didn't parse successfully or the password 79 | /// failed to verify against the hash. 80 | pub fn verify_password(password: impl AsRef<[u8]>, hash: &str) -> Result<(), VerifyError> { 81 | let hash = PasswordHash::new(hash).map_err(ParseError::new)?; 82 | 83 | let algs: &[&dyn PasswordVerifier] = &[ 84 | #[cfg(feature = "argon2")] 85 | &Argon2::default(), 86 | #[cfg(feature = "pbkdf2")] 87 | &Pbkdf2, 88 | #[cfg(feature = "scrypt")] 89 | &Scrypt, 90 | ]; 91 | 92 | for &alg in algs { 93 | if alg.verify_password(password.as_ref(), &hash).is_ok() { 94 | return Ok(()); 95 | } 96 | } 97 | 98 | Err(VerifyError::PasswordInvalid) 99 | } 100 | 101 | /// Determine if the given password hash is using the recommended algorithm and 102 | /// parameters. 103 | /// 104 | /// This can be used by implementations which wish to lazily update their 105 | /// password hashes (i.e. by rehashing the password with [`generate_hash`]) 106 | /// to determine if such an update should be applied. 107 | /// 108 | /// # Returns 109 | /// - `Ok(true)` if the hash *isn't* using the latest recommended algorithm/parameters. 110 | /// - `Ok(false)` if the hash *is* using the latest recommended algorithm/parameters. 111 | /// - `Err(ParseError)` if the hash could not be parsed. 112 | #[allow(unreachable_code)] 113 | pub fn is_hash_obsolete(hash: &str) -> Result { 114 | let hash = PasswordHash::new(hash).map_err(ParseError::new)?; 115 | 116 | #[cfg(feature = "argon2")] 117 | return Ok(hash.algorithm != argon2::Algorithm::default().ident() 118 | || hash.params != default_params_string::()); 119 | 120 | #[cfg(feature = "scrypt")] 121 | return Ok(hash.algorithm != scrypt::phc::ALG_ID 122 | || hash.params != default_params_string::()); 123 | 124 | #[cfg(feature = "pbkdf2")] 125 | return Ok(hash.algorithm != *pbkdf2::Algorithm::default().ident() 126 | || hash.params != default_params_string::()); 127 | 128 | Ok(true) 129 | } 130 | 131 | fn default_params_string() -> ParamsString 132 | where 133 | T: Default + TryInto, 134 | { 135 | T::default().try_into().expect("invalid default params") 136 | } 137 | 138 | #[cfg(test)] 139 | mod tests { 140 | use super::{generate_hash, is_hash_obsolete, verify_password}; 141 | 142 | const EXAMPLE_PASSWORD: &str = "password"; 143 | 144 | #[test] 145 | fn happy_path() { 146 | let hash = generate_hash(EXAMPLE_PASSWORD); 147 | assert!(verify_password(EXAMPLE_PASSWORD, &hash).is_ok()); 148 | assert!(verify_password("bogus", &hash).is_err()); 149 | assert!(!is_hash_obsolete(&hash).expect("hash can be parsed")); 150 | } 151 | 152 | #[cfg(feature = "argon2")] 153 | mod argon2 { 154 | use super::{EXAMPLE_PASSWORD, verify_password}; 155 | 156 | /// Argon2 hash for the string "password". 157 | const EXAMPLE_HASH: &str = "$argon2i$v=19$m=65536,t=1,p=1$c29tZXNhbHQAAAAAAAAAAA$+r0d29hqEB0yasKr55ZgICsQGSkl0v0kgwhd+U3wyRo"; 158 | 159 | #[test] 160 | fn verify() { 161 | assert!(verify_password(EXAMPLE_PASSWORD, EXAMPLE_HASH).is_ok()); 162 | assert!(verify_password("bogus", EXAMPLE_HASH).is_err()); 163 | } 164 | } 165 | 166 | #[cfg(feature = "pbkdf2")] 167 | mod pdkdf2 { 168 | use super::verify_password; 169 | 170 | /// PBKDF2 password test vector from the `pbkdf2` crate 171 | const EXAMPLE_PASSWORD: &[u8] = b"passwordPASSWORDpassword"; 172 | 173 | /// PBKDF2 hash test vector from the `pbkdf2` crate 174 | const EXAMPLE_HASH: &str = "$pbkdf2-sha256$i=4096,l=40\ 175 | $c2FsdFNBTFRzYWx0U0FMVHNhbHRTQUxUc2FsdFNBTFRzYWx0\ 176 | $NIyJ28vTKy8y2BS4EW6EzysXNH68GAAYHE4qH7jdU+HGNVGMfaxH6Q"; 177 | 178 | #[test] 179 | fn verify() { 180 | assert!(verify_password(EXAMPLE_PASSWORD, EXAMPLE_HASH).is_ok()); 181 | assert!(verify_password("bogus", EXAMPLE_HASH).is_err()); 182 | } 183 | } 184 | 185 | #[cfg(feature = "scrypt")] 186 | mod scrypt { 187 | use super::{EXAMPLE_PASSWORD, verify_password}; 188 | 189 | /// scrypt hash for the string "password". 190 | const EXAMPLE_HASH: &str = "$scrypt$ln=16,r=8,p=1$aM15713r3Xsvxbi31lqr1Q$nFNh2CVHVjNldFVKDHDlm4CbdRSCdEBsjjJxD+iCs5E"; 191 | 192 | #[test] 193 | fn verify() { 194 | assert!(verify_password(EXAMPLE_PASSWORD, EXAMPLE_HASH).is_ok()); 195 | assert!(verify_password("bogus", EXAMPLE_HASH).is_err()); 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /yescrypt/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![cfg_attr(docsrs, feature(doc_cfg))] 3 | #![doc = include_str!("../README.md")] 4 | #![doc( 5 | html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", 6 | html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg" 7 | )] 8 | #![deny(unsafe_code)] 9 | #![warn( 10 | clippy::cast_lossless, 11 | clippy::cast_possible_truncation, 12 | clippy::cast_possible_wrap, 13 | clippy::cast_precision_loss, 14 | clippy::cast_sign_loss, 15 | clippy::checked_conversions, 16 | clippy::implicit_saturating_sub, 17 | clippy::panic, 18 | clippy::panic_in_result_fn, 19 | clippy::unwrap_used, 20 | missing_docs, 21 | rust_2018_idioms, 22 | unused_lifetimes, 23 | unused_qualifications 24 | )] 25 | 26 | //! # Usage 27 | //! ## Password Hashing 28 | #![cfg_attr(feature = "getrandom", doc = "```")] 29 | #![cfg_attr(not(feature = "getrandom"), doc = "```ignore")] 30 | //! # fn main() -> yescrypt::password_hash::Result<()> { 31 | //! // NOTE: example requires `getrandom` feature is enabled 32 | //! 33 | //! use yescrypt::{Yescrypt, PasswordHasher, PasswordVerifier}; 34 | //! 35 | //! let password = b"pleaseletmein"; // don't actually use this as a password! 36 | //! let password_hash = Yescrypt.hash_password(password)?; 37 | //! assert!(password_hash.as_str().starts_with("$y$")); 38 | //! 39 | //! // verify password is correct for the given hash 40 | //! Yescrypt.verify_password(password, &password_hash)?; 41 | //! # Ok(()) 42 | //! # } 43 | //! ``` 44 | //! 45 | //! ## Key Derivation Function (KDF) 46 | //! ``` 47 | //! # fn main() -> yescrypt::Result<()> { 48 | //! let password = b"pleaseletmein"; // don't actually use this as a password! 49 | //! let salt = b"WZaPV7LSUEKMo34."; // unique per password, ideally 16-bytes and random 50 | //! let params = yescrypt::Params::default(); // use recommended settings 51 | //! 52 | //! let mut output = [0u8; 32]; // can be sized as desired 53 | //! yescrypt::yescrypt(password, salt, ¶ms, &mut output)?; 54 | //! # Ok(()) 55 | //! # } 56 | //! ``` 57 | 58 | // Adapted from the yescrypt reference implementation available at: 59 | // 60 | // 61 | // Relicensed from the BSD-2-Clause license to Apache 2.0+MIT with permission: 62 | // 63 | 64 | extern crate alloc; 65 | 66 | mod error; 67 | mod mode; 68 | mod params; 69 | mod pwxform; 70 | mod salsa20; 71 | mod smix; 72 | mod util; 73 | 74 | #[cfg(feature = "password-hash")] 75 | mod mcf; 76 | 77 | pub use crate::{ 78 | error::{Error, Result}, 79 | mode::Mode, 80 | params::Params, 81 | }; 82 | 83 | #[cfg(feature = "password-hash")] 84 | pub use { 85 | crate::mcf::{PasswordHash, PasswordHashRef, Yescrypt}, 86 | password_hash::{self, CustomizedPasswordHasher, PasswordHasher, PasswordVerifier}, 87 | }; 88 | 89 | use alloc::vec; 90 | use sha2::{Digest, Sha256}; 91 | 92 | /// yescrypt Key Derivation Function (KDF). 93 | /// 94 | /// This is the low-level interface useful for producing cryptographic keys directly. 95 | /// 96 | /// If you are looking for a higher-level interface which can express and store password hashes as 97 | /// strings, please check out the [`Yescrypt`] type. 98 | pub fn yescrypt(passwd: &[u8], salt: &[u8], params: &Params, out: &mut [u8]) -> Result<()> { 99 | let mut passwd = passwd; 100 | let mut dk = [0u8; 32]; 101 | 102 | // Conditionally perform pre-hashing 103 | if params.mode.is_rw() 104 | && params.p >= 1 105 | && params.n / u64::from(params.p) >= 0x100 106 | && params.n / u64::from(params.p) * u64::from(params.r) >= 0x20000 107 | { 108 | let mut prehash_params = *params; 109 | prehash_params.n >>= 6; 110 | prehash_params.t = 0; 111 | yescrypt_body(passwd, salt, &prehash_params, true, &mut dk)?; 112 | 113 | // Use derived key as the "password" for the subsequent step when pre-hashing 114 | passwd = &dk; 115 | } 116 | 117 | yescrypt_body(passwd, salt, params, false, out) 118 | } 119 | 120 | /// Compute yescrypt and write the result into `out`. 121 | fn yescrypt_body( 122 | passwd: &[u8], 123 | salt: &[u8], 124 | params: &Params, 125 | prehash: bool, 126 | out: &mut [u8], 127 | ) -> Result<()> { 128 | let mode = params.mode; 129 | let n = params.n; 130 | let r = params.r; 131 | let p = params.p; 132 | let t = params.t; 133 | 134 | if !((out.len() as u64 <= u64::from(u32::MAX) * 32) 135 | && (u64::from(r) * u64::from(p) < (1 << 30) as u64) 136 | && !(n & (n - 1) != 0 || n <= 1 || r < 1 || p < 1) 137 | && !(u64::from(r) > u64::MAX / 128 / u64::from(p) || n > u64::MAX / 128 / u64::from(r)) 138 | && (n <= u64::MAX / (u64::from(t) + 1))) 139 | { 140 | return Err(Error::Params); 141 | } 142 | 143 | let mut v = vec![0; 32 * (r as usize) * usize::try_from(n)?]; 144 | let mut b = vec![0; 32 * (r as usize) * (p as usize)]; 145 | let mut xy = vec![0; 64 * (r as usize)]; 146 | 147 | let mut passwd = passwd; 148 | let mut sha256 = [0u8; 32]; 149 | let key: &[u8] = if prehash { 150 | b"yescrypt-prehash" 151 | } else { 152 | b"yescrypt" 153 | }; 154 | if !mode.is_classic() { 155 | sha256 = util::hmac_sha256(key, passwd)?; 156 | passwd = &sha256; 157 | } 158 | 159 | // 1: (B_0 ... B_{p-1}) <-- PBKDF2(P, S, 1, p * MFLen) 160 | pbkdf2::pbkdf2_hmac::(passwd, salt, 1, util::cast_slice_mut(&mut b)?); 161 | 162 | if !mode.is_classic() { 163 | sha256.copy_from_slice(util::cast_slice(&b[..8])?); 164 | passwd = &sha256; 165 | } 166 | 167 | if mode.is_rw() { 168 | smix::smix(&mut b, r, n, p, t, mode, &mut v, &mut xy, &mut sha256)?; 169 | passwd = &sha256; 170 | } else { 171 | // 2: for i = 0 to p - 1 do 172 | for i in 0..p { 173 | // 3: B_i <-- MF(B_i, N) 174 | smix::smix( 175 | &mut b[(32 * (r as usize) * (i as usize))..], 176 | r, 177 | n, 178 | 1, 179 | t, 180 | mode, 181 | &mut v, 182 | &mut xy, 183 | &mut [], 184 | )?; 185 | } 186 | } 187 | 188 | let mut dk = [0u8; 32]; 189 | if !mode.is_classic() && out.len() < 32 { 190 | pbkdf2::pbkdf2_hmac::(passwd, util::cast_slice(&b)?, 1, &mut dk); 191 | } 192 | 193 | // 5: DK <-- PBKDF2(P, B, 1, dkLen) 194 | pbkdf2::pbkdf2_hmac::(passwd, util::cast_slice(&b)?, 1, out); 195 | 196 | // Except when computing classic scrypt, allow all computation so far 197 | // to be performed on the client. The final steps below match those of 198 | // SCRAM (RFC 5802), so that an extension of SCRAM (with the steps so 199 | // far in place of SCRAM's use of PBKDF2 and with SHA-256 in place of 200 | // SCRAM's use of SHA-1) would be usable with yescrypt hashes. 201 | if !mode.is_classic() && !prehash { 202 | let dkp = if !mode.is_classic() && out.len() < 32 { 203 | &mut dk 204 | } else { 205 | &mut *out 206 | }; 207 | 208 | // Compute ClientKey 209 | sha256 = util::hmac_sha256(&dkp[..32], b"Client Key")?; 210 | 211 | // Compute StoredKey 212 | let clen = out.len().clamp(0, 32); 213 | dk = Sha256::digest(sha256).into(); 214 | out[..clen].copy_from_slice(&dk[..clen]); 215 | } 216 | 217 | Ok(()) 218 | } 219 | --------------------------------------------------------------------------------