use crate::{
account::{Aes128Ctr, Cipher, Kdf, Pbkdf2, Prf},
crypto::{self, Keccak256},
json,
random::Random,
Error,
};
use cfxkey::{Password, Secret};
use smallvec::SmallVec;
use std::str;
#[derive(Debug, PartialEq, Clone)]
pub struct Crypto {
pub cipher: Cipher,
pub ciphertext: Vec<u8>,
pub kdf: Kdf,
pub mac: [u8; 32],
}
impl From<json::Crypto> for Crypto {
fn from(json: json::Crypto) -> Self {
Crypto {
cipher: json.cipher.into(),
ciphertext: json.ciphertext.into(),
kdf: json.kdf.into(),
mac: json.mac.into(),
}
}
}
impl From<Crypto> for json::Crypto {
fn from(c: Crypto) -> Self {
json::Crypto {
cipher: c.cipher.into(),
ciphertext: c.ciphertext.into(),
kdf: c.kdf.into(),
mac: c.mac.into(),
}
}
}
impl str::FromStr for Crypto {
type Err = <json::Crypto as str::FromStr>::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse::<json::Crypto>().map(Into::into)
}
}
impl From<Crypto> for String {
fn from(c: Crypto) -> Self { json::Crypto::from(c).into() }
}
impl Crypto {
pub fn with_secret(
secret: &Secret, password: &Password, iterations: u32,
) -> Result<Self, crypto::Error> {
Crypto::with_plain(secret.as_ref(), password, iterations)
}
pub fn with_plain(
plain: &[u8], password: &Password, iterations: u32,
) -> Result<Self, crypto::Error> {
let salt: [u8; 32] = Random::random();
let iv: [u8; 16] = Random::random();
let (derived_left_bits, derived_right_bits) =
crypto::derive_key_iterations(
password.as_bytes(),
&salt,
iterations,
);
let plain_len = plain.len();
let mut ciphertext: SmallVec<[u8; 32]> =
SmallVec::from_vec(vec![0; plain_len]);
crypto::aes::encrypt_128_ctr(
&derived_left_bits,
&iv,
plain,
&mut *ciphertext,
)?;
let mac =
crypto::derive_mac(&derived_right_bits, &*ciphertext).keccak256();
Ok(Crypto {
cipher: Cipher::Aes128Ctr(Aes128Ctr { iv }),
ciphertext: ciphertext.into_vec(),
kdf: Kdf::Pbkdf2(Pbkdf2 {
dklen: crypto::KEY_LENGTH as u32,
salt: salt.to_vec(),
c: iterations,
prf: Prf::HmacSha256,
}),
mac,
})
}
pub fn secret(&self, password: &Password) -> Result<Secret, Error> {
if self.ciphertext.len() > 32 {
return Err(Error::InvalidSecret);
}
let secret = self.do_decrypt(password, 32)?;
Ok(Secret::from_unsafe_slice(&secret)?)
}
pub fn decrypt(&self, password: &Password) -> Result<Vec<u8>, Error> {
let expected_len = self.ciphertext.len();
self.do_decrypt(password, expected_len)
}
fn do_decrypt(
&self, password: &Password, expected_len: usize,
) -> Result<Vec<u8>, Error> {
let (derived_left_bits, derived_right_bits) = match self.kdf {
Kdf::Pbkdf2(ref params) => crypto::derive_key_iterations(
password.as_bytes(),
¶ms.salt,
params.c,
),
Kdf::Scrypt(ref params) => crypto::scrypt::derive_key(
password.as_bytes(),
¶ms.salt,
params.n,
params.p,
params.r,
)?,
};
let mac = crypto::derive_mac(&derived_right_bits, &self.ciphertext)
.keccak256();
if !crypto::is_equal(&mac, &self.mac) {
return Err(Error::InvalidPassword);
}
let mut plain: SmallVec<[u8; 32]> =
SmallVec::from_vec(vec![0; expected_len]);
match self.cipher {
Cipher::Aes128Ctr(ref params) => {
debug_assert!(expected_len >= self.ciphertext.len());
let from = expected_len - self.ciphertext.len();
crypto::aes::decrypt_128_ctr(
&derived_left_bits,
¶ms.iv,
&self.ciphertext,
&mut plain[from..],
)?;
Ok(plain.into_iter().collect())
}
}
}
}
#[cfg(test)]
mod tests {
use super::{Crypto, Error};
use cfxkey::{Generator, Random};
use matches::assert_matches;
#[test]
fn crypto_with_secret_create() {
let keypair = Random.generate().unwrap();
let passwd = "this is sparta".into();
let crypto =
Crypto::with_secret(keypair.secret(), &passwd, 10240).unwrap();
let secret = crypto.secret(&passwd).unwrap();
assert_eq!(keypair.secret(), &secret);
}
#[test]
fn crypto_with_secret_invalid_password() {
let keypair = Random.generate().unwrap();
let crypto = Crypto::with_secret(
keypair.secret(),
&"this is sparta".into(),
10240,
)
.unwrap();
assert_matches!(
crypto.secret(&"this is sparta!".into()),
Err(Error::InvalidPassword)
)
}
#[test]
fn crypto_with_null_plain_data() {
let original_data = b"";
let passwd = "this is sparta".into();
let crypto =
Crypto::with_plain(&original_data[..], &passwd, 10240).unwrap();
let decrypted_data = crypto.decrypt(&passwd).unwrap();
assert_eq!(original_data[..], *decrypted_data);
}
#[test]
fn crypto_with_tiny_plain_data() {
let original_data = b"{}";
let passwd = "this is sparta".into();
let crypto =
Crypto::with_plain(&original_data[..], &passwd, 10240).unwrap();
let decrypted_data = crypto.decrypt(&passwd).unwrap();
assert_eq!(original_data[..], *decrypted_data);
}
#[test]
fn crypto_with_huge_plain_data() {
let original_data: Vec<_> =
(1..65536).map(|i| (i % 256) as u8).collect();
let passwd = "this is sparta".into();
let crypto =
Crypto::with_plain(&original_data, &passwd, 10240).unwrap();
let decrypted_data = crypto.decrypt(&passwd).unwrap();
assert_eq!(&original_data, &decrypted_data);
}
}