1#![cfg_attr(docsrs, feature(doc_cfg))]
2use aes::{
6 cipher::{self, InnerIvInit, KeyInit, StreamCipherCore},
7 Aes128,
8};
9use digest::{Digest, Update};
10use hmac::Hmac;
11use pbkdf2::pbkdf2;
12use rand::{CryptoRng, Rng};
13use scrypt::{scrypt, Params as ScryptParams};
14use sha2::Sha256;
15use sha3::Keccak256;
16use uuid::Uuid;
17
18use std::{
19 fs::File,
20 io::{Read, Write},
21 path::Path,
22};
23
24mod error;
25mod keystore;
26mod utils;
27
28#[cfg(feature = "geth-compat")]
29use utils::geth_compat::address_from_pk;
30
31pub use error::KeystoreError;
32pub use keystore::{CipherparamsJson, CryptoJson, EthKeystore, KdfType, KdfparamsType};
33
34const DEFAULT_CIPHER: &str = "aes-128-ctr";
35const DEFAULT_KEY_SIZE: usize = 32usize;
36const DEFAULT_IV_SIZE: usize = 16usize;
37const DEFAULT_KDF_PARAMS_DKLEN: u8 = 32u8;
38const DEFAULT_KDF_PARAMS_LOG_N: u8 = 13u8;
39const DEFAULT_KDF_PARAMS_R: u32 = 8u32;
40const DEFAULT_KDF_PARAMS_P: u32 = 1u32;
41
42pub fn new<P, R, S>(
66 dir: P,
67 rng: &mut R,
68 password: S,
69 name: Option<&str>,
70) -> Result<(Vec<u8>, String), KeystoreError>
71where
72 P: AsRef<Path>,
73 R: Rng + CryptoRng,
74 S: AsRef<[u8]>,
75{
76 let mut pk = vec![0u8; DEFAULT_KEY_SIZE];
78 rng.fill_bytes(pk.as_mut_slice());
79
80 let name = encrypt_key(dir, rng, &pk, password, name)?;
81 Ok((pk, name))
82}
83
84pub fn decrypt_key<P, S>(path: P, password: S) -> Result<Vec<u8>, KeystoreError>
101where
102 P: AsRef<Path>,
103 S: AsRef<[u8]>,
104{
105 let mut file = File::open(path)?;
107 let mut contents = String::new();
108 file.read_to_string(&mut contents)?;
109 let keystore: EthKeystore = serde_json::from_str(&contents)?;
110
111 let key = match keystore.crypto.kdfparams {
113 KdfparamsType::Pbkdf2 {
114 c,
115 dklen,
116 prf: _,
117 salt,
118 } => {
119 let mut key = vec![0u8; dklen as usize];
120 pbkdf2::<Hmac<Sha256>>(password.as_ref(), &salt, c, key.as_mut_slice());
121 key
122 }
123 KdfparamsType::Scrypt {
124 dklen,
125 n,
126 p,
127 r,
128 salt,
129 } => {
130 let mut key = vec![0u8; dklen as usize];
131 let log_n = (n as f32).log2() as u8;
132 let scrypt_params = ScryptParams::new(log_n, r, p)?;
133 scrypt(password.as_ref(), &salt, &scrypt_params, key.as_mut_slice())?;
134 key
135 }
136 };
137
138 let derived_mac = Keccak256::new()
140 .chain(&key[16..32])
141 .chain(&keystore.crypto.ciphertext)
142 .finalize();
143
144 if derived_mac.as_slice() != keystore.crypto.mac.as_slice() {
145 return Err(KeystoreError::MacMismatch);
146 }
147
148 let decryptor =
150 Aes128Ctr::new(&key[..16], &keystore.crypto.cipherparams.iv[..16]).expect("invalid length");
151
152 let mut pk = keystore.crypto.ciphertext;
153 decryptor.apply_keystream(&mut pk);
154
155 Ok(pk)
156}
157
158pub fn encrypt_key<P, R, B, S>(
183 dir: P,
184 rng: &mut R,
185 pk: B,
186 password: S,
187 name: Option<&str>,
188) -> Result<String, KeystoreError>
189where
190 P: AsRef<Path>,
191 R: Rng + CryptoRng,
192 B: AsRef<[u8]>,
193 S: AsRef<[u8]>,
194{
195 let mut salt = vec![0u8; DEFAULT_KEY_SIZE];
197 rng.fill_bytes(salt.as_mut_slice());
198
199 let mut key = vec![0u8; DEFAULT_KDF_PARAMS_DKLEN as usize];
201 let scrypt_params = ScryptParams::new(
202 DEFAULT_KDF_PARAMS_LOG_N,
203 DEFAULT_KDF_PARAMS_R,
204 DEFAULT_KDF_PARAMS_P,
205 )?;
206 scrypt(password.as_ref(), &salt, &scrypt_params, key.as_mut_slice())?;
207
208 let mut iv = vec![0u8; DEFAULT_IV_SIZE];
210 rng.fill_bytes(iv.as_mut_slice());
211
212 let encryptor = Aes128Ctr::new(&key[..16], &iv[..16]).expect("invalid length");
213
214 let mut ciphertext = pk.as_ref().to_vec();
215 encryptor.apply_keystream(&mut ciphertext);
216
217 let mac = Keccak256::new()
219 .chain(&key[16..32])
220 .chain(&ciphertext)
221 .finalize();
222
223 let id = Uuid::new_v4();
225 let name = if let Some(name) = name {
226 name.to_string()
227 } else {
228 id.to_string()
229 };
230
231 let keystore = EthKeystore {
233 id,
234 version: 3,
235 crypto: CryptoJson {
236 cipher: String::from(DEFAULT_CIPHER),
237 cipherparams: CipherparamsJson { iv },
238 ciphertext: ciphertext.to_vec(),
239 kdf: KdfType::Scrypt,
240 kdfparams: KdfparamsType::Scrypt {
241 dklen: DEFAULT_KDF_PARAMS_DKLEN,
242 n: 2u32.pow(DEFAULT_KDF_PARAMS_LOG_N as u32),
243 p: DEFAULT_KDF_PARAMS_P,
244 r: DEFAULT_KDF_PARAMS_R,
245 salt,
246 },
247 mac: mac.to_vec(),
248 },
249 #[cfg(feature = "geth-compat")]
250 address: address_from_pk(&pk)?,
251 };
252 let contents = serde_json::to_string(&keystore)?;
253
254 let mut file = File::create(dir.as_ref().join(&name))?;
256 file.write_all(contents.as_bytes())?;
257
258 Ok(id.to_string())
259}
260
261struct Aes128Ctr {
262 inner: ctr::CtrCore<Aes128, ctr::flavors::Ctr128BE>,
263}
264
265impl Aes128Ctr {
266 fn new(key: &[u8], iv: &[u8]) -> Result<Self, cipher::InvalidLength> {
267 let cipher = aes::Aes128::new_from_slice(key).unwrap();
268 let inner = ctr::CtrCore::inner_iv_slice_init(cipher, iv).unwrap();
269 Ok(Self { inner })
270 }
271
272 fn apply_keystream(self, buf: &mut [u8]) {
273 self.inner.apply_keystream_partial(buf.into());
274 }
275}