ssh_utils_lib/config/
crypto.rs1use anyhow::{Context, Result};
2use argon2::Config;
3use openssl::symm::{Cipher, Crypter, Mode};
4use rand::{thread_rng, Rng};
5use sha2::{Digest, Sha256};
6
7pub fn derive_sha256_digest(password: &str) -> [u8; 16] {
11 let mut hasher = Sha256::new();
12 hasher.update(password.as_bytes());
13 let result = hasher.finalize();
14 let mut salt = [0u8; 16];
15 salt.copy_from_slice(&result[..16]);
16 salt
17}
18
19pub fn derive_key_from_password(password: &str) -> Result<[u8; 32]> {
23 let salt = derive_sha256_digest(password);
25
26 let config = Config::owasp3();
27 let key = argon2::hash_raw(password.as_bytes(), &salt, &config)
28 .context("Failed to derive key using Argon2")?;
29 let mut key_arr = [0u8; 32];
30 key_arr.copy_from_slice(&key);
31 Ok(key_arr)
32}
33
34pub fn generate_iv() -> [u8; 16] {
38 let mut iv = [0u8; 16];
39 let mut rng = thread_rng();
40 rng.fill(&mut iv);
41 iv
42}
43
44pub fn aes_encrypt(key: &[u8], iv: &[u8], data: &[u8]) -> Result<Vec<u8>> {
45 let cipher = Cipher::aes_256_ctr();
46 let mut crypter =
47 Crypter::new(cipher, Mode::Encrypt, key, Some(iv)).context("Failed to create Crypter")?;
48 let mut ciphertext = vec![0; data.len() + cipher.block_size()];
49 let mut count = crypter
50 .update(data, &mut ciphertext)
51 .context("Failed to encrypt data")?;
52 count += crypter
53 .finalize(&mut ciphertext[count..])
54 .context("Failed to finalize encryption")?;
55 ciphertext.truncate(count);
56 Ok(ciphertext)
57}
58
59pub fn aes_decrypt(key: &[u8], iv: &[u8], data: &[u8]) -> Result<Vec<u8>> {
60 let cipher = Cipher::aes_256_ctr();
62 let mut crypter =
63 Crypter::new(cipher, Mode::Decrypt, key, Some(iv)).context("Failed to create Crypter")?;
64 let mut plaintext = vec![0; data.len() + cipher.block_size()];
65 let mut count = crypter
66 .update(data, &mut plaintext)
67 .context("Failed to decrypt data")?;
68 count += crypter
69 .finalize(&mut plaintext[count..])
70 .context("Failed to finalize decryption")?;
71 plaintext.truncate(count);
72 Ok(plaintext)
73}
74
75#[cfg(test)]
76mod tests {
77 use std::{io::Write, process::Command};
78
79 use tempfile::NamedTempFile;
80
81 use super::*;
82
83 #[test]
84 fn test_derive_key_from_password() {
85 let password = "super_secret_password";
86 let salt = b"super_secret_password";
87 let expected_key_hex = "66e5467d6adc707c5fe42c2516de285204f4ce590612e58eddaab21b763aaca2";
92 let expected_key = hex::decode(expected_key_hex).expect("Decoding failed");
93
94 let derived_key_result = derive_key_from_password(password);
95
96 let config = Config::owasp3();
97 let key = argon2::hash_raw(password.as_bytes(), salt, &config).unwrap();
98
99 assert!(derived_key_result.is_ok());
101
102 let derived_key = derived_key_result.unwrap();
103
104 assert_eq!(derived_key.len(), 32);
106
107 assert!(derived_key.iter().any(|&byte| byte != 0));
109
110 assert_eq!(key, expected_key);
112 }
113
114 #[test]
115 fn test_derive_sha256_digest() {
116 let password = "super_secret_password";
117
118 let output = std::process::Command::new("sh")
120 .arg("-c")
121 .arg(format!(
122 "echo -n \"{}\" | sha256sum | awk '{{print $1}}' | cut -c 1-32",
123 password
124 ))
125 .output()
126 .expect("Failed to execute command");
127
128 let expected_digest_hex = String::from_utf8(output.stdout)
129 .expect("Failed to convert output to string")
130 .trim()
131 .to_string();
132
133 let expected_digest = hex::decode(expected_digest_hex).expect("Decoding failed");
134
135 let derived_digest = derive_sha256_digest(password);
136
137 assert_eq!(derived_digest.len(), 16);
139
140 assert_eq!(derived_digest, expected_digest.as_slice());
142 }
143
144 #[test]
145 fn test_aes_encrypt_decrypt() {
146 let key = b"01234567890123456789012345678901"; let iv = b"0123456789012345"; let data = b"Hello, AES encryption!";
149
150 let mut data_file = NamedTempFile::new().expect("Failed to create temporary file");
152 data_file.write_all(data).expect("Failed to write data to file");
153 let data_file_path = data_file.path().to_str().unwrap();
154
155 let expected_encrypted_output = Command::new("openssl")
157 .arg("enc")
158 .arg("-aes-256-ctr")
159 .arg("-in")
160 .arg(data_file_path)
161 .arg("-K")
162 .arg(hex::encode(key))
163 .arg("-iv")
164 .arg(hex::encode(iv))
165 .output()
166 .expect("Failed to execute openssl command");
167 let expected_encrypted_data = expected_encrypted_output.stdout;
168
169 let encrypted_data = aes_encrypt(key, iv, data).expect("Failed to encrypt data");
171 assert!(!encrypted_data.is_empty(), "Encrypted data should not be empty");
172 assert_eq!(encrypted_data, expected_encrypted_data, "Encrypted data should match the expected value");
173
174 let mut encrypted_file = NamedTempFile::new().expect("Failed to create temporary file");
176 encrypted_file.write_all(&encrypted_data).expect("Failed to write encrypted data to file");
177 let encrypted_file_path = encrypted_file.path().to_str().unwrap();
178
179 let expected_decrypted_output = Command::new("openssl")
181 .arg("enc")
182 .arg("-d")
183 .arg("-aes-256-ctr")
184 .arg("-in")
185 .arg(encrypted_file_path)
186 .arg("-K")
187 .arg(hex::encode(key))
188 .arg("-iv")
189 .arg(hex::encode(iv))
190 .output()
191 .expect("Failed to execute openssl command");
192 let expected_decrypted_data = expected_decrypted_output.stdout;
193
194 let decrypted_data = aes_decrypt(key, iv, &encrypted_data).expect("Failed to decrypt data");
196 assert_eq!(decrypted_data, data, "Decrypted data should match original data");
197 assert_eq!(decrypted_data, expected_decrypted_data, "Decrypted data should match the expected value");
198 }
199}