cfxstore/account/
crypto.rs

1// Copyright 2015-2019 Parity Technologies (UK) Ltd.
2// This file is part of Parity Ethereum.
3
4// Parity Ethereum is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Parity Ethereum is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Parity Ethereum.  If not, see <http://www.gnu.org/licenses/>.
16
17use crate::{
18    account::{Aes128Ctr, Cipher, Kdf, Pbkdf2, Prf},
19    json,
20    random::Random,
21    Error,
22};
23use cfx_crypto::crypto::{
24    aes, derive_mac, is_equal, keccak::Keccak256, pbkdf2, scrypt, KEY_LENGTH,
25};
26use cfxkey::{Password, Secret};
27use smallvec::SmallVec;
28use std::str;
29
30/// Encrypted data
31#[derive(Debug, PartialEq, Clone)]
32pub struct Crypto {
33    /// Encryption parameters
34    pub cipher: Cipher,
35    /// Encrypted data buffer
36    pub ciphertext: Vec<u8>,
37    /// Key derivation function parameters
38    pub kdf: Kdf,
39    /// Message authentication code
40    pub mac: [u8; 32],
41}
42
43impl From<json::Crypto> for Crypto {
44    fn from(json: json::Crypto) -> Self {
45        Crypto {
46            cipher: json.cipher.into(),
47            ciphertext: json.ciphertext.into(),
48            kdf: json.kdf.into(),
49            mac: json.mac.into(),
50        }
51    }
52}
53
54impl From<Crypto> for json::Crypto {
55    fn from(c: Crypto) -> Self {
56        json::Crypto {
57            cipher: c.cipher.into(),
58            ciphertext: c.ciphertext.into(),
59            kdf: c.kdf.into(),
60            mac: c.mac.into(),
61        }
62    }
63}
64
65impl str::FromStr for Crypto {
66    type Err = <json::Crypto as str::FromStr>::Err;
67
68    fn from_str(s: &str) -> Result<Self, Self::Err> {
69        s.parse::<json::Crypto>().map(Into::into)
70    }
71}
72
73impl From<Crypto> for String {
74    fn from(c: Crypto) -> Self { json::Crypto::from(c).into() }
75}
76
77impl Crypto {
78    /// Encrypt account secret
79    pub fn with_secret(
80        secret: &Secret, password: &Password, iterations: u32,
81    ) -> Result<Self, Error> {
82        Crypto::with_plain(secret.as_ref(), password, iterations)
83    }
84
85    /// Encrypt custom plain data
86    pub fn with_plain(
87        plain: &[u8], password: &Password, iterations: u32,
88    ) -> Result<Self, Error> {
89        let salt: [u8; 32] = Random::random();
90        let iv: [u8; 16] = Random::random();
91
92        // two parts of derived key
93        // DK = [ DK[0..15] DK[16..31] ] = [derived_left_bits,
94        // derived_right_bits]
95        let (derived_left_bits, derived_right_bits) =
96            pbkdf2::derive_key_iterations(
97                password.as_bytes(),
98                &salt,
99                iterations,
100            )
101            .map_err(Error::EthCrypto)?;
102
103        // preallocated (on-stack in case of `Secret`) buffer to hold cipher
104        // length = length(plain) as we are using CTR-approach
105        let plain_len = plain.len();
106        let mut ciphertext: SmallVec<[u8; 32]> =
107            SmallVec::from_vec(vec![0; plain_len]);
108
109        // aes-128-ctr with initial vector of iv
110        aes::encrypt_128_ctr(&derived_left_bits, &iv, plain, &mut *ciphertext)?;
111
112        // KECCAK(DK[16..31] ++ <ciphertext>), where DK[16..31] -
113        // derived_right_bits
114        let mac = derive_mac(&derived_right_bits, &*ciphertext).keccak256();
115
116        Ok(Crypto {
117            cipher: Cipher::Aes128Ctr(Aes128Ctr { iv }),
118            ciphertext: ciphertext.into_vec(),
119            kdf: Kdf::Pbkdf2(Pbkdf2 {
120                dklen: KEY_LENGTH as u32,
121                salt: salt.to_vec(),
122                c: iterations,
123                prf: Prf::HmacSha256,
124            }),
125            mac,
126        })
127    }
128
129    /// Try to decrypt and convert result to account secret
130    pub fn secret(&self, password: &Password) -> Result<Secret, Error> {
131        if self.ciphertext.len() > 32 {
132            return Err(Error::InvalidSecret);
133        }
134
135        let secret = self.do_decrypt(password, 32)?;
136        Ok(Secret::from_unsafe_slice(&secret)?)
137    }
138
139    /// Try to decrypt and return result as is
140    pub fn decrypt(&self, password: &Password) -> Result<Vec<u8>, Error> {
141        let expected_len = self.ciphertext.len();
142        self.do_decrypt(password, expected_len)
143    }
144
145    fn do_decrypt(
146        &self, password: &Password, expected_len: usize,
147    ) -> Result<Vec<u8>, Error> {
148        let (derived_left_bits, derived_right_bits) = match self.kdf {
149            Kdf::Pbkdf2(ref params) => pbkdf2::derive_key_iterations(
150                password.as_bytes(),
151                &params.salt,
152                params.c,
153            )
154            .map_err(Error::EthCrypto)?,
155            Kdf::Scrypt(ref params) => scrypt::derive_key(
156                password.as_bytes(),
157                &params.salt,
158                params.n,
159                params.p,
160                params.r,
161            )
162            .map_err(Error::EthCrypto)?,
163        };
164
165        let mac = derive_mac(&derived_right_bits, &self.ciphertext).keccak256();
166
167        if !is_equal(&mac, &self.mac) {
168            return Err(Error::InvalidPassword);
169        }
170
171        let mut plain: SmallVec<[u8; 32]> =
172            SmallVec::from_vec(vec![0; expected_len]);
173
174        match self.cipher {
175            Cipher::Aes128Ctr(ref params) => {
176                // checker by callers
177                debug_assert!(expected_len >= self.ciphertext.len());
178
179                let from = expected_len - self.ciphertext.len();
180                aes::decrypt_128_ctr(
181                    &derived_left_bits,
182                    &params.iv,
183                    &self.ciphertext,
184                    &mut plain[from..],
185                )?;
186                Ok(plain.into_iter().collect())
187            }
188        }
189    }
190}
191
192#[cfg(test)]
193mod tests {
194    use super::{Crypto, Error};
195    use cfxkey::{Generator, Random};
196    use matches::assert_matches;
197
198    #[test]
199    fn crypto_with_secret_create() {
200        let keypair = Random.generate().unwrap();
201        let passwd = "this is sparta".into();
202        let crypto =
203            Crypto::with_secret(keypair.secret(), &passwd, 10240).unwrap();
204        let secret = crypto.secret(&passwd).unwrap();
205        assert_eq!(keypair.secret(), &secret);
206    }
207
208    #[test]
209    fn crypto_with_secret_invalid_password() {
210        let keypair = Random.generate().unwrap();
211        let crypto = Crypto::with_secret(
212            keypair.secret(),
213            &"this is sparta".into(),
214            10240,
215        )
216        .unwrap();
217        assert_matches!(
218            crypto.secret(&"this is sparta!".into()),
219            Err(Error::InvalidPassword)
220        )
221    }
222
223    #[test]
224    fn crypto_with_null_plain_data() {
225        let original_data = b"";
226        let passwd = "this is sparta".into();
227        let crypto =
228            Crypto::with_plain(&original_data[..], &passwd, 10240).unwrap();
229        let decrypted_data = crypto.decrypt(&passwd).unwrap();
230        assert_eq!(original_data[..], *decrypted_data);
231    }
232
233    #[test]
234    fn crypto_with_tiny_plain_data() {
235        let original_data = b"{}";
236        let passwd = "this is sparta".into();
237        let crypto =
238            Crypto::with_plain(&original_data[..], &passwd, 10240).unwrap();
239        let decrypted_data = crypto.decrypt(&passwd).unwrap();
240        assert_eq!(original_data[..], *decrypted_data);
241    }
242
243    #[test]
244    fn crypto_with_huge_plain_data() {
245        let original_data: Vec<_> =
246            (1..65536).map(|i| (i % 256) as u8).collect();
247        let passwd = "this is sparta".into();
248        let crypto =
249            Crypto::with_plain(&original_data, &passwd, 10240).unwrap();
250        let decrypted_data = crypto.decrypt(&passwd).unwrap();
251        assert_eq!(&original_data, &decrypted_data);
252    }
253}