Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use argon2id by default #2377

Merged
merged 1 commit into from
May 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions hash/src/argon2kdf.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use argon2::Config;
pub use argon2::Error as Argon2Error;
pub use argon2::{Error as Argon2Error, Variant as Argon2Variant};

// Taken from https://github.com/nimiq/core-js/blob/c98d56b2dd967d9a9c9a97fe4c54bfaac743aa0c/src/main/generic/utils/crypto/CryptoWorkerImpl.js#L146
const MEMORY_COST: u32 = 512;
Expand All @@ -9,12 +9,13 @@ pub fn compute_argon2_kdf(
salt: &[u8],
iterations: u32,
derived_key_length: usize,
variant: Argon2Variant,
) -> Result<Vec<u8>, Argon2Error> {
let config = Config {
time_cost: iterations,
hash_length: derived_key_length as u32,
mem_cost: MEMORY_COST,
variant: argon2::Variant::Argon2d,
variant,
..Default::default()
};

Expand Down
8 changes: 7 additions & 1 deletion hash/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,13 @@ fn it_can_compute_argon2_kdf() {
let password = "test";
let salt = "nimiqrocks!";

let res = argon2kdf::compute_argon2_kdf(password.as_bytes(), salt.as_bytes(), 1, 32);
let res = argon2kdf::compute_argon2_kdf(
password.as_bytes(),
salt.as_bytes(),
1,
32,
argon2::Variant::Argon2d,
);
assert_eq!(
res.unwrap(),
hex::decode("8c259fdcc2ad6799df728c11e895a3369e9dbae6a3166ebc3b353399fc565524").unwrap()
Expand Down
67 changes: 60 additions & 7 deletions utils/src/otp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::{

use clear_on_drop::clear::Clear;
use nimiq_database_value::{FromDatabaseValue, IntoDatabaseValue};
use nimiq_hash::argon2kdf::{compute_argon2_kdf, Argon2Error};
use nimiq_hash::argon2kdf::{compute_argon2_kdf, Argon2Error, Argon2Variant};
use nimiq_serde::{Deserialize, Serialize};
use rand::{rngs::OsRng, RngCore};

Expand Down Expand Up @@ -81,8 +81,9 @@ impl<T: Clear + Deserialize + Serialize> Unlocked<T> {
password: &[u8],
iterations: u32,
salt_length: usize,
algorithm: Algorithm,
) -> Result<Self, Argon2Error> {
let locked = Locked::create(&secret, password, iterations, salt_length)?;
let locked = Locked::create(&secret, password, iterations, salt_length, algorithm)?;
Ok(Unlocked {
data: ClearOnDrop::new(secret),
lock: locked,
Expand All @@ -96,6 +97,7 @@ impl<T: Clear + Deserialize + Serialize> Unlocked<T> {
password,
OtpLock::<T>::DEFAULT_ITERATIONS,
OtpLock::<T>::DEFAULT_SALT_LENGTH,
Algorithm::default(),
)
}

Expand Down Expand Up @@ -130,12 +132,43 @@ impl<T: Clear + Deserialize + Serialize> Deref for Unlocked<T> {
}
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Algorithm {
Argon2d = 0,

/// With side-channel protection.
Argon2id = 2,
}

impl Algorithm {
pub fn backwards_compatible_default() -> Algorithm {
Self::Argon2d
}
}

impl From<Algorithm> for Argon2Variant {
fn from(value: Algorithm) -> Self {
match value {
Algorithm::Argon2d => Argon2Variant::Argon2d,
Algorithm::Argon2id => Argon2Variant::Argon2id,
}
}
}

impl Default for Algorithm {
fn default() -> Self {
Self::Argon2id
}
}

// Locked container
#[derive(Serialize, Deserialize)]
pub struct Locked<T: Clear + Deserialize + Serialize> {
lock: Vec<u8>,
salt: Vec<u8>,
iterations: u32,
#[serde(default = "Algorithm::backwards_compatible_default")]
algorithm: Algorithm,
phantom: PhantomData<T>,
}

Expand All @@ -146,8 +179,9 @@ impl<T: Clear + Deserialize + Serialize> Locked<T> {
password: &[u8],
iterations: u32,
salt_length: usize,
algorithm: Algorithm,
) -> Result<Self, Argon2Error> {
let result = Locked::create(&secret, password, iterations, salt_length)?;
let result = Locked::create(&secret, password, iterations, salt_length, algorithm)?;

// Remove secret from memory.
secret.clear();
Expand All @@ -162,13 +196,21 @@ impl<T: Clear + Deserialize + Serialize> Locked<T> {
password,
OtpLock::<T>::DEFAULT_ITERATIONS,
OtpLock::<T>::DEFAULT_SALT_LENGTH,
Algorithm::default(),
)
}

/// Calling code should make sure to clear the password from memory after use.
/// The integrity of the output value is not checked.
pub fn unlock_unchecked(self, password: &[u8]) -> Result<Unlocked<T>, Locked<T>> {
let key_opt = Self::otp(&self.lock, password, self.iterations, &self.salt).ok();
let key_opt = Self::otp(
&self.lock,
password,
self.iterations,
&self.salt,
self.algorithm,
)
.ok();
let mut key = if let Some(key_content) = key_opt {
key_content
} else {
Expand Down Expand Up @@ -197,8 +239,10 @@ impl<T: Clear + Deserialize + Serialize> Locked<T> {
password: &[u8],
iterations: u32,
salt: &[u8],
algorithm: Algorithm,
) -> Result<Vec<u8>, Argon2Error> {
let mut key = compute_argon2_kdf(password, salt, iterations, secret.len())?;
let mut key =
compute_argon2_kdf(password, salt, iterations, secret.len(), algorithm.into())?;
assert_eq!(key.len(), secret.len());

for (key_byte, secret_byte) in key.iter_mut().zip(secret.iter()) {
Expand All @@ -213,9 +257,10 @@ impl<T: Clear + Deserialize + Serialize> Locked<T> {
password: &[u8],
iterations: u32,
salt: Vec<u8>,
algorithm: Algorithm,
) -> Result<Self, Argon2Error> {
let mut data = secret.serialize_to_vec();
let lock = Self::otp(&data, password, iterations, &salt)?;
let lock = Self::otp(&data, password, iterations, &salt, algorithm)?;

// Always overwrite unencrypted vector.
for byte in data.iter_mut() {
Expand All @@ -226,6 +271,7 @@ impl<T: Clear + Deserialize + Serialize> Locked<T> {
lock,
salt,
iterations,
algorithm,
phantom: PhantomData,
})
}
Expand All @@ -235,10 +281,11 @@ impl<T: Clear + Deserialize + Serialize> Locked<T> {
password: &[u8],
iterations: u32,
salt_length: usize,
algorithm: Algorithm,
) -> Result<Self, Argon2Error> {
let mut salt = vec![0; salt_length];
OsRng.fill_bytes(salt.as_mut_slice());
Self::lock(secret, password, iterations, salt)
Self::lock(secret, password, iterations, salt, algorithm)
}

pub fn into_otp_lock(self) -> OtpLock<T> {
Expand Down Expand Up @@ -301,12 +348,14 @@ impl<T: Clear + Deserialize + Serialize> OtpLock<T> {
password: &[u8],
iterations: u32,
salt_length: usize,
algorithm: Algorithm,
) -> Result<Self, Argon2Error> {
Ok(OtpLock::Unlocked(Unlocked::new(
secret,
password,
iterations,
salt_length,
algorithm,
)?))
}

Expand All @@ -317,6 +366,7 @@ impl<T: Clear + Deserialize + Serialize> OtpLock<T> {
password,
Self::DEFAULT_ITERATIONS,
Self::DEFAULT_SALT_LENGTH,
Algorithm::default(),
)
}

Expand All @@ -326,12 +376,14 @@ impl<T: Clear + Deserialize + Serialize> OtpLock<T> {
password: &[u8],
iterations: u32,
salt_length: usize,
algorithm: Algorithm,
) -> Result<Self, Argon2Error> {
Ok(OtpLock::Locked(Locked::new(
secret,
password,
iterations,
salt_length,
algorithm,
)?))
}

Expand All @@ -342,6 +394,7 @@ impl<T: Clear + Deserialize + Serialize> OtpLock<T> {
password,
Self::DEFAULT_ITERATIONS,
Self::DEFAULT_SALT_LENGTH,
Algorithm::default(),
)
}

Expand Down