Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions mm2src/crypto/src/encrypt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use hmac::{Hmac, Mac};
use mm2_err_handle::prelude::*;
use sha2::Sha256;

const ENCRYPTED_DATA_VERSION: u8 = 1;

type Aes256CbcEnc = cbc::Encryptor<Aes256>;

#[derive(Debug, Display, PartialEq)]
Expand Down Expand Up @@ -41,6 +43,10 @@ pub enum EncryptionAlgorithm {
/// providing a robust and comprehensive approach to securing sensitive mnemonic data.
#[derive(Serialize, Deserialize, Debug)]
pub struct EncryptedData {
/// Version of the encrypted data format.
/// This version value allows future changes to this struct while maintaining backward compatibility.
pub version: u8,

/// The encryption algorithm used to encrypt the mnemonic.
/// Example: "AES-256-CBC".
pub encryption_algorithm: EncryptionAlgorithm,
Expand Down Expand Up @@ -107,6 +113,7 @@ pub fn encrypt_data(
let tag = mac.finalize().into_bytes();

let encrypted_data = EncryptedData {
version: ENCRYPTED_DATA_VERSION,
Comment thread
shamardy marked this conversation as resolved.
encryption_algorithm: EncryptionAlgorithm::AES256CBC,
key_derivation_details,
iv: STANDARD.encode(iv),
Expand Down
97 changes: 65 additions & 32 deletions mm2src/crypto/src/key_derivation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ use hmac::{Hmac, Mac};
use mm2_err_handle::mm_error::MmResult;
use mm2_err_handle::prelude::*;
use sha2::Sha512;
use std::convert::TryInto;
use std::convert::{TryFrom, TryInto};

const ARGON2_ALGORITHM: &str = "Argon2id";
const ARGON2ID_VERSION: &str = "0x13";
const ARGON2_ALGORITHM: &str = "argon2id";
const ARGON2ID_VERSION: u32 = 19;
const ARGON2ID_M_COST: u32 = 65536;
const ARGON2ID_T_COST: u32 = 2;
const ARGON2ID_P_COST: u32 = 1;
const ARGON2ID_OUTPUT_LEN: usize = 32;
Comment thread
shamardy marked this conversation as resolved.

#[allow(dead_code)]
type HmacSha512 = Hmac<Sha512>;
Expand All @@ -25,6 +26,8 @@ pub enum KeyDerivationError {
HmacInitialization,
#[display(fmt = "Invalid key length")]
InvalidKeyLength,
#[display(fmt = "Not supported: {}", _0)]
NotSupported(String),
}

impl From<argon2::password_hash::Error> for KeyDerivationError {
Expand All @@ -41,8 +44,8 @@ pub struct Argon2Params {
/// The specific variant of the Argon2 algorithm used (e.g., Argon2id).
algorithm: String,

/// The version of the Argon2 algorithm (e.g., 0x13 for the latest version).
version: String,
/// The version of the Argon2 algorithm (e.g., 0x13 19 in u32) for the latest version).
version: u32,

/// The memory cost parameter defining the memory usage of the algorithm.
/// Expressed in kibibytes (KiB).
Expand All @@ -54,16 +57,20 @@ pub struct Argon2Params {

/// The parallelism cost parameter defining the number of parallel threads.
p_cost: u32,

/// The size of the output hash in bytes.
output_len: usize,
}

impl Default for Argon2Params {
fn default() -> Self {
Argon2Params {
algorithm: ARGON2_ALGORITHM.to_string(),
version: ARGON2ID_VERSION.to_string(),
version: ARGON2ID_VERSION,
m_cost: ARGON2ID_M_COST,
t_cost: ARGON2ID_T_COST,
p_cost: ARGON2ID_P_COST,
output_len: ARGON2ID_OUTPUT_LEN,
}
}
}
Expand Down Expand Up @@ -95,40 +102,66 @@ pub enum KeyDerivationDetails {
// Placeholder for future algorithms.
}

fn build_argon2_instance(params: &Argon2Params) -> Result<Argon2<'_>, KeyDerivationError> {
let argon2_params = argon2::Params::new(params.m_cost, params.t_cost, params.p_cost, Some(params.output_len))
.map_err(|e| KeyDerivationError::PasswordHashingFailed(format!("Invalid Argon2 parameters: {}", e)))?;

let algorithm = argon2::Algorithm::new(&params.algorithm)
.map_err(|e| KeyDerivationError::PasswordHashingFailed(format!("Unknown Argon2 algorithm: {}", e)))?;

let version = argon2::Version::try_from(params.version)
.map_err(|e| KeyDerivationError::PasswordHashingFailed(format!("Unknown Argon2 version: {}", e)))?;

Ok(Argon2::new(algorithm, version, argon2_params))
}

/// Derives AES and HMAC keys from a given password and salts for mnemonic encryption/decryption.
///
/// # Returns
/// A tuple containing the AES key and HMAC key as byte arrays, or a `MnemonicError` in case of failure.
#[allow(dead_code)]
pub(crate) fn derive_keys_for_mnemonic(
password: &str,
salt_aes: &SaltString,
salt_hmac: &SaltString,
key_details: &KeyDerivationDetails,
) -> MmResult<([u8; 32], [u8; 32]), KeyDerivationError> {
let argon2 = Argon2::default();

// Derive AES Key
let aes_password_hash = argon2.hash_password(password.as_bytes(), salt_aes)?;
let key_aes_output = aes_password_hash
.serialize()
.hash()
.ok_or_else(|| KeyDerivationError::PasswordHashingFailed("Error finding AES key hashing output".to_string()))?;
let key_aes = key_aes_output
.as_bytes()
.try_into()
.map_err(|_| KeyDerivationError::PasswordHashingFailed("Invalid AES key length".to_string()))?;

// Derive HMAC Key
let hmac_password_hash = argon2.hash_password(password.as_bytes(), salt_hmac)?;
let key_hmac_output = hmac_password_hash.serialize().hash().ok_or_else(|| {
KeyDerivationError::PasswordHashingFailed("Error finding HMAC key hashing output".to_string())
})?;
let key_hmac = key_hmac_output
.as_bytes()
.try_into()
.map_err(|_| KeyDerivationError::PasswordHashingFailed("Invalid HMAC key length".to_string()))?;
match key_details {
KeyDerivationDetails::Argon2 {
params,
salt_aes,
salt_hmac,
} => {
let argon2 = build_argon2_instance(params)?;

let salt_aes = SaltString::from_b64(salt_aes)
.map_err(|e| KeyDerivationError::PasswordHashingFailed(format!("Invalid AES salt: {}", e)))?;
let salt_hmac = SaltString::from_b64(salt_hmac)
.map_err(|e| KeyDerivationError::PasswordHashingFailed(format!("Invalid HMAC salt: {}", e)))?;

Ok((key_aes, key_hmac))
// Derive AES Key
let aes_password_hash = argon2.hash_password(password.as_bytes(), &salt_aes)?;
let key_aes_output = aes_password_hash.serialize().hash().ok_or_else(|| {
KeyDerivationError::PasswordHashingFailed("Error finding AES key hashing output".to_string())
})?;
let key_aes = key_aes_output
.as_bytes()
.try_into()
.map_err(|_| KeyDerivationError::PasswordHashingFailed("Invalid AES key length".to_string()))?;

// Derive HMAC Key
let hmac_password_hash = argon2.hash_password(password.as_bytes(), &salt_hmac)?;
let key_hmac_output = hmac_password_hash.serialize().hash().ok_or_else(|| {
KeyDerivationError::PasswordHashingFailed("Error finding HMAC key hashing output".to_string())
})?;
let key_hmac = key_hmac_output
.as_bytes()
.try_into()
.map_err(|_| KeyDerivationError::PasswordHashingFailed("Invalid HMAC key length".to_string()))?;

Ok((key_aes, key_hmac))
},
KeyDerivationDetails::SLIP0021 { .. } => MmError::err(KeyDerivationError::NotSupported(
"SLIP-0021 key derivation is not supported for mnemonic keys".to_string(),
)),
}
}

/// Splits a path into its components and derives a key for each component.
Expand Down
20 changes: 2 additions & 18 deletions mm2src/crypto/src/mnemonic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@ impl From<bip39::Error> for MnemonicError {
fn from(e: bip39::Error) -> Self { MnemonicError::BIP39Error(e.to_string()) }
}

impl From<argon2::password_hash::Error> for MnemonicError {
fn from(e: argon2::password_hash::Error) -> Self { MnemonicError::KeyDerivationError(e.to_string()) }
}

impl From<KeyDerivationError> for MnemonicError {
fn from(e: KeyDerivationError) -> Self { MnemonicError::KeyDerivationError(e.to_string()) }
}
Expand Down Expand Up @@ -84,7 +80,7 @@ pub fn encrypt_mnemonic(mnemonic: &str, password: &str) -> MmResult<EncryptedDat
};

// Derive AES and HMAC keys
let (key_aes, key_hmac) = derive_keys_for_mnemonic(password, &salt_aes, &salt_hmac)?;
let (key_aes, key_hmac) = derive_keys_for_mnemonic(password, &key_derivation_details)?;

encrypt_data(mnemonic.as_bytes(), key_derivation_details, &key_aes, &key_hmac)
.mm_err(|e| MnemonicError::EncryptionError(e.to_string()))
Expand All @@ -105,20 +101,8 @@ pub fn encrypt_mnemonic(mnemonic: &str, password: &str) -> MmResult<EncryptedDat
/// # Errors
/// This function can return various errors related to decoding, key derivation, encryption, and HMAC verification.
pub fn decrypt_mnemonic(encrypted_data: &EncryptedData, password: &str) -> MmResult<String, MnemonicError> {
// Re-create the salts from Base64-encoded strings
let (salt_aes, salt_hmac) = match &encrypted_data.key_derivation_details {
KeyDerivationDetails::Argon2 {
salt_aes, salt_hmac, ..
} => (SaltString::from_b64(salt_aes)?, SaltString::from_b64(salt_hmac)?),
_ => {
return MmError::err(MnemonicError::KeyDerivationError(
"Key derivation details should be Argon2!".to_string(),
))
},
};

// Re-create the keys from the password and salts
let (key_aes, key_hmac) = derive_keys_for_mnemonic(password, &salt_aes, &salt_hmac)?;
let (key_aes, key_hmac) = derive_keys_for_mnemonic(password, &encrypted_data.key_derivation_details)?;

// Decrypt the ciphertext
let decrypted_data =
Expand Down