mod key_manager;
pub mod provider;
mod zero_knowledge;
pub use key_manager::KeyManager;
pub use provider::{
CryptoProvider, CryptoKey, HashOutput,
init_provider, provider as get_provider, is_fips_build, provider_name,
hash_content, derive_key, generate_random_key,
};
pub use zero_knowledge::{
ZkeConfig, ZkeDerivedKeys, ZkeKeyDerivation, ZkeMode, ZkeRequestContext,
ZeroKnowledgeSession, NonceTracker, TimestampValidator,
};
use crate::{Result, Error};
pub type EncryptionKey = [u8; 32];
pub type Nonce = [u8; 12];
pub fn encrypt(key: &EncryptionKey, plaintext: &[u8]) -> Result<Vec<u8>> {
use aes_gcm::{
aead::{Aead, KeyInit},
Aes256Gcm, Nonce as AesNonce,
};
let cipher = Aes256Gcm::new(key.into());
let nonce_bytes: Nonce = rand::random();
let nonce = AesNonce::from_slice(&nonce_bytes);
let ciphertext = cipher
.encrypt(nonce, plaintext)
.map_err(|e| Error::encryption(format!("Encryption failed: {}", e)))?;
let mut result = nonce_bytes.to_vec();
result.extend_from_slice(&ciphertext);
Ok(result)
}
pub fn decrypt(key: &EncryptionKey, ciphertext_with_nonce: &[u8]) -> Result<Vec<u8>> {
use aes_gcm::{
aead::{Aead, KeyInit},
Aes256Gcm, Nonce as AesNonce,
};
if ciphertext_with_nonce.len() < 12 {
return Err(Error::encryption("Ciphertext too short"));
}
let nonce_bytes = ciphertext_with_nonce.get(0..12)
.ok_or_else(|| Error::encryption("Ciphertext too short for nonce"))?;
let ciphertext = ciphertext_with_nonce.get(12..)
.ok_or_else(|| Error::encryption("Ciphertext too short for data"))?;
let cipher = Aes256Gcm::new(key.into());
let nonce = AesNonce::from_slice(nonce_bytes);
let plaintext = cipher
.decrypt(nonce, ciphertext)
.map_err(|e| Error::encryption(format!("Decryption failed: {}", e)))?;
Ok(plaintext)
}
pub fn derive_key_from_password(password: &str, salt: &[u8]) -> Result<EncryptionKey> {
use argon2::{Argon2, PasswordHasher};
use argon2::password_hash::SaltString;
let salt_string = SaltString::encode_b64(salt)
.map_err(|e| Error::encryption(format!("Salt encoding failed: {}", e)))?;
let argon2 = Argon2::default();
let hash = argon2
.hash_password(password.as_bytes(), &salt_string)
.map_err(|e| Error::encryption(format!("Key derivation failed: {}", e)))?;
let hash_bytes = hash.hash.ok_or_else(|| Error::encryption("No hash generated"))?;
let key_bytes = hash_bytes.as_bytes();
if key_bytes.len() < 32 {
return Err(Error::encryption("Derived key too short"));
}
let mut key = [0u8; 32];
key.copy_from_slice(key_bytes.get(0..32).ok_or_else(|| Error::encryption("Derived key too short"))?);
Ok(key)
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn test_encrypt_decrypt() {
let key: EncryptionKey = rand::random();
let plaintext = b"Hello, HeliosDB Lite!";
let ciphertext = encrypt(&key, plaintext)
.expect("Failed to encrypt plaintext");
let decrypted = decrypt(&key, &ciphertext)
.expect("Failed to decrypt ciphertext");
assert_eq!(plaintext, &decrypted[..]);
}
#[test]
fn test_key_derivation() {
let password = "supersecret";
let salt = b"randomsalt123456";
let key = derive_key_from_password(password, salt)
.expect("Failed to derive key from password");
assert_eq!(key.len(), 32);
}
}