web3_keystore/
lib.rs

1//! Library to encrypt and decrypt keystores as per the
2//! [Web3 Secret Storage Definition](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition).
3//!
4//! This is a fork of
5//! [eth-keystore-rs](https://docs.rs/eth-keystore/latest/eth_keystore/) which
6//! does not write to disc automatically so is
7//! easier to integrate with WASM and storage
8//! destinations other than the file system.
9
10#![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)]
25/// An error thrown when encrypting or decrypting keystores.
26pub enum KeyStoreError {
27    /// An error thrown while decrypting an encrypted
28    /// keystore if the computed MAC does not
29    /// match the MAC declared in the keystore.
30    #[error("Mac Mismatch")]
31    MacMismatch,
32    /// Invalid scrypt parameters
33    #[error("scrypt {0:?}")]
34    ScryptInvalidParams(scrypt::errors::InvalidParams),
35    /// Invalid scrypt output length
36    #[error("scrypt {0:?}")]
37    ScryptInvalidOuputLen(scrypt::errors::InvalidOutputLen),
38    /// Invalid aes key nonce length
39    #[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
77/// Creates a new keystore using a random 32 byte secret and the
78/// [Scrypt](https://tools.ietf.org/html/rfc7914.html)
79/// key derivation function.
80///
81/// The keystore is encrypted by a key derived from the provided `password`.
82///
83/// # Example
84///
85/// ```no_run
86/// use web3_keystore::new_random;
87/// let mut rng = rand::thread_rng();
88/// let password = "super-secret-password";
89/// let (keystore, secret) = new_random(&mut rng, password).unwrap();
90/// assert_eq!(32, secret.len());
91/// ```
92pub 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
104/// Decrypts an encrypted keystore using the provided `password`.
105///
106/// Decryption supports the
107/// [Scrypt](https://tools.ietf.org/html/rfc7914.html) and
108/// [PBKDF2](https://ietf.org/rfc/rfc2898.txt) key derivation functions.
109///
110/// # Example
111///
112/// ```
113/// use web3_keystore::{decrypt, new_random};
114/// let mut rng = rand::thread_rng();
115/// let password = "super-secret-password";
116/// let (keystore, secret) = new_random(&mut rng, password).unwrap();
117/// let private_key = decrypt(&keystore, password).unwrap();
118/// assert_eq!(secret, private_key);
119/// ```
120pub fn decrypt<S>(
121    keystore: &KeyStore,
122    password: S,
123) -> Result<Vec<u8>, KeyStoreError>
124where
125    S: AsRef<[u8]>,
126{
127    // Derive the key.
128    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            // TODO: use int_log https://github.com/rust-lang/rust/issues/70887
153            // TODO: when it is stable
154            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    // Derive the MAC from the derived key and ciphertext.
167    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    // Decrypt the private key bytes using AES-128-CTR
177    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
188/// Encrypts the given private key using the
189/// [Scrypt](https://tools.ietf.org/html/rfc7914.html)
190/// password-based key derivation function.
191///
192/// # Example
193///
194/// ```
195/// use web3_keystore::{encrypt, decrypt};
196/// use rand::Rng;
197/// let mut rng = rand::thread_rng();
198/// let secret: [u8; 32] = rng.gen();
199/// let password = "super-secret-password";
200/// let address = Some(String::from("0x0"));
201/// let keystore = encrypt(
202///     &mut rng, &secret, password, address, None).unwrap();
203/// let private_key = decrypt(&keystore, password).unwrap();
204/// assert_eq!(secret.to_vec(), private_key);
205/// ```
206pub 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    // Generate a random salt.
219    let mut salt = vec![0u8; DEFAULT_KEY_SIZE];
220    rng.fill_bytes(salt.as_mut_slice());
221
222    // Derive the key.
223    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    // Encrypt the private key using AES-128-CTR.
232    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    // Calculate the MAC.
241    let mac = Keccak256::new()
242        .chain(&key[16..32])
243        .chain(&ciphertext)
244        .finalize();
245
246    let id = Uuid::new_v4();
247
248    // Construct and serialize the encrypted JSON keystore.
249    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}