cml_crypto/
emip3.rs

1use cryptoxide::chacha20poly1305::ChaCha20Poly1305;
2use cryptoxide::hmac::Hmac;
3use cryptoxide::pbkdf2::pbkdf2;
4use cryptoxide::sha2::Sha512;
5use hex::ToHex;
6
7use std::iter::repeat;
8
9// taken from js-cardano-wasm
10
11mod password_encryption_parameter {
12    pub const ITER: u32 = 19_162;
13    pub const SALT_SIZE: usize = 32;
14    pub const NONCE_SIZE: usize = 12;
15    pub const KEY_SIZE: usize = 32;
16    pub const TAG_SIZE: usize = 16;
17
18    pub const METADATA_SIZE: usize = SALT_SIZE + NONCE_SIZE + TAG_SIZE;
19
20    pub const SALT_START: usize = 0;
21    pub const SALT_END: usize = SALT_START + SALT_SIZE;
22    pub const NONCE_START: usize = SALT_END;
23    pub const NONCE_END: usize = NONCE_START + NONCE_SIZE;
24    pub const TAG_START: usize = NONCE_END;
25    pub const TAG_END: usize = TAG_START + TAG_SIZE;
26    pub const ENCRYPTED_START: usize = TAG_END;
27}
28
29#[derive(Debug, thiserror::Error)]
30pub enum EmIP3Error {
31    #[error("Invalid hex: {0}")]
32    InvalidHex(#[from] hex::FromHexError),
33    #[error("Salt len must be {0}, found {1} bytes")]
34    SaltLen(usize, usize),
35    #[error("Nonce len must be {0}, found {1} bytes")]
36    NonceLen(usize, usize),
37    #[error("Password len cannot be 0")]
38    EmptyPassword,
39    #[error("Missing input data: Needed {0}, found {1} bytes")]
40    MissingInputData(usize, usize),
41    #[error("Decryption failed")]
42    DecryptionFailed,
43}
44
45/// Encrypt using Emip3: https://github.com/Emurgo/EmIPs/blob/master/specs/emip-003.md
46pub fn emip3_encrypt_with_password(
47    password: &str,
48    salt: &str,
49    nonce: &str,
50    data: &str,
51) -> Result<String, EmIP3Error> {
52    use password_encryption_parameter::*;
53
54    let password = hex::decode(password)?;
55    let salt = hex::decode(salt)?;
56    let nonce = hex::decode(nonce)?;
57    let data = hex::decode(data)?;
58
59    if salt.len() != SALT_SIZE {
60        return Err(EmIP3Error::SaltLen(SALT_SIZE, salt.len()));
61    }
62    if nonce.len() != NONCE_SIZE {
63        return Err(EmIP3Error::NonceLen(NONCE_SIZE, nonce.len()));
64    }
65    if password.is_empty() {
66        return Err(EmIP3Error::EmptyPassword);
67    }
68
69    let key = {
70        let mut mac = Hmac::new(Sha512::new(), &password);
71        let mut key: Vec<u8> = repeat(0).take(KEY_SIZE).collect();
72        pbkdf2(&mut mac, &salt[..], ITER, &mut key);
73        key
74    };
75
76    let mut tag = [0; TAG_SIZE];
77    let mut encrypted: Vec<u8> = repeat(0).take(data.len()).collect();
78    {
79        ChaCha20Poly1305::new(&key, &nonce, &[]).encrypt(&data, &mut encrypted, &mut tag);
80    }
81
82    let mut output = Vec::with_capacity(data.len() + METADATA_SIZE);
83    output.extend_from_slice(&salt);
84    output.extend_from_slice(&nonce);
85    output.extend_from_slice(&tag);
86    output.extend_from_slice(&encrypted);
87
88    Ok(output.encode_hex::<String>())
89}
90
91/// Decrypt using Emip3: https://github.com/Emurgo/EmIPs/blob/master/specs/emip-003.md
92pub fn emip3_decrypt_with_password(password: &str, data: &str) -> Result<String, EmIP3Error> {
93    use password_encryption_parameter::*;
94    let password = hex::decode(password)?;
95    let data = hex::decode(data)?;
96
97    if data.len() <= METADATA_SIZE {
98        // not enough input to decrypt.
99        return Err(EmIP3Error::MissingInputData(METADATA_SIZE, data.len()));
100    }
101
102    let salt = &data[SALT_START..SALT_END];
103    let nonce = &data[NONCE_START..NONCE_END];
104    let tag = &data[TAG_START..TAG_END];
105    let encrypted = &data[ENCRYPTED_START..];
106
107    let key = {
108        let mut mac = Hmac::new(Sha512::new(), &password);
109        let mut key: Vec<u8> = repeat(0).take(KEY_SIZE).collect();
110        pbkdf2(&mut mac, salt, ITER, &mut key);
111        key
112    };
113
114    let mut decrypted: Vec<u8> = repeat(0).take(encrypted.len()).collect();
115    let decryption_succeed =
116        { ChaCha20Poly1305::new(&key, nonce, &[]).decrypt(encrypted, &mut decrypted, tag) };
117
118    if decryption_succeed {
119        Ok(decrypted.encode_hex::<String>())
120    } else {
121        Err(EmIP3Error::DecryptionFailed)
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    #[test]
130    fn encryption() {
131        let password = "70617373776f7264";
132        let salt = "50515253c0c1c2c3c4c5c6c750515253c0c1c2c3c4c5c6c750515253c0c1c2c3";
133        let nonce = "50515253c0c1c2c3c4c5c6c7";
134        let data = "736f6d65206461746120746f20656e6372797074";
135        let encrypted_data = emip3_encrypt_with_password(password, salt, nonce, data).unwrap();
136        let decrypted_data = emip3_decrypt_with_password(password, &encrypted_data).unwrap();
137        assert_eq!(data, decrypted_data);
138    }
139}