1use argon2::password_hash::rand_core::{OsRng, RngCore};
5use argon2::{Argon2, ParamsBuilder};
6use chacha20poly1305::aead::{Aead, AeadCore};
7use chacha20poly1305::{KeyInit, XChaCha20Poly1305};
8use zeroize::Zeroize;
10
11pub mod error;
12pub mod ffi;
13pub mod ffi_utils;
14
15pub use crate::error::SeasmokeError;
16
17const NONCE_LEN: usize = 24;
18const SALT_LEN: usize = 32;
19const KEY_LEN: usize = 32;
20
21pub type Result<T> = std::result::Result<T, SeasmokeError>;
22pub type Key = [u8; KEY_LEN];
23
24pub fn decrypt_data(password: &str, data: &[u8]) -> Result<Vec<u8>> {
26 assert!(data.len() >= SALT_LEN + NONCE_LEN, "data is too short");
27 let salt = &data[data.len() - SALT_LEN..];
28 let nonce = &data[data.len() - SALT_LEN - NONCE_LEN..data.len() - SALT_LEN];
29 let key = derive_key(password.as_bytes(), salt)?;
30
31 let decryptor = XChaCha20Poly1305::new(&key.into());
32 let decipher_data = decryptor
33 .decrypt(nonce.into(), &data[0..data.len() - SALT_LEN - NONCE_LEN])
34 .map_err(SeasmokeError::DecryptionError)?;
35
36 Ok(decipher_data)
38}
39
40pub fn encrypt_data(password: &str, data: &[u8]) -> Result<Vec<u8>> {
42 let mut salt = [0u8; SALT_LEN];
44 OsRng
45 .try_fill_bytes(&mut salt)
46 .map_err(SeasmokeError::RandomSaltError)?;
47 let key = derive_key(password.as_bytes(), &salt)?;
48
49 let mut nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
50 assert!(nonce.len() == NONCE_LEN);
51
52 let encryptor = XChaCha20Poly1305::new(&key.into());
53 let mut cipher_data = encryptor
54 .encrypt(&nonce, data)
55 .map_err(SeasmokeError::EncryptionError)?;
56 cipher_data.extend_from_slice(&nonce);
57 cipher_data.extend_from_slice(&salt);
58
59 nonce.zeroize();
60 salt.zeroize();
61
62 Ok(cipher_data)
63}
64
65fn default_secure_argon<'key>() -> Result<Argon2<'key>> {
66 let params = ParamsBuilder::default()
68 .t_cost(10)
69 .output_len(32)
70 .build()
71 .map_err(SeasmokeError::KeyDerivationParamEarror)?;
72
73 Ok(Argon2::new(
74 argon2::Algorithm::Argon2id,
75 argon2::Version::V0x13,
76 params,
77 ))
78}
79
80fn derive_key(password: &[u8], salt: &[u8]) -> Result<[u8; 32]> {
81 let mut output_key_material = [0u8; 32]; default_secure_argon()?
83 .hash_password_into(password, salt, &mut output_key_material)
84 .map_err(SeasmokeError::KeyDerivationError)?;
85
86 Ok(output_key_material)
87}
88
89#[cfg(test)]
90mod tests {
91 use argon2::{password_hash::SaltString, PasswordHash, PasswordVerifier};
92
93 use super::*;
94
95 #[test]
96 fn test_kye_derivation() {
97 let password = b"hunter42"; let mut rnd = argon2::password_hash::rand_core::OsRng;
99 let mut salt = [0; 32];
100 rnd.try_fill_bytes(&mut salt).unwrap();
101
102 let mut output_key_material = [0u8; 32]; Argon2::default()
104 .hash_password_into(password, &salt, &mut output_key_material)
105 .unwrap();
106
107 assert_ne!(salt, [0u8; 32]);
108 assert_ne!(output_key_material, [0u8; 32]);
109 }
110
111 #[test]
112 fn test_password_hash() {
113 let hash_string = "$argon2i$v=19$m=65536,t=1,p=1$c29tZXNhbHQAAAAAAAAAAA$+r0d29hqEB0yasKr55ZgICsQGSkl0v0kgwhd+U3wyRo";
115 let input_password = "password";
116
117 let password_hash = PasswordHash::new(hash_string).expect("invalid password hash");
118
119 let algs: &[&dyn PasswordVerifier] = &[&Argon2::default()];
121
122 password_hash
123 .verify_password(algs, input_password)
124 .expect("invalid password");
125 }
126
127 #[test]
128 fn test_generate_a_password_hash() {
129 let password = b"hunter42"; let mut rnd = OsRng;
131 let salt = SaltString::generate(&mut rnd);
132 let password_hash = PasswordHash::generate(Argon2::default(), password, &salt).unwrap();
133
134 assert_ne!(password_hash.to_string(), "");
135 }
136
137 #[test]
138 fn test_encryption_round_trip() {
139 let password = "resistance is futile";
140 let data = b"lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
142
143 let cipher_data = encrypt_data(password, data).unwrap();
144 let decipher_data = decrypt_data(password, &cipher_data).unwrap();
145
146 assert_ne!(data, cipher_data.as_slice());
147 assert_eq!(data, decipher_data.as_slice());
148 }
149}