cardano_serialization_lib/
emip3.rs

1use super::error::JsError;
2use super::*;
3
4use cryptoxide::chacha20poly1305::ChaCha20Poly1305;
5use cryptoxide::hmac::Hmac;
6use cryptoxide::pbkdf2::pbkdf2;
7use cryptoxide::sha2::Sha512;
8use hex::ToHex;
9
10use std::iter::repeat;
11
12// taken from js-cardano-wasm
13
14mod password_encryption_parameter {
15    pub const ITER: u32 = 19_162;
16    pub const SALT_SIZE: usize = 32;
17    pub const NONCE_SIZE: usize = 12;
18    pub const KEY_SIZE: usize = 32;
19    pub const TAG_SIZE: usize = 16;
20
21    pub const METADATA_SIZE: usize = SALT_SIZE + NONCE_SIZE + TAG_SIZE;
22
23    pub const SALT_START: usize = 0;
24    pub const SALT_END: usize = SALT_START + SALT_SIZE;
25    pub const NONCE_START: usize = SALT_END;
26    pub const NONCE_END: usize = NONCE_START + NONCE_SIZE;
27    pub const TAG_START: usize = NONCE_END;
28    pub const TAG_END: usize = TAG_START + TAG_SIZE;
29    pub const ENCRYPTED_START: usize = TAG_END;
30}
31
32#[wasm_bindgen]
33pub fn encrypt_with_password(
34    password: &str,
35    salt: &str,
36    nonce: &str,
37    data: &str,
38) -> Result<String, JsError> {
39    use password_encryption_parameter::*;
40
41    let password = hex::decode(password).map_err(|e| JsError::from_str(&e.to_string()))?;
42    let salt = hex::decode(salt).map_err(|e| JsError::from_str(&e.to_string()))?;
43    let nonce = hex::decode(nonce).map_err(|e| JsError::from_str(&e.to_string()))?;
44    let data = hex::decode(data).map_err(|e| JsError::from_str(&e.to_string()))?;
45
46    if salt.len() != SALT_SIZE {
47        return Err(JsError::from_str(&format!(
48            "salt len must be {}, found {} bytes",
49            SALT_SIZE,
50            salt.len()
51        )));
52    }
53    if nonce.len() != NONCE_SIZE {
54        return Err(JsError::from_str(&format!(
55            "nonce len must be {}, found {} bytes",
56            NONCE_SIZE,
57            nonce.len()
58        )));
59    }
60    if password.len() == 0 {
61        return Err(JsError::from_str("Password len cannot be 0"));
62    }
63
64    let key = {
65        let mut mac = Hmac::new(Sha512::new(), &password);
66        let mut key: Vec<u8> = repeat(0).take(KEY_SIZE).collect();
67        pbkdf2(&mut mac, &salt[..], ITER, &mut key);
68        key
69    };
70
71    let mut tag = [0; TAG_SIZE];
72    let mut encrypted: Vec<u8> = repeat(0).take(data.len()).collect();
73    {
74        ChaCha20Poly1305::new(&key, &nonce, &[]).encrypt(&data, &mut encrypted, &mut tag);
75    }
76
77    let mut output = Vec::with_capacity(data.len() + METADATA_SIZE);
78    output.extend_from_slice(&salt);
79    output.extend_from_slice(&nonce);
80    output.extend_from_slice(&tag);
81    output.extend_from_slice(&encrypted);
82
83    Ok(output.encode_hex::<String>())
84}
85
86#[wasm_bindgen]
87pub fn decrypt_with_password(password: &str, data: &str) -> Result<String, JsError> {
88    use password_encryption_parameter::*;
89    let password = hex::decode(password).map_err(|e| JsError::from_str(&e.to_string()))?;
90    let data = hex::decode(data).map_err(|e| JsError::from_str(&e.to_string()))?;
91
92    if data.len() <= METADATA_SIZE {
93        // not enough input to decrypt.
94        return Err(JsError::from_str("Missing input data"));
95    }
96
97    let salt = &data[SALT_START..SALT_END];
98    let nonce = &data[NONCE_START..NONCE_END];
99    let tag = &data[TAG_START..TAG_END];
100    let encrypted = &data[ENCRYPTED_START..];
101
102    let key = {
103        let mut mac = Hmac::new(Sha512::new(), &password);
104        let mut key: Vec<u8> = repeat(0).take(KEY_SIZE).collect();
105        pbkdf2(&mut mac, &salt[..], ITER, &mut key);
106        key
107    };
108
109    let mut decrypted: Vec<u8> = repeat(0).take(encrypted.len()).collect();
110    let decryption_succeed =
111        { ChaCha20Poly1305::new(&key, &nonce, &[]).decrypt(&encrypted, &mut decrypted, &tag) };
112
113    if decryption_succeed {
114        Ok(decrypted.encode_hex::<String>())
115    } else {
116        Err(JsError::from_str("Decryption error"))
117    }
118}