116 lines
3.7 KiB
Rust
116 lines
3.7 KiB
Rust
use aes_gcm::{Aes256Gcm, Key, Nonce};
|
|
use aes_gcm::aead::{Aead, AeadCore, OsRng};
|
|
use sha2::Sha256;
|
|
use base64::{Engine as _, engine::general_purpose::STANDARD};
|
|
use std::fmt;
|
|
|
|
#[derive(Debug)]
|
|
pub enum CryptoError {
|
|
KeyDerivationFailed,
|
|
EncryptionFailed,
|
|
DecryptionFailed,
|
|
InvalidKey,
|
|
InvalidNonce,
|
|
}
|
|
|
|
impl fmt::Display for CryptoError {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
CryptoError::KeyDerivationFailed => write!(f, "Failed to derive encryption key from password"),
|
|
CryptoError::EncryptionFailed => write!(f, "Failed to encrypt data"),
|
|
CryptoError::DecryptionFailed => write!(f, "Failed to decrypt data"),
|
|
CryptoError::InvalidKey => write!(f, "Invalid encryption key"),
|
|
CryptoError::InvalidNonce => write!(f, "Invalid nonce"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for CryptoError {}
|
|
|
|
pub struct CryptoManager {
|
|
key: Key<Aes256Gcm>,
|
|
}
|
|
|
|
impl CryptoManager {
|
|
const KEY_SIZE: usize = 32;
|
|
|
|
pub fn from_password(password: &str, salt: &[u8; 32]) -> Result<Self, CryptoError> {
|
|
let password_bytes = password.as_bytes();
|
|
let mut key_material = Vec::with_capacity(password_bytes.len() + salt.len());
|
|
key_material.extend_from_slice(password_bytes);
|
|
key_material.extend_from_slice(salt);
|
|
|
|
let hash = Sha256::digest(&key_material);
|
|
let key = Key::<Aes256Gcm>::from_slice(&hash);
|
|
|
|
Ok(CryptoManager { key: *key })
|
|
}
|
|
|
|
pub fn encrypt(&self, plaintext: &[u8]) -> Result<(Vec<u8>, Vec<u8>), CryptoError> {
|
|
let nonce = Aes256Gcm::generate_nonce(OsRng);
|
|
let ciphertext = Aes256Gcm::encrypt(&nonce, plaintext, None)
|
|
.map_err(|_| CryptoError::EncryptionFailed)?;
|
|
|
|
Ok((nonce.to_vec(), ciphertext))
|
|
}
|
|
|
|
pub fn decrypt(&self, nonce: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, CryptoError> {
|
|
let nonce = Nonce::from_slice(nonce);
|
|
let plaintext = Aes256Gcm::decrypt(nonce, ciphertext, None)
|
|
.map_err(|_| CryptoError::DecryptionFailed)?;
|
|
|
|
Ok(plaintext)
|
|
}
|
|
|
|
pub fn encrypt_base64(&self, plaintext: &str) -> Result<String, CryptoError> {
|
|
let (nonce, ciphertext) = self.encrypt(plaintext.as_bytes())?;
|
|
|
|
let mut combined = Vec::with_capacity(nonce.len() + ciphertext.len());
|
|
combined.extend_from_slice(&nonce);
|
|
combined.extend_from_slice(&ciphertext);
|
|
|
|
Ok(STANDARD.encode(combined))
|
|
}
|
|
|
|
pub fn decrypt_base64(&self, encrypted_data: &str) -> Result<String, CryptoError> {
|
|
let combined = STANDARD.decode(encrypted_data)
|
|
.map_err(|_| CryptoError::DecryptionFailed)?;
|
|
|
|
if combined.len() < 12 + 16 {
|
|
return Err(CryptoError::DecryptionFailed);
|
|
}
|
|
|
|
let nonce_len = 12;
|
|
let nonce = &combined[..nonce_len];
|
|
let ciphertext = &combined[nonce_len..];
|
|
|
|
let plaintext = self.decrypt(nonce, ciphertext)?;
|
|
|
|
String::from_utf8(plaintext)
|
|
.map_err(|_| CryptoError::DecryptionFailed)
|
|
}
|
|
}
|
|
|
|
pub fn generate_salt() -> [u8; 32] {
|
|
let mut salt = [0u8; 32];
|
|
rand::Rng::fill(&mut rand::thread_rng(), &mut salt);
|
|
salt
|
|
}
|
|
|
|
pub fn salt_to_base64(salt: &[u8; 32]) -> String {
|
|
STANDARD.encode(salt)
|
|
}
|
|
|
|
pub fn salt_from_base64(salt_str: &str) -> Result<[u8; 32], CryptoError> {
|
|
let decoded = STANDARD.decode(salt_str)
|
|
.map_err(|_| CryptoError::DecryptionFailed)?;
|
|
|
|
if decoded.len() != 32 {
|
|
return Err(CryptoError::DecryptionFailed);
|
|
}
|
|
|
|
let mut salt = [0u8; 32];
|
|
salt.copy_from_slice(&decoded);
|
|
Ok(salt)
|
|
}
|