1#![deny(missing_docs)]
11#![cfg_attr(docsrs, feature(doc_cfg))]
12
13use aes::cipher::{KeyIvInit, StreamCipher};
14use digest::{Digest, Update};
15use hmac::Hmac;
16use pbkdf2::pbkdf2;
17use rand::{CryptoRng, Rng};
18use scrypt::{scrypt, Params as ScryptParams};
19use sha2::Sha256;
20use sha3::Keccak256;
21use thiserror::Error;
22use uuid::Uuid;
23
24#[derive(Error, Debug)]
25pub enum KeyStoreError {
27 #[error("Mac Mismatch")]
31 MacMismatch,
32 #[error("scrypt {0:?}")]
34 ScryptInvalidParams(scrypt::errors::InvalidParams),
35 #[error("scrypt {0:?}")]
37 ScryptInvalidOuputLen(scrypt::errors::InvalidOutputLen),
38 #[error("aes {0:?}")]
40 AesInvalidKeyNonceLength(aes::cipher::InvalidLength),
41}
42
43impl From<scrypt::errors::InvalidParams> for KeyStoreError {
44 fn from(e: scrypt::errors::InvalidParams) -> Self {
45 Self::ScryptInvalidParams(e)
46 }
47}
48
49impl From<scrypt::errors::InvalidOutputLen> for KeyStoreError {
50 fn from(e: scrypt::errors::InvalidOutputLen) -> Self {
51 Self::ScryptInvalidOuputLen(e)
52 }
53}
54
55impl From<aes::cipher::InvalidLength> for KeyStoreError {
56 fn from(e: aes::cipher::InvalidLength) -> Self {
57 Self::AesInvalidKeyNonceLength(e)
58 }
59}
60
61mod keystore;
62
63use keystore::{CipherParams, CryptoData, KdfParamsType, KdfType};
64
65pub use keystore::KeyStore;
66
67type Aes128Ctr = ctr::Ctr128BE<aes::Aes128>;
68
69const DEFAULT_CIPHER: &str = "aes-128-ctr";
70const DEFAULT_KEY_SIZE: usize = 32usize;
71const DEFAULT_IV_SIZE: usize = 16usize;
72const DEFAULT_KDF_PARAMS_DKLEN: u8 = 32u8;
73const DEFAULT_KDF_PARAMS_LOG_N: u8 = 13u8;
74const DEFAULT_KDF_PARAMS_R: u32 = 8u32;
75const DEFAULT_KDF_PARAMS_P: u32 = 1u32;
76
77pub fn new_random<R, S>(
93 rng: &mut R,
94 password: S,
95) -> Result<(KeyStore, Vec<u8>), KeyStoreError>
96where
97 R: Rng + CryptoRng,
98 S: AsRef<[u8]>,
99{
100 let pk: [u8; DEFAULT_KEY_SIZE] = rng.gen();
101 Ok((encrypt(rng, &pk, password, None, None)?, pk.to_vec()))
102}
103
104pub fn decrypt<S>(
121 keystore: &KeyStore,
122 password: S,
123) -> Result<Vec<u8>, KeyStoreError>
124where
125 S: AsRef<[u8]>,
126{
127 let key = match &keystore.crypto.kdfparams {
129 KdfParamsType::Pbkdf2 {
130 c,
131 dklen,
132 prf: _,
133 salt,
134 } => {
135 let mut key = vec![0u8; *dklen as usize];
136 pbkdf2::<Hmac<Sha256>>(
137 password.as_ref(),
138 salt,
139 *c,
140 key.as_mut_slice(),
141 );
142 key
143 }
144 KdfParamsType::Scrypt {
145 dklen,
146 n,
147 p,
148 r,
149 salt,
150 } => {
151 let mut key = vec![0u8; *dklen as usize];
152 let log_n = (*n as f32).log2().ceil() as u8;
155 let scrypt_params = ScryptParams::new(log_n, *r, *p)?;
156 scrypt(
157 password.as_ref(),
158 salt,
159 &scrypt_params,
160 key.as_mut_slice(),
161 )?;
162 key
163 }
164 };
165
166 let derived_mac = Keccak256::new()
168 .chain(&key[16..32])
169 .chain(&keystore.crypto.ciphertext)
170 .finalize();
171
172 if derived_mac.as_slice() != keystore.crypto.mac.as_slice() {
173 return Err(KeyStoreError::MacMismatch);
174 }
175
176 let mut decryptor = Aes128Ctr::new(
178 (&key[..16]).into(),
179 (&keystore.crypto.cipherparams.iv[..16]).into(),
180 );
181
182 let mut pk = keystore.crypto.ciphertext.clone();
183 decryptor.apply_keystream(&mut pk);
184
185 Ok(pk)
186}
187
188pub fn encrypt<R, B, S>(
207 rng: &mut R,
208 pk: B,
209 password: S,
210 address: Option<String>,
211 label: Option<String>,
212) -> Result<KeyStore, KeyStoreError>
213where
214 R: Rng + CryptoRng,
215 B: AsRef<[u8]>,
216 S: AsRef<[u8]>,
217{
218 let mut salt = vec![0u8; DEFAULT_KEY_SIZE];
220 rng.fill_bytes(salt.as_mut_slice());
221
222 let mut key = vec![0u8; DEFAULT_KDF_PARAMS_DKLEN as usize];
224 let scrypt_params = ScryptParams::new(
225 DEFAULT_KDF_PARAMS_LOG_N,
226 DEFAULT_KDF_PARAMS_R,
227 DEFAULT_KDF_PARAMS_P,
228 )?;
229 scrypt(password.as_ref(), &salt, &scrypt_params, key.as_mut_slice())?;
230
231 let mut iv = vec![0u8; DEFAULT_IV_SIZE];
233 rng.fill_bytes(iv.as_mut_slice());
234
235 let mut encryptor = Aes128Ctr::new((&key[..16]).into(), (&iv[..16]).into());
236
237 let mut ciphertext = pk.as_ref().to_vec();
238 encryptor.apply_keystream(&mut ciphertext);
239
240 let mac = Keccak256::new()
242 .chain(&key[16..32])
243 .chain(&ciphertext)
244 .finalize();
245
246 let id = Uuid::new_v4();
247
248 let keystore = KeyStore {
250 id,
251 address,
252 label,
253 version: 3,
254 crypto: CryptoData {
255 cipher: String::from(DEFAULT_CIPHER),
256 cipherparams: CipherParams { iv },
257 ciphertext: ciphertext.to_vec(),
258 kdf: KdfType::Scrypt,
259 kdfparams: KdfParamsType::Scrypt {
260 dklen: DEFAULT_KDF_PARAMS_DKLEN,
261 n: 2u32.pow(DEFAULT_KDF_PARAMS_LOG_N as u32),
262 p: DEFAULT_KDF_PARAMS_P,
263 r: DEFAULT_KDF_PARAMS_R,
264 salt,
265 },
266 mac: mac.to_vec(),
267 },
268 };
269
270 Ok(keystore)
271}