2018-06-04 10:19:50 +02:00
|
|
|
// Copyright 2015-2018 Parity Technologies (UK) Ltd.
|
2017-01-30 11:44:09 +01:00
|
|
|
// This file is part of Parity.
|
|
|
|
|
|
|
|
// Parity is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
|
|
|
|
// Parity is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
2017-04-11 10:24:56 +02:00
|
|
|
use std::str;
|
2018-06-22 15:09:15 +02:00
|
|
|
use ethkey::{Password, Secret};
|
2017-01-30 11:44:09 +01:00
|
|
|
use {json, Error, crypto};
|
|
|
|
use crypto::Keccak256;
|
|
|
|
use random::Random;
|
|
|
|
use smallvec::SmallVec;
|
|
|
|
use account::{Cipher, Kdf, Aes128Ctr, Pbkdf2, Prf};
|
|
|
|
|
|
|
|
/// Encrypted data
|
|
|
|
#[derive(Debug, PartialEq, Clone)]
|
|
|
|
pub struct Crypto {
|
|
|
|
/// Encryption parameters
|
|
|
|
pub cipher: Cipher,
|
|
|
|
/// Encrypted data buffer
|
|
|
|
pub ciphertext: Vec<u8>,
|
|
|
|
/// Key derivation function parameters
|
|
|
|
pub kdf: Kdf,
|
|
|
|
/// Message authentication code
|
|
|
|
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(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-11 10:24:56 +02:00
|
|
|
impl From<Crypto> for json::Crypto {
|
|
|
|
fn from(c: Crypto) -> Self {
|
2017-01-30 11:44:09 +01:00
|
|
|
json::Crypto {
|
2017-04-11 10:24:56 +02:00
|
|
|
cipher: c.cipher.into(),
|
|
|
|
ciphertext: c.ciphertext.into(),
|
|
|
|
kdf: c.kdf.into(),
|
|
|
|
mac: c.mac.into(),
|
2017-01-30 11:44:09 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-11 10:24:56 +02:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
impl Crypto {
|
2017-04-13 14:26:07 +02:00
|
|
|
/// Encrypt account secret
|
2018-06-22 15:09:15 +02:00
|
|
|
pub fn with_secret(secret: &Secret, password: &Password, iterations: u32) -> Result<Self, crypto::Error> {
|
2017-01-30 11:44:09 +01:00
|
|
|
Crypto::with_plain(&*secret, password, iterations)
|
|
|
|
}
|
|
|
|
|
2017-04-13 14:26:07 +02:00
|
|
|
/// Encrypt custom plain data
|
2018-06-22 15:09:15 +02:00
|
|
|
pub fn with_plain(plain: &[u8], password: &Password, iterations: u32) -> Result<Self, crypto::Error> {
|
2017-01-30 11:44:09 +01:00
|
|
|
let salt: [u8; 32] = Random::random();
|
|
|
|
let iv: [u8; 16] = Random::random();
|
|
|
|
|
|
|
|
// two parts of derived key
|
|
|
|
// DK = [ DK[0..15] DK[16..31] ] = [derived_left_bits, derived_right_bits]
|
2019-01-03 14:07:27 +01:00
|
|
|
let (derived_left_bits, derived_right_bits) =
|
|
|
|
crypto::derive_key_iterations(password.as_bytes(), &salt, iterations);
|
2017-01-30 11:44:09 +01:00
|
|
|
|
|
|
|
// preallocated (on-stack in case of `Secret`) buffer to hold cipher
|
|
|
|
// length = length(plain) as we are using CTR-approach
|
|
|
|
let plain_len = plain.len();
|
2017-11-14 13:06:50 +01:00
|
|
|
let mut ciphertext: SmallVec<[u8; 32]> = SmallVec::from_vec(vec![0; plain_len]);
|
2017-01-30 11:44:09 +01:00
|
|
|
|
|
|
|
// aes-128-ctr with initial vector of iv
|
2018-05-05 11:02:33 +02:00
|
|
|
crypto::aes::encrypt_128_ctr(&derived_left_bits, &iv, plain, &mut *ciphertext)?;
|
2017-01-30 11:44:09 +01:00
|
|
|
|
|
|
|
// KECCAK(DK[16..31] ++ <ciphertext>), where DK[16..31] - derived_right_bits
|
|
|
|
let mac = crypto::derive_mac(&derived_right_bits, &*ciphertext).keccak256();
|
|
|
|
|
2018-05-05 11:02:33 +02:00
|
|
|
Ok(Crypto {
|
2017-01-30 11:44:09 +01:00
|
|
|
cipher: Cipher::Aes128Ctr(Aes128Ctr {
|
|
|
|
iv: iv,
|
|
|
|
}),
|
2017-06-28 14:16:53 +02:00
|
|
|
ciphertext: ciphertext.into_vec(),
|
2017-01-30 11:44:09 +01:00
|
|
|
kdf: Kdf::Pbkdf2(Pbkdf2 {
|
|
|
|
dklen: crypto::KEY_LENGTH as u32,
|
2019-01-03 14:07:27 +01:00
|
|
|
salt: salt.to_vec(),
|
2017-01-30 11:44:09 +01:00
|
|
|
c: iterations,
|
|
|
|
prf: Prf::HmacSha256,
|
|
|
|
}),
|
|
|
|
mac: mac,
|
2018-05-05 11:02:33 +02:00
|
|
|
})
|
2017-01-30 11:44:09 +01:00
|
|
|
}
|
|
|
|
|
2017-04-13 14:26:07 +02:00
|
|
|
/// Try to decrypt and convert result to account secret
|
2018-06-22 15:09:15 +02:00
|
|
|
pub fn secret(&self, password: &Password) -> Result<Secret, Error> {
|
2017-01-30 11:44:09 +01:00
|
|
|
if self.ciphertext.len() > 32 {
|
|
|
|
return Err(Error::InvalidSecret);
|
|
|
|
}
|
|
|
|
|
|
|
|
let secret = self.do_decrypt(password, 32)?;
|
2017-05-19 17:06:36 +02:00
|
|
|
Ok(Secret::from_unsafe_slice(&secret)?)
|
2017-01-30 11:44:09 +01:00
|
|
|
}
|
|
|
|
|
2017-04-13 14:26:07 +02:00
|
|
|
/// Try to decrypt and return result as is
|
2018-06-22 15:09:15 +02:00
|
|
|
pub fn decrypt(&self, password: &Password) -> Result<Vec<u8>, Error> {
|
2017-01-30 11:44:09 +01:00
|
|
|
let expected_len = self.ciphertext.len();
|
|
|
|
self.do_decrypt(password, expected_len)
|
|
|
|
}
|
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
fn do_decrypt(&self, password: &Password, expected_len: usize) -> Result<Vec<u8>, Error> {
|
2017-01-30 11:44:09 +01:00
|
|
|
let (derived_left_bits, derived_right_bits) = match self.kdf {
|
2018-06-22 15:09:15 +02:00
|
|
|
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)?,
|
2017-01-30 11:44:09 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
let mac = crypto::derive_mac(&derived_right_bits, &self.ciphertext).keccak256();
|
|
|
|
|
2018-05-05 11:02:33 +02:00
|
|
|
if !crypto::is_equal(&mac, &self.mac) {
|
|
|
|
return Err(Error::InvalidPassword)
|
2017-01-30 11:44:09 +01:00
|
|
|
}
|
|
|
|
|
2017-11-14 13:06:50 +01:00
|
|
|
let mut plain: SmallVec<[u8; 32]> = SmallVec::from_vec(vec![0; expected_len]);
|
2017-01-30 11:44:09 +01:00
|
|
|
|
|
|
|
match self.cipher {
|
|
|
|
Cipher::Aes128Ctr(ref params) => {
|
|
|
|
// checker by callers
|
|
|
|
debug_assert!(expected_len >= self.ciphertext.len());
|
|
|
|
|
|
|
|
let from = expected_len - self.ciphertext.len();
|
2018-05-05 11:02:33 +02:00
|
|
|
crypto::aes::decrypt_128_ctr(&derived_left_bits, ¶ms.iv, &self.ciphertext, &mut plain[from..])?;
|
2017-01-30 11:44:09 +01:00
|
|
|
Ok(plain.into_iter().collect())
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use ethkey::{Generator, Random};
|
2018-03-14 15:41:35 +01:00
|
|
|
use super::{Crypto, Error};
|
2017-01-30 11:44:09 +01:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn crypto_with_secret_create() {
|
|
|
|
let keypair = Random.generate().unwrap();
|
2018-06-22 15:09:15 +02:00
|
|
|
let passwd = "this is sparta".into();
|
|
|
|
let crypto = Crypto::with_secret(keypair.secret(), &passwd, 10240).unwrap();
|
|
|
|
let secret = crypto.secret(&passwd).unwrap();
|
2017-01-30 11:44:09 +01:00
|
|
|
assert_eq!(keypair.secret(), &secret);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn crypto_with_secret_invalid_password() {
|
|
|
|
let keypair = Random.generate().unwrap();
|
2018-06-22 15:09:15 +02:00
|
|
|
let crypto = Crypto::with_secret(keypair.secret(), &"this is sparta".into(), 10240).unwrap();
|
|
|
|
assert_matches!(crypto.secret(&"this is sparta!".into()), Err(Error::InvalidPassword))
|
2017-01-30 11:44:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn crypto_with_null_plain_data() {
|
|
|
|
let original_data = b"";
|
2018-06-22 15:09:15 +02:00
|
|
|
let passwd = "this is sparta".into();
|
|
|
|
let crypto = Crypto::with_plain(&original_data[..], &passwd, 10240).unwrap();
|
|
|
|
let decrypted_data = crypto.decrypt(&passwd).unwrap();
|
2017-01-30 11:44:09 +01:00
|
|
|
assert_eq!(original_data[..], *decrypted_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn crypto_with_tiny_plain_data() {
|
|
|
|
let original_data = b"{}";
|
2018-06-22 15:09:15 +02:00
|
|
|
let passwd = "this is sparta".into();
|
|
|
|
let crypto = Crypto::with_plain(&original_data[..], &passwd, 10240).unwrap();
|
|
|
|
let decrypted_data = crypto.decrypt(&passwd).unwrap();
|
2017-01-30 11:44:09 +01:00
|
|
|
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();
|
2018-06-22 15:09:15 +02:00
|
|
|
let passwd = "this is sparta".into();
|
|
|
|
let crypto = Crypto::with_plain(&original_data, &passwd, 10240).unwrap();
|
|
|
|
let decrypted_data = crypto.decrypt(&passwd).unwrap();
|
2017-01-30 11:44:09 +01:00
|
|
|
assert_eq!(&original_data, &decrypted_data);
|
|
|
|
}
|
|
|
|
}
|