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
9mod 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
45pub 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
91pub 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 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}