use aes_gcm::aead::{Aead, KeyInit};
use aes_gcm::{AeadCore, Aes256Gcm};
use argon2::password_hash::rand_core::{OsRng, RngCore};
use argon2::password_hash::{PasswordHasher, SaltString};
use argon2::{Algorithm, Argon2, Params, Version};
use base64::engine::general_purpose::STANDARD as base64_standard;
use base64::Engine;
use rsa::{Oaep, RsaPublicKey};
use serde::Serialize;
use sha2::Sha256;
use zeroize::Zeroize;
use crate::{IronCryptError, PasswordCriteria};
#[derive(Clone, Debug)]
pub struct Argon2Config {
pub memory_cost: u32, pub time_cost: u32, pub parallelism: u32, }
impl Default for Argon2Config {
fn default() -> Self {
Self {
memory_cost: 65536,
time_cost: 3,
parallelism: 1,
}
}
}
#[derive(Serialize, Debug)]
pub struct EncryptedData {
pub key_version: String,
pub encrypted_symmetric_key: String,
pub nonce: String,
pub ciphertext: String,
pub password_hash: Option<String>,
}
pub fn encrypt_data_with_criteria(
data: &[u8],
password: &mut String, public_key: &RsaPublicKey,
criteria: &PasswordCriteria,
key_version: &str,
argon_cfg: Argon2Config,
hash_password: bool,
) -> Result<EncryptedData, IronCryptError> {
criteria.validate(password)?;
let password_hash = if hash_password {
let params = Params::new(
argon_cfg.memory_cost,
argon_cfg.time_cost,
argon_cfg.parallelism,
None,
)?;
let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
let salt = SaltString::generate(&mut OsRng);
let hash_str = argon2
.hash_password(password.as_bytes(), &salt)?
.to_string();
Some(base64_standard.encode(hash_str))
} else {
None
};
let mut symmetric_key = [0u8; 32];
OsRng.fill_bytes(&mut symmetric_key);
let cipher = Aes256Gcm::new_from_slice(&symmetric_key)
.map_err(|e| IronCryptError::EncryptionError(e.to_string()))?;
let nonce = Aes256Gcm::generate_nonce(&mut OsRng); let ciphertext = cipher.encrypt(&nonce, data)
.map_err(|e| IronCryptError::EncryptionError(format!("AES encryption error: {e}")))?;
let padding = Oaep::new::<Sha256>();
let encrypted_symmetric_key = public_key
.encrypt(&mut OsRng, padding, &symmetric_key)
.map_err(|e| {
IronCryptError::EncryptionError(format!("RSA symmetric key encryption error: {e}"))
})?;
let result = EncryptedData {
key_version: key_version.to_string(),
encrypted_symmetric_key: base64_standard.encode(&encrypted_symmetric_key),
nonce: base64_standard.encode(nonce),
ciphertext: base64_standard.encode(&ciphertext),
password_hash,
};
symmetric_key.zeroize();
password.zeroize();
Ok(result)
}