diff --git a/util/src/keys/directory.rs b/util/src/keys/directory.rs index ccd7d8fb4..641a5cae4 100644 --- a/util/src/keys/directory.rs +++ b/util/src/keys/directory.rs @@ -168,6 +168,8 @@ pub struct KeyFileCrypto { pub cipher_text: Bytes, /// Password derived key generator function settings. pub kdf: KeyFileKdf, + /// Mac + pub mac: H256 } impl KeyFileCrypto { @@ -216,15 +218,24 @@ impl KeyFileCrypto { } }; - let cipher_text = match as_object["ciphertext"].as_string() { - None => { return Err(CryptoParseError::NoCipherText); } + let cipher_text = match try!(as_object.get("ciphertext").ok_or(CryptoParseError::NoCipherText)).as_string() { + None => { return Err(CryptoParseError::InvalidCipherText); } Some(text) => text }; + let mac: H256 = match try!(as_object.get("mac").ok_or(CryptoParseError::NoMac)).as_string() { + None => { return Err(CryptoParseError::InvalidMacFormat(None)) }, + Some(salt_value) => match H256::from_str(salt_value) { + Ok(salt_hex_value) => salt_hex_value, + Err(from_hex_error) => { return Err(CryptoParseError::InvalidMacFormat(Some(from_hex_error))); }, + } + }; + Ok(KeyFileCrypto { cipher_text: Bytes::from(cipher_text), cipher_type: cipher_type, kdf: kdf, + mac: mac, }) } @@ -251,6 +262,8 @@ impl KeyFileCrypto { KeyFileKdf::Scrypt(ref scrypt_params) => scrypt_params.to_json() }); + map.insert("mac".to_owned(), Json::String(format!("{:?}", self.mac))); + Json::Object(map) } @@ -270,6 +283,7 @@ impl KeyFileCrypto { c: c, prf: Pbkdf2CryptoFunction::HMacSha256 }), + mac: H256::random(), } } } @@ -324,7 +338,10 @@ pub struct KeyFileContent { #[derive(Debug)] enum CryptoParseError { + InvalidMacFormat(Option), + NoMac, NoCipherText, + InvalidCipherText, NoCipherType, InvalidJsonFormat, InvalidKdfType(Mismatch), diff --git a/util/src/keys/mod.rs b/util/src/keys/mod.rs index f886d362c..abd029444 100644 --- a/util/src/keys/mod.rs +++ b/util/src/keys/mod.rs @@ -18,4 +18,4 @@ pub mod directory; -mod encryptor; +mod store; diff --git a/util/src/keys/encryptor.rs b/util/src/keys/store.rs similarity index 60% rename from util/src/keys/encryptor.rs rename to util/src/keys/store.rs index 7645a2b84..9972b6e57 100644 --- a/util/src/keys/encryptor.rs +++ b/util/src/keys/store.rs @@ -27,6 +27,9 @@ const KEY_LENGTH: u32 = 32; const KEY_ITERATIONS: u32 = 4096; const KEY_LENGTH_AES: u32 = KEY_LENGTH/2; +const KEY_LENGTH_USIZE: usize = KEY_LENGTH as usize; +const KEY_LENGTH_AES_USIZE: usize = KEY_LENGTH_AES as usize; + pub trait EncryptedHashMap { // Returns existing value for the key, if any fn get(&self, key: &Key, password: &str) -> Option; @@ -61,6 +64,26 @@ impl SecretStore { } } +fn derive_key_iterations(password: &str, salt: &H256, c: u32) -> (Bytes, Bytes) { + let mut h_mac = Hmac::new(::rcrypto::sha2::Sha256::new(), password.as_bytes()); + let mut derived_key = vec![0u8; KEY_LENGTH_USIZE]; + pbkdf2(&mut h_mac, &salt.as_slice(), c, &mut derived_key); + let derived_right_bits = &derived_key[0..KEY_LENGTH_AES_USIZE]; + let derived_left_bits = &derived_key[KEY_LENGTH_AES_USIZE..KEY_LENGTH_USIZE]; + (derived_right_bits.to_vec(), derived_left_bits.to_vec()) +} + +fn derive_key(password: &str, salt: &H256) -> (Bytes, Bytes) { + derive_key_iterations(password, salt, KEY_ITERATIONS) +} + +fn derive_mac(derived_left_bits: &[u8], cipher_text: &[u8]) -> Bytes { + let mut mac = vec![0u8; KEY_LENGTH_AES_USIZE + cipher_text.len()]; + mac[0..KEY_LENGTH_AES_USIZE].clone_from_slice(derived_left_bits); + mac[KEY_LENGTH_AES_USIZE..cipher_text.len()+KEY_LENGTH_AES_USIZE].clone_from_slice(cipher_text); + mac +} + impl EncryptedHashMap for SecretStore { fn get(&self, key: &H128, password: &str) -> Option { match self.directory.get(key) { @@ -81,14 +104,13 @@ impl EncryptedHashMap for SecretStore { let iv = H128::random(); let mut key_file = KeyFileContent::new(KeyFileCrypto::new_pbkdf2(vec![], iv.clone(), salt.clone(), KEY_ITERATIONS, KEY_LENGTH)); - let mut mac = Hmac::new(::rcrypto::sha2::Sha256::new(), password.as_bytes()); - let mut derived_key = vec![0u8; KEY_LENGTH as usize]; - pbkdf2(&mut mac, &salt.as_slice(), KEY_ITERATIONS, &mut derived_key); - let key = &derived_key[KEY_LENGTH_AES as usize..KEY_LENGTH as usize]; + let (derived_left_bits, derived_right_bits) = derive_key(password, &salt); let mut cipher_text = vec![0u8; value.as_slice().len()]; - crypto::aes::encrypt(&key, &iv.as_slice(), &value.as_slice(), &mut cipher_text); - key_file.crypto.cipher_text = cipher_text; + crypto::aes::encrypt(&derived_left_bits, &iv.as_slice(), &value.as_slice(), &mut cipher_text); + key_file.crypto.cipher_text = cipher_text.clone(); + + key_file.crypto.mac = derive_mac(&derived_right_bits, &cipher_text).sha3(); previous } @@ -103,6 +125,31 @@ impl EncryptedHashMap for SecretStore { } +#[cfg(test)] +mod vector_tests { + use super::{derive_key,derive_mac,derive_key_iterations}; + use common::*; + + + #[test] + fn mac_vector() { + let password = "testpassword"; + let salt = H256::from_str("ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd").unwrap(); + let cipher_text = FromHex::from_hex("5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46").unwrap(); + let iterations = 262144u32; + + let (derived_left_bits, derived_right_bits) = derive_key_iterations(password, &salt, iterations); + assert_eq!("f06d69cdc7da0faffb1008270bca38f5", derived_left_bits.to_hex()); + assert_eq!("e31891a3a773950e6d0fea48a7188551", derived_right_bits.to_hex()); + + let mut mac_body = derive_mac(&derived_right_bits, &cipher_text); + assert_eq!("e31891a3a773950e6d0fea48a71885515318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46", mac_body.to_hex()); + + let mac = mac_body.sha3(); + assert_eq!("517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2", format!("{:?}", mac)); + } +} + #[cfg(test)] mod tests { @@ -118,4 +165,6 @@ mod tests { sstore.insert(H128::random(), "Cat".to_owned(), "pass"); } + + }