diff --git a/Cargo.lock b/Cargo.lock index c6795d8..ff01b38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -166,6 +166,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "better-panic" version = "0.3.0" @@ -452,6 +458,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -680,6 +687,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "humantime" version = "2.1.0" @@ -984,6 +1000,27 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1165,12 +1202,33 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "password-hash", + "pbkdf2", + "salsa20", + "sha2", +] + [[package]] name = "semver" version = "1.0.23" @@ -1308,6 +1366,7 @@ dependencies = [ "libsodium-sys-stable", "mimalloc", "rstest", + "scrypt", "serde", "serde_json", "sha2", diff --git a/Cargo.toml b/Cargo.toml index abdceed..869407f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ chacha20poly1305 = "0.10.1" glob = "0.3.1" indicatif = "0.17.9" chrono = "0.4.38" +scrypt = "0.11.0" [dev-dependencies] rstest = "0.18.2" diff --git a/README.md b/README.md index b617899..9cf1c01 100755 --- a/README.md +++ b/README.md @@ -175,32 +175,6 @@ CROSS_COMPILE=x86_64-linux-musl- cargo build --target=x86_64-unknown-linux-musl cargo build --target=x86_64-unknown-linux-musl ``` -### For Windows - -In order to get stuff working later, use the `nightly` branch of Rust: - -```sh -rustup override set nightly -``` - -Install the standard Windows target on a Mac (note, that the opposite is currently impossible): - -```sh -rustup target add x86_64-pc-windows-gnu -``` - -Use `homebrew` to install mingw-w64: - -```sh -brew install mingw-w64 -``` - -Now you can build it: - -```sh -cargo build --release --target=x86_64-pc-windows-gnu -``` - ## Examples In this tool, the input provided by the user is first evaluated to determine its format. If the input string begins with `0x`, it is interpreted as a hexadecimal representation of a byte array. The tool will then parse this hexadecimal string into its corresponding byte sequence, allowing for hexadecimal data to be input directly in a recognizable format. Conversely, if the input does not start with `0x`, it is treated as raw data and used as is, without any conversion. This dual functionality enables flexibility, allowing users to input either hexadecimal or raw data based on their needs. diff --git a/build.sh b/build.sh index 89434bf..c38295c 100755 --- a/build.sh +++ b/build.sh @@ -24,6 +24,3 @@ export CXX_x86_64_unknown_linux_musl=x86_64-unknown-linux-musl-g++ export AR_x86_64_unknown_linux_musl=x86_64-unknown-linux-musl-ar export CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER=x86_64-unknown-linux-musl-gcc cargo build --release --target=x86_64-unknown-linux-musl - -echo "Building v${VERSION} for Windows x64..." -cargo build --release --target=x86_64-pc-windows-gnu diff --git a/release.sh b/release.sh index 6c5b3e4..f927790 100755 --- a/release.sh +++ b/release.sh @@ -34,15 +34,6 @@ tar zcvf ${LINUX_X64_RELEASE} target/x86_64-unknown-linux-musl/release/slowkey LINUX_X64_RELEASE_SHA512=$(shasum -a512 ${LINUX_X64_RELEASE}) gpg --output ${LINUX_X64_RELEASE_SIG} --detach-sig ${LINUX_X64_RELEASE} -echo "Creating v${VERSION} bundle for Windows x64..." -WINDOWS_X64_TARGET="slowkey-${VERSION}-windows-amd64.tgz" -WINDOWS_X64_TARGET_SIG=${WINDOWS_X64_TARGET}.sig -WINDOWS_X64_RELEASE="target/${WINDOWS_X64_TARGET}" -WINDOWS_X64_RELEASE_SIG=${WINDOWS_X64_RELEASE}.sig -tar zcvf ${WINDOWS_X64_RELEASE} target/x86_64-pc-windows-gnu/release/slowkey.exe -WINDOWS_X64_RELEASE_SHA512=$(shasum -a512 ${WINDOWS_X64_RELEASE}) -gpg --output ${WINDOWS_X64_RELEASE_SIG} --detach-sig ${WINDOWS_X64_RELEASE} - RELEASE_NOTES="target/release.md" echo "Preparing release notes..." @@ -91,18 +82,4 @@ Verify the digital signature: gpg --verify ${LINUX_X64_TARGET_SIG} ${LINUX_X64_TARGET} \`\`\` -## Windows x64 - -Calculate the SHA512: - -\`\`\`sh -shasum -a512 ${WINDOWS_X64_RELEASE} ${WINDOWS_X64_RELEASE_SHA512} -\`\`\` - -Verify the digital signature: - -\`\`\`sh -gpg --verify ${WINDOWS_X64_TARGET_SIG} ${WINDOWS_X64_TARGET} -\`\`\` - EOF diff --git a/src/main.rs b/src/main.rs index f6e1fe6..549e43a 100755 --- a/src/main.rs +++ b/src/main.rs @@ -92,10 +92,10 @@ enum Commands { #[arg( long, - default_value = SlowKeyOptions::default().scrypt.n.to_string(), - help = format!("Scrypt CPU/memory cost parameter (must be lesser than {})", ScryptOptions::MAX_N) + default_value = SlowKeyOptions::default().scrypt.log_n.to_string(), + help = format!("Scrypt CPU/memory cost parameter (must be lesser than {})", ScryptOptions::MAX_LOG_N) )] - scrypt_n: u64, + scrypt_log_n: u8, #[arg( long, @@ -408,7 +408,7 @@ fn main() { base64, base58, output, - scrypt_n, + scrypt_log_n, scrypt_r, scrypt_p, argon2_m_cost, @@ -466,7 +466,7 @@ fn main() { slowkey_opts = SlowKeyOptions::new( iterations, length, - &ScryptOptions::new(scrypt_n, scrypt_r, scrypt_p), + &ScryptOptions::new(scrypt_log_n, scrypt_r, scrypt_p), &Argon2idOptions::new(argon2_m_cost, argon2_t_cost), ); } diff --git a/src/slowkey.rs b/src/slowkey.rs index 8025917..3ef8c37 100755 --- a/src/slowkey.rs +++ b/src/slowkey.rs @@ -3,6 +3,7 @@ use crate::utils::{ scrypt::{Scrypt, ScryptOptions}, }; use crossterm::style::Stylize; +use scrypt::password_hash::SaltString; use serde::{Deserialize, Serialize}; use sha2::Sha512; use sha3::{Digest, Keccak512}; @@ -20,8 +21,8 @@ impl SlowKeyOptions { pub const MAX_ITERATIONS: usize = u32::MAX as usize; pub const DEFAULT_ITERATIONS: usize = 100; - pub const MIN_KEY_SIZE: usize = 10; - pub const MAX_KEY_SIZE: usize = 128; + pub const MIN_KEY_SIZE: usize = 9; + pub const MAX_KEY_SIZE: usize = 64; pub const DEFAULT_KEY_SIZE: usize = 32; pub fn new(iterations: usize, length: usize, scrypt: &ScryptOptions, argon2id: &Argon2idOptions) -> Self { @@ -59,14 +60,14 @@ impl SlowKeyOptions { pub fn print(&self) { println!( - "{}:\n {}: {}\n {}: {}\n {}: (n: {}, r: {}, p: {})\n {}: (version: {}, m_cost: {}, t_cost: {})\n", + "{}:\n {}: {}\n {}: {}\n {}: (log_n: {}, r: {}, p: {})\n {}: (version: {}, m_cost: {}, t_cost: {})\n", "SlowKey Parameters".yellow(), "Iterations".green(), &self.iterations.to_string().cyan(), "Length".green(), &self.length.to_string().cyan(), "Scrypt".green(), - &self.scrypt.n.to_string().cyan(), + &self.scrypt.log_n.to_string().cyan(), &self.scrypt.r.to_string().cyan(), &self.scrypt.p.to_string().cyan(), "Argon2id".green(), @@ -158,6 +159,8 @@ impl SlowKey { _ => offset_data.to_vec(), }; + let salt_string = SaltString::encode_b64(salt).unwrap(); + for i in offset..self.iterations { let iteration = i as u64; @@ -165,7 +168,7 @@ impl SlowKey { self.double_hash(salt, password, iteration, &mut res); // Calculate the Scrypt hash of the result and the inputs - self.scrypt(salt, password, iteration, &mut res); + self.scrypt(salt, &salt_string, password, iteration, &mut res); // Calculate the SHA2 and SHA3 hashes of the result and the inputs again self.double_hash(salt, password, iteration, &mut res); @@ -205,12 +208,12 @@ impl SlowKey { *res = keccack512.finalize().to_vec(); } - fn scrypt(&self, salt: &[u8], password: &[u8], iteration: u64, res: &mut Vec) { + fn scrypt(&self, salt: &[u8], salt_string: &SaltString, password: &[u8], iteration: u64, res: &mut Vec) { res.extend_from_slice(salt); res.extend_from_slice(password); res.extend_from_slice(&iteration.to_le_bytes()); - *res = self.scrypt.hash(salt, res); + *res = self.scrypt.hash(salt_string, res); } fn argon2id(&self, salt: &[u8], password: &[u8], iteration: u64, res: &mut Vec) { @@ -241,7 +244,7 @@ mod tests { #[case(&SlowKeyOptions { iterations: 10, length: 32, - scrypt: ScryptOptions { n: 1 << 12, r: 8, p: 1 }, + scrypt: ScryptOptions { log_n: 12, r: 8, p: 1 }, argon2id: Argon2idOptions::default() }, b"saltsaltsaltsalt", b"test", &Vec::new(), 0, "6fe4ad1ea824710e75b4a3914c6f3c617c70b3aeb0451639188c253b6f52880e")] @@ -255,66 +258,52 @@ mod tests { #[case(&SlowKeyOptions { iterations: 4, length: 64, - scrypt: ScryptOptions { n: 1 << 20, r: 8, p: 1 }, + scrypt: ScryptOptions { log_n: 20, r: 8, p: 1 }, argon2id: Argon2idOptions::default() }, b"saltsaltsaltsalt", b"test", &Vec::new(), 0, "3ed36a2cb71a043a901cbe237df6976b7a724acadfbc12112c90402548876dd5e76be1da2a1cb57e924a858c36b51c68db13b986e70ddc23254d7fa7a15c2ee0")] - #[case(&SlowKeyOptions { - iterations: 4, - length: 128, - scrypt: ScryptOptions { n: 1 << 20, r: 8, p: 1 }, - argon2id: Argon2idOptions::default() - }, b"saltsaltsaltsalt", b"test", &Vec::new(), 0, - "8e69eb21b3aa9cf0d5b42d18b5a80c8db50908c3baadd9c425d8dfc21ca0f37a503e37a18c5312cf040654f643cc1a5b1801e1f8e86fde355d05a5d2699725b088bf6bf02b0a5888e9198c1876ce82b2664185ff914c853b86b6ead34a351fcfd7124e75bfd643fbdb391025eee3483f30b1f765eae304547a1a1168d0ef448b")] #[case(&SlowKeyOptions { iterations: 4, length: 64, - scrypt: ScryptOptions { n: 1 << 15, r: 8, p: 1 }, + scrypt: ScryptOptions { log_n: 15, r: 8, p: 1 }, argon2id: Argon2idOptions::default() }, b"saltsaltsaltsalt", b"", &Vec::new(), 0, "3af13ebf654ddf60014f4a7f37826f5f60e4defddefffdfc6bf5431e37420c1e308e823bef30a6adb3f862c4b4270aa55e9b0440af7e8ec8d52a3458c1cb3ff4")] #[case(&SlowKeyOptions { iterations: 10, length: 64, - scrypt: ScryptOptions { n: 1 << 15, r: 8, p: 1 }, + scrypt: ScryptOptions { log_n: 15, r: 8, p: 1 }, argon2id: Argon2idOptions::default() }, b"saltsaltsaltsalt", b"test", &Vec::new(), 0, "c2a74fca9621ca13f2ab1a1bdf7cb8e6abe231d7494c280ff40024b1e92f964579d7c77e4b5c32ec438f2932b612f8eae9eeedbba93b0708e1f1b497bcdaed5d")] #[case(&SlowKeyOptions { iterations: 10, length: 64, - scrypt: ScryptOptions { n: 1 << 15, r: 8, p: 1 }, + scrypt: ScryptOptions { log_n: 15, r: 8, p: 1 }, argon2id: Argon2idOptions::default() }, b"saltsaltsaltsal2", b"test", &Vec::new(), 0, "016bbfa52b69c0fc366f9b93b5209d0c9783c018102101eb755f217627541778b13c5db624a105ed6470d7a916e8e5843f952f20bb9f0e9b6053e72176b6158b")] #[case(&SlowKeyOptions { iterations: 10, length: 64, - scrypt: ScryptOptions { n: 1 << 15, r: 8, p: 1 }, + scrypt: ScryptOptions { log_n: 15, r: 8, p: 1 }, argon2id: Argon2idOptions::default() }, b"saltsaltsaltsalt", b"test2", &Vec::new(), 0, "f20e5bf61c9c0ab9208eb1b5a2f3a51a8276dbc5490862f17afbba5ffe539ee95765095aff000d86371ed6ca927efe736008fd048fbde77af56b20331ebde083")] #[case(&SlowKeyOptions { iterations: 10, length: 32, - scrypt: ScryptOptions { n: 1 << 12, r: 8, p: 1 }, + scrypt: ScryptOptions { log_n: 12, r: 8, p: 1 }, argon2id: Argon2idOptions::default() }, b"saltsaltsaltsalt", b"test", &Vec::new(), 1, "dc4ca67e268ac2df2bbaa377afabafda82012b6188d562d67ef57f66f2f592e1")] #[case(&SlowKeyOptions { iterations: 10, length: 64, - scrypt: ScryptOptions { n: 1 << 15, r: 8, p: 1 }, + scrypt: ScryptOptions { log_n: 15, r: 8, p: 1 }, argon2id: Argon2idOptions::default() }, b"saltsaltsaltsalt", b"test", &Vec::new(), 5, "488d73ed1e5c22edfe060d542dc1bc517cdc567aede68fbf87f344fc153b1febbfff6bb52f236a21fa6aaa16e39769248f7eb01c80a48988049a9faee7434f99")] - #[case(&SlowKeyOptions { - iterations: 10, - length: 128, - scrypt: ScryptOptions { n: 1 << 15, r: 8, p: 1 }, - argon2id: Argon2idOptions::default() - }, b"saltsaltsaltsalt", b"test", &Vec::new(), 5, - "0ff28531af487240b664d549ebc2a367df89a2b5d94baed94a53025601b2b2f5ced135415c7cf880b4cc1fe97ea5ba052838caebb8301719d268b7a2d795d75908712910839c8145a70b7ebdf49e2f61a4c1466e89e2e5bd8fb45eb076a72baa60bc803162ee20481b1b85a5985d768908b283e95e52df4466f116ab9014945a")] fn derive_test( #[case] options: &SlowKeyOptions, #[case] salt: &[u8], #[case] password: &[u8], #[case] offset_data: &[u8], diff --git a/src/utils/argon2id.rs b/src/utils/argon2id.rs index 8cc3714..b63cacc 100644 --- a/src/utils/argon2id.rs +++ b/src/utils/argon2id.rs @@ -1,30 +1,22 @@ -use argon2::{ - password_hash::{PasswordHasher, SaltString}, - Algorithm, Argon2 as RustArgon2, Params, Version, -}; +use libsodium_sys::{crypto_pwhash_ALG_ARGON2ID13, crypto_pwhash_argon2id}; use serde::{Deserialize, Serialize}; #[derive(PartialEq, Debug, Clone, Copy, Serialize, Deserialize)] pub struct Argon2idOptions { pub m_cost: u32, pub t_cost: u32, - pub p_cost: u32, } impl Argon2idOptions { - pub const MIN_M_COST: u32 = Params::MIN_M_COST; - pub const MAX_M_COST: u32 = Params::MAX_M_COST; + pub const MIN_M_COST: u32 = 8; + pub const MAX_M_COST: u32 = u32::MAX; pub const DEFAULT_M_COST: u32 = 1 << 21; - pub const MIN_T_COST: u32 = Params::MIN_T_COST; - pub const MAX_T_COST: u32 = Params::MAX_T_COST; + pub const MIN_T_COST: u32 = 2; + pub const MAX_T_COST: u32 = u32::MAX; pub const DEFAULT_T_COST: u32 = 2; - pub const MIN_P_COST: u32 = Params::MIN_P_COST; - pub const MAX_P_COST: u32 = Params::MAX_P_COST; - pub const DEFAULT_P_COST: u32 = 1; - - pub fn new(m_cost: u32, t_cost: u32, p_cost: u32) -> Self { + pub fn new(m_cost: u32, t_cost: u32) -> Self { if m_cost < Self::MIN_M_COST { panic!( "m_cost {} is shorter than the min length of {}", @@ -47,23 +39,7 @@ impl Argon2idOptions { // Note that there is no need to check if t_cost > Self::MAX_T_COST because Self::MAX_T_COST is the maximum // value for this type - if p_cost < Self::MIN_P_COST { - panic!( - "p_cost {} is shorter than the min length of {}", - Self::MIN_P_COST, - p_cost - ); - } - - if p_cost > Self::MAX_P_COST { - panic!( - "p_cost {} is greater than the max length of {}", - Self::MAX_P_COST, - p_cost - ); - } - - Self { m_cost, t_cost, p_cost } + Self { m_cost, t_cost } } } @@ -72,41 +48,51 @@ impl Default for Argon2idOptions { Self { m_cost: Self::DEFAULT_M_COST, t_cost: Self::DEFAULT_T_COST, - p_cost: Self::DEFAULT_P_COST, } } } -pub struct Argon2id<'a> { - argon2: RustArgon2<'a>, +pub struct Argon2id { + length: usize, + opts: Argon2idOptions, } -impl<'a> Argon2id<'a> { - pub const VERSION: Version = Version::V0x13; +impl Argon2id { + pub const VERSION: u8 = 0x13; + const BYTES_IN_KIB: usize = 1024; pub fn new(length: usize, opts: &Argon2idOptions) -> Self { - Self { - argon2: RustArgon2::new( - Algorithm::Argon2id, - Argon2id::VERSION, - Params::new(opts.m_cost, opts.t_cost, opts.p_cost, Some(length)).unwrap(), - ), - } + Self { length, opts: *opts } } - pub fn hash(&self, salt: &SaltString, password: &[u8]) -> Vec { - let res = self.argon2.hash_password(password, salt).unwrap(); + pub fn hash(&self, salt: &[u8], password: &[u8]) -> Vec { + let mut dk = vec![0; self.length]; + + unsafe { + let ret = crypto_pwhash_argon2id( + dk.as_mut_ptr(), + dk.len() as u64, + password.as_ptr() as *const _, + password.len() as u64, + salt.as_ptr(), + self.opts.t_cost as u64, + (self.opts.m_cost as usize) * Self::BYTES_IN_KIB, + crypto_pwhash_ALG_ARGON2ID13 as i32, + ); - match res.hash { - Some(output) => output.as_bytes().to_vec(), - None => panic!("hash_password failed"), + if ret != 0 { + panic!("crypto_pwhash_argon2id failed with: {ret}"); + } } + + dk.to_vec() } } #[cfg(test)] mod tests { use super::*; + use crate::utils::sodium_init::initialize; use rstest::rstest; #[rstest] @@ -114,22 +100,21 @@ mod tests { #[case(b"saltsaltsaltsalt", b"test", 64, &Argon2idOptions::default(), "b545c20926e06955c505deb14c01ca8126fb9e167470393797bc3627e46a232f9e16186a26e197eb58f6c4ec2e3897f366b32846040eb5715be6427a8f04233c")] #[case(b"saltsaltsaltsalt", b"test", 32, &Argon2idOptions::default(), "a9db18ddae667012b832af543436a77de985cd51de591e2b3916a73198ce5940")] #[case(b"saltsaltsaltsalt", b"test", 16, &Argon2idOptions::default(), "2b5ccde09d4c345912874cb0a4b15b54")] - #[case(b"saltsaltsaltsalt", b"test", 64, &Argon2idOptions { m_cost: Argon2idOptions::DEFAULT_M_COST / 2, t_cost: Argon2idOptions::DEFAULT_T_COST, p_cost: Argon2idOptions::DEFAULT_P_COST }, "3aab062d5ba93d7da6573746f19d85c6abaa735aeac5c13c12358f1a9d16d9e87e984e245b41613079e76096062aefbccc5bc36fe6d9626b08ccbe545c7fa357")] - #[case(b"saltsaltsaltsalt", b"test", 32, &Argon2idOptions { m_cost: Argon2idOptions::DEFAULT_M_COST / 2, t_cost: Argon2idOptions::DEFAULT_T_COST, p_cost: Argon2idOptions::DEFAULT_P_COST }, "9224d42695a83bb30ef48faf18751c5e53f5f97a084d0fe7409b19a954ccedbe")] - #[case(b"saltsaltsaltsalt", b"test", 16, &Argon2idOptions { m_cost: Argon2idOptions::DEFAULT_M_COST / 2, t_cost: Argon2idOptions::DEFAULT_T_COST, p_cost: Argon2idOptions::DEFAULT_P_COST }, "844320f4c7cfce471b902a3d4848b79c")] - #[case(b"saltsaltsaltsalt", b"test", 64, &Argon2idOptions { m_cost: Argon2idOptions::DEFAULT_M_COST / 2, t_cost: Argon2idOptions::DEFAULT_T_COST * 2, p_cost: Argon2idOptions::DEFAULT_P_COST}, "57033d89807061a02b859b28fa7428ef4547db408ce4c73e7bc31a4bab3359f6413a87d861eb9a43015e141d9a88866d225035e04ea1fb771c64505b5fe8660c")] - #[case(b"saltsaltsaltsalt", b"test", 32, &Argon2idOptions { m_cost: Argon2idOptions::DEFAULT_M_COST / 2, t_cost: Argon2idOptions::DEFAULT_T_COST * 2, p_cost: Argon2idOptions::DEFAULT_P_COST}, "3e0d26c8b6f9c8c001e25595195d98c0e5658756dfc9c88fc5375c2295fb03bd")] - #[case(b"saltsaltsaltsalt", b"test", 16, &Argon2idOptions { m_cost: Argon2idOptions::DEFAULT_M_COST / 2, t_cost: Argon2idOptions::DEFAULT_T_COST * 2, p_cost: Argon2idOptions::DEFAULT_P_COST}, "c2f48dba626afcbfe5283c3a3cba9c60")] - #[case(b"saltsaltsaltsalt", b"test", 64, &Argon2idOptions { m_cost: Argon2idOptions::DEFAULT_M_COST / 2, t_cost: Argon2idOptions::DEFAULT_T_COST * 2, p_cost: Argon2idOptions::DEFAULT_P_COST * 2}, "ed114b4c0a2a7a4e9244b262bcf61ee48cf509093498ccb3b1596a3c365fd568f9fb0409d454b67bd70c518e4d1bf4ce9af819ce8b23b1c75d87294cb89c2410")] - #[case(b"saltsaltsaltsalt", b"test", 32, &Argon2idOptions { m_cost: Argon2idOptions::DEFAULT_M_COST / 2, t_cost: Argon2idOptions::DEFAULT_T_COST * 2, p_cost: Argon2idOptions::DEFAULT_P_COST * 2}, "d1b91a999cd989aefe64ff99a73f1cb9ea49bbc0590e5001e755b2f32f472171")] - #[case(b"saltsaltsaltsalt", b"test", 16, &Argon2idOptions { m_cost: Argon2idOptions::DEFAULT_M_COST / 2, t_cost: Argon2idOptions::DEFAULT_T_COST * 2, p_cost: Argon2idOptions::DEFAULT_P_COST* 2}, "05b1bc2ff5a8ca60d93904dcd578dad6")] + #[case(b"saltsaltsaltsalt", b"test", 64, &Argon2idOptions { m_cost: Argon2idOptions::DEFAULT_M_COST / 2, t_cost: Argon2idOptions::DEFAULT_T_COST }, "3aab062d5ba93d7da6573746f19d85c6abaa735aeac5c13c12358f1a9d16d9e87e984e245b41613079e76096062aefbccc5bc36fe6d9626b08ccbe545c7fa357")] + #[case(b"saltsaltsaltsalt", b"test", 32, &Argon2idOptions { m_cost: Argon2idOptions::DEFAULT_M_COST / 2, t_cost: Argon2idOptions::DEFAULT_T_COST }, "9224d42695a83bb30ef48faf18751c5e53f5f97a084d0fe7409b19a954ccedbe")] + #[case(b"saltsaltsaltsalt", b"test", 16, &Argon2idOptions { m_cost: Argon2idOptions::DEFAULT_M_COST / 2, t_cost: Argon2idOptions::DEFAULT_T_COST }, "844320f4c7cfce471b902a3d4848b79c")] + #[case(b"saltsaltsaltsalt", b"test", 64, &Argon2idOptions { m_cost: Argon2idOptions::DEFAULT_M_COST / 2, t_cost: Argon2idOptions::DEFAULT_T_COST * 2}, "57033d89807061a02b859b28fa7428ef4547db408ce4c73e7bc31a4bab3359f6413a87d861eb9a43015e141d9a88866d225035e04ea1fb771c64505b5fe8660c")] + #[case(b"saltsaltsaltsalt", b"test", 32, &Argon2idOptions { m_cost: Argon2idOptions::DEFAULT_M_COST / 2, t_cost: Argon2idOptions::DEFAULT_T_COST * 2}, "3e0d26c8b6f9c8c001e25595195d98c0e5658756dfc9c88fc5375c2295fb03bd")] + #[case(b"saltsaltsaltsalt", b"test", 16, &Argon2idOptions { m_cost: Argon2idOptions::DEFAULT_M_COST / 2, t_cost: Argon2idOptions::DEFAULT_T_COST * 2}, "c2f48dba626afcbfe5283c3a3cba9c60")] fn argon2_test( #[case] salt: &[u8], #[case] password: &[u8], #[case] length: usize, #[case] opts: &Argon2idOptions, #[case] expected: &str, ) { + initialize(); + let argon2 = Argon2id::new(length, opts); - let key = argon2.hash(&SaltString::encode_b64(salt).unwrap(), password); + let key = argon2.hash(salt, password); assert_eq!(hex::encode(key), expected); } diff --git a/src/utils/checkpoints/checkpoint.rs b/src/utils/checkpoints/checkpoint.rs index b8e7094..210ce67 100644 --- a/src/utils/checkpoints/checkpoint.rs +++ b/src/utils/checkpoints/checkpoint.rs @@ -136,13 +136,13 @@ impl CheckpointData { if display.options { output = format!( - "{}\n {}:\n {}: {}\n {}: (n: {}, r: {}, p: {})\n {}: (version: {}, m_cost: {}, t_cost: {})\n", + "{}\n {}:\n {}: {}\n {}: (log_n: {}, r: {}, p: {})\n {}: (version: {}, m_cost: {}, t_cost: {})\n", output, "SlowKey Parameters".yellow(), "Length".green(), &self.data.slowkey.length.to_string().cyan(), "Scrypt".green(), - &self.data.slowkey.scrypt.n.to_string().cyan(), + &self.data.slowkey.scrypt.log_n.to_string().cyan(), &self.data.slowkey.scrypt.r.to_string().cyan(), &self.data.slowkey.scrypt.p.to_string().cyan(), "Argon2id".green(), diff --git a/src/utils/mod.rs b/src/utils/mod.rs index f726e54..c118187 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -3,3 +3,4 @@ pub mod chacha20poly1305; pub mod checkpoints; pub mod outputs; pub mod scrypt; +pub mod sodium_init; diff --git a/src/utils/scrypt.rs b/src/utils/scrypt.rs index c4f40f1..a023b54 100644 --- a/src/utils/scrypt.rs +++ b/src/utils/scrypt.rs @@ -1,16 +1,20 @@ -use libsodium_sys::crypto_pwhash_scryptsalsa208sha256_ll; use serde::{Deserialize, Serialize}; +use scrypt::{ + password_hash::{PasswordHasher, SaltString}, + Params, Scrypt as RustScrypt, +}; + #[derive(PartialEq, Debug, Clone, Copy, Serialize, Deserialize)] pub struct ScryptOptions { - pub n: u64, + pub log_n: u8, pub r: u32, pub p: u32, } impl ScryptOptions { - pub const MAX_N: u64 = u64::MAX; - pub const DEFAULT_N: u64 = 1 << 20; + pub const MAX_LOG_N: u8 = usize::BITS as u8; + pub const DEFAULT_LOG_N: u8 = 20; pub const MIN_R: u32 = 0; pub const MAX_R: u32 = u32::MAX; @@ -20,18 +24,18 @@ impl ScryptOptions { pub const MAX_P: u32 = u32::MAX; pub const DEFAULT_P: u32 = 1; - pub fn new(n: u64, r: u32, p: u32) -> Self { + pub fn new(log_n: u8, r: u32, p: u32) -> Self { // Note that there is no need to check if either n, r or p are in bounds, since both are bound by the maximum // and the minimum values for this type - Self { n, r, p } + Self { log_n, r, p } } } impl Default for ScryptOptions { fn default() -> Self { Self { - n: Self::DEFAULT_N, + log_n: Self::DEFAULT_LOG_N, r: Self::DEFAULT_R, p: Self::DEFAULT_P, } @@ -48,28 +52,21 @@ impl Scrypt { Self { length, opts: *opts } } - pub fn hash(&self, salt: &[u8], password: &[u8]) -> Vec { - let mut dk = vec![0; self.length]; - - unsafe { - let ret = crypto_pwhash_scryptsalsa208sha256_ll( - password.as_ptr(), - password.len(), - salt.as_ptr(), - salt.len(), - self.opts.n, - self.opts.r, - self.opts.p, - dk.as_mut_ptr(), - dk.len(), - ); - - if ret != 0 { - panic!("crypto_pwhash_scryptsalsa208sha256_ll failed with: {ret}"); - } + pub fn hash(&self, salt: &SaltString, password: &[u8]) -> Vec { + let res = RustScrypt + .hash_password_customized( + password, + None, + None, + Params::new(self.opts.log_n, self.opts.r, self.opts.p, self.length).unwrap(), + salt, + ) + .unwrap(); + + match res.hash { + Some(output) => output.as_bytes().to_vec(), + None => panic!("AAAA"), } - - dk.to_vec() } } @@ -80,17 +77,16 @@ mod tests { use rstest::rstest; #[rstest] - #[case(&Vec::new(), &Vec::new(), 64, &ScryptOptions::default(), "d436cba148427322d47a09a84b9bbb64d5ff086545170518711f3ec6936124e0383b3f47409e0329776231b295df5038ab07b096b8717718fd6f092195bfb03a")] - #[case(b"salt", b"", 64, &ScryptOptions { n: 1 << 15, r: 8, p: 1 }, "6e6d0720a5766a2f99679af8dbf78794d8cfe4c2b658ec82a1d005c0d54582846583ccf105fa66271ad7907868b4e3f5bb61f12b427fe0dd2c75df55afce74c1")] + #[case(b"salt", b"", 64, &ScryptOptions { log_n: 15, r: 8, p: 1 }, "6e6d0720a5766a2f99679af8dbf78794d8cfe4c2b658ec82a1d005c0d54582846583ccf105fa66271ad7907868b4e3f5bb61f12b427fe0dd2c75df55afce74c1")] #[case(b"salt", b"test", 64, &ScryptOptions::default(), "c91328bf58e9904c6c3aa15b26178b7ff03caf4eab382e3b9e1a335fb487c775b64ff03b82391a33b655047a632391b6216b98b2595cd82e89eaa1d9c8c2ccf5")] #[case(b"salt", b"test", 32, &ScryptOptions::default(), "c91328bf58e9904c6c3aa15b26178b7ff03caf4eab382e3b9e1a335fb487c775")] #[case(b"salt", b"test", 16, &ScryptOptions::default(), "c91328bf58e9904c6c3aa15b26178b7f")] - #[case(b"salt", b"test", 64, &ScryptOptions { n: 1 << 12, r: 8, p: 2 }, "3ed57e6edeae5e46f2932b6d22e0a73e47ff22c66d3acab5f0488cda26297425693b2d5cbd463c3521c8132056fb801997b915a9f8d051948a430142c7aa5855")] - #[case(b"salt", b"test", 32, &ScryptOptions { n: 1 << 12, r: 8, p: 2 }, "3ed57e6edeae5e46f2932b6d22e0a73e47ff22c66d3acab5f0488cda26297425")] - #[case(b"salt", b"test", 16, &ScryptOptions { n: 1 << 12, r: 8, p: 2 }, "3ed57e6edeae5e46f2932b6d22e0a73e")] - #[case(b"salt", b"test", 64, &ScryptOptions { n: 1 << 12, r: 16, p: 1 }, "107a4e74f205207f82c8fd0f8a4a5fbe3a485fb9509e1b839d9cb98d63649354a0d56eaad6340f2c1e92dd25a6883b51f9806b6c7980c60c1b290b96dbceec45")] - #[case(b"salt", b"test", 32, &ScryptOptions { n: 1 << 12, r: 16, p: 1 }, "107a4e74f205207f82c8fd0f8a4a5fbe3a485fb9509e1b839d9cb98d63649354")] - #[case(b"salt", b"test", 16, &ScryptOptions { n: 1 << 12, r: 16, p: 1 }, "107a4e74f205207f82c8fd0f8a4a5fbe")] + #[case(b"salt", b"test", 64, &ScryptOptions { log_n: 12, r: 8, p: 2 }, "3ed57e6edeae5e46f2932b6d22e0a73e47ff22c66d3acab5f0488cda26297425693b2d5cbd463c3521c8132056fb801997b915a9f8d051948a430142c7aa5855")] + #[case(b"salt", b"test", 32, &ScryptOptions { log_n: 12, r: 8, p: 2 }, "3ed57e6edeae5e46f2932b6d22e0a73e47ff22c66d3acab5f0488cda26297425")] + #[case(b"salt", b"test", 16, &ScryptOptions { log_n: 12, r: 8, p: 2 }, "3ed57e6edeae5e46f2932b6d22e0a73e")] + #[case(b"salt", b"test", 64, &ScryptOptions { log_n: 12, r: 16, p: 1 }, "107a4e74f205207f82c8fd0f8a4a5fbe3a485fb9509e1b839d9cb98d63649354a0d56eaad6340f2c1e92dd25a6883b51f9806b6c7980c60c1b290b96dbceec45")] + #[case(b"salt", b"test", 32, &ScryptOptions { log_n: 12, r: 16, p: 1 }, "107a4e74f205207f82c8fd0f8a4a5fbe3a485fb9509e1b839d9cb98d63649354")] + #[case(b"salt", b"test", 16, &ScryptOptions { log_n: 12, r: 16, p: 1 }, "107a4e74f205207f82c8fd0f8a4a5fbe")] fn scrypt_test( #[case] salt: &[u8], #[case] password: &[u8], #[case] length: usize, #[case] opts: &ScryptOptions, @@ -99,7 +95,7 @@ mod tests { initialize(); let scrypt = Scrypt::new(length, opts); - let key = scrypt.hash(salt, password); + let key = scrypt.hash(&SaltString::encode_b64(salt).unwrap(), password); assert_eq!(hex::encode(key), expected); } diff --git a/src/utils/sodium_init.rs b/src/utils/sodium_init.rs new file mode 100644 index 0000000..45e9ed1 --- /dev/null +++ b/src/utils/sodium_init.rs @@ -0,0 +1,13 @@ +use libsodium_sys::sodium_init; +use std::sync::Once; + +static INIT: Once = Once::new(); + +pub fn initialize() { + INIT.call_once(|| unsafe { + let res = sodium_init(); + if res != 0 { + panic!("sodium_init failed with: {res}"); + } + }); +}