#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(feature = "derive")]
extern crate self as cryptainer;
extern crate alloc;
use alloc::vec;
use alloc::vec::Vec;
#[cfg(feature = "derive")]
pub use cryptainer_derive::Cryptainer;
#[cfg(feature = "std")]
use std::{
fs::File,
io::{Read, Write},
};
use aes_gcm::{
Aes256Gcm, Nonce,
aead::{Aead, KeyInit},
};
use argon2::Argon2;
use thiserror::Error;
use rand::{CryptoRng, Rng};
#[cfg(feature = "zeroize")]
use zeroize::Zeroizing;
#[derive(Error, Debug)]
pub enum CryptError {
#[error("failed to hash password")]
HashingError,
#[error("encryption error: {0}")]
EncryptionError(#[from] aes_gcm::Error),
#[error("invalid key or nonce length")]
InvalidLength,
#[cfg(feature = "std")]
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("invalid data format: {0}")]
InvalidData(&'static str),
#[cfg(feature = "derive")]
#[error("serialization error")]
SerializationError,
}
#[cfg(feature = "derive")]
#[doc(hidden)]
pub mod __private {
pub use bincode;
pub use rand;
pub use serde;
}
#[derive(Debug)]
pub struct CryptData {
pub ciphertext: Vec<u8>,
pub salt: Vec<u8>,
pub nonce_bytes: Vec<u8>,
}
impl CryptData {
fn from_bytes(data: &[u8]) -> Result<Self, CryptError> {
if data.len() < 44 {
return Err(CryptError::InvalidData(
"File is too short to contain valid encrypted data",
));
}
Ok(Self {
salt: data[0..16].to_vec(),
nonce_bytes: data[16..28].to_vec(),
ciphertext: data[28..].to_vec(),
})
}
}
pub fn encrypt_data(
password: &str,
plaintext: &[u8],
rng: &mut (impl Rng + CryptoRng),
) -> Result<CryptData, CryptError> {
let mut salt = vec![0u8; 16];
rng.fill_bytes(&mut salt);
#[cfg(feature = "zeroize")]
let mut derived_key = Zeroizing::new(vec![0u8; 32]);
#[cfg(not(feature = "zeroize"))]
let mut derived_key = vec![0u8; 32];
Argon2::default()
.hash_password_into(password.as_bytes(), &salt, derived_key.as_mut_slice())
.map_err(|_| CryptError::HashingError)?;
let cipher = Aes256Gcm::new_from_slice(&derived_key).map_err(|_| CryptError::InvalidLength)?;
let mut nonce_bytes = vec![0u8; 12];
rng.fill_bytes(&mut nonce_bytes);
let nonce = Nonce::try_from(nonce_bytes.as_slice()).map_err(|_| CryptError::InvalidLength)?;
let ciphertext = cipher.encrypt(&nonce, plaintext)?;
Ok(CryptData {
ciphertext,
salt,
nonce_bytes,
})
}
pub fn decrypt_data(data: &CryptData, password: &str) -> Result<Vec<u8>, CryptError> {
decrypt_parts(&data.salt, &data.nonce_bytes, &data.ciphertext, password)
}
#[cfg(feature = "derive")]
pub fn encrypt_value<T: serde::Serialize>(
value: &T,
password: &str,
rng: &mut (impl Rng + CryptoRng),
) -> Result<CryptData, CryptError> {
let bytes = bincode::serde::encode_to_vec(value, bincode::config::standard())
.map_err(|_| CryptError::SerializationError)?;
#[cfg(feature = "zeroize")]
let bytes = Zeroizing::new(bytes);
encrypt_data(password, &bytes, rng)
}
#[cfg(feature = "derive")]
pub fn decrypt_value<T: serde::de::DeserializeOwned>(
data: &CryptData,
password: &str,
) -> Result<T, CryptError> {
let bytes = decrypt_data(data, password)?;
#[cfg(feature = "zeroize")]
let bytes = Zeroizing::new(bytes);
bincode::serde::decode_from_slice(&bytes, bincode::config::standard())
.map(|(value, _)| value)
.map_err(|_| CryptError::SerializationError)
}
pub fn decrypt_bytes(data: &[u8], password: &str) -> Result<Vec<u8>, CryptError> {
decrypt_data(&CryptData::from_bytes(data)?, password)
}
fn decrypt_parts(
salt: &[u8],
nonce_bytes: &[u8],
ciphertext: &[u8],
password: &str,
) -> Result<Vec<u8>, CryptError> {
if salt.len() != 16 || nonce_bytes.len() != 12 || ciphertext.len() < 16 {
return Err(CryptError::InvalidData("Invalid encrypted data"));
}
#[cfg(feature = "zeroize")]
let mut derived_key = Zeroizing::new(vec![0u8; 32]);
#[cfg(not(feature = "zeroize"))]
let mut derived_key = vec![0u8; 32];
Argon2::default()
.hash_password_into(password.as_bytes(), salt, derived_key.as_mut_slice())
.map_err(|_| CryptError::HashingError)?;
let cipher = Aes256Gcm::new_from_slice(&derived_key).map_err(|_| CryptError::InvalidLength)?;
let nonce = Nonce::try_from(nonce_bytes).map_err(|_| CryptError::InvalidLength)?;
let plaintext = cipher.decrypt(&nonce, ciphertext)?;
Ok(plaintext)
}
#[cfg(feature = "std")]
pub fn save_file(file_path: &str, data: &CryptData) -> Result<(), CryptError> {
let mut file = File::create(file_path)?;
file.write_all(&data.salt)?;
file.write_all(&data.nonce_bytes)?;
file.write_all(&data.ciphertext)?;
Ok(())
}
#[cfg(feature = "std")]
pub fn load_file(file_path: &str) -> Result<CryptData, CryptError> {
let mut file = File::open(file_path)?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
CryptData::from_bytes(&buffer)
}
#[cfg(all(feature = "std", feature = "derive"))]
pub fn load_value<T: serde::de::DeserializeOwned>(
file_path: &str,
password: &str,
) -> Result<T, CryptError> {
let data = load_file(file_path)?;
decrypt_value(&data, password)
}
#[cfg(feature = "std")]
pub fn decrypt_file(file_path: &str, password: &str) -> Result<Vec<u8>, CryptError> {
decrypt_data(&load_file(file_path)?, password)
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
#[cfg(feature = "derive")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "derive")]
#[derive(Serialize, Deserialize, Cryptainer)]
struct Secret {
message: String,
}
fn t_encrypt_file() -> Result<(), CryptError> {
let mut rng = rand::rng();
let data = encrypt_data("password", "super secret stuff".as_bytes(), &mut rng)?;
let _ = save_file("secret.stuff", &data)?;
Ok(())
}
fn t_decrypt_file() -> Result<(), CryptError> {
let data =
alloc::string::String::from_utf8(decrypt_file("secret.stuff", "password")?).unwrap();
assert_eq!(data, "super secret stuff");
Ok(())
}
#[test]
fn test_load_file() -> Result<(), CryptError> {
let mut rng = rand::rng();
let encrypted = encrypt_data("password", b"loaded secret", &mut rng)?;
save_file("secret.stuff", &encrypted)?;
let loaded = load_file("secret.stuff")?;
assert_eq!(decrypt_data(&loaded, "password")?, b"loaded secret");
let _ = std::fs::remove_file("secret.stuff");
Ok(())
}
#[test]
fn test_encrypt_decrypt() -> Result<(), CryptError> {
t_encrypt_file()?;
t_decrypt_file()?;
let _ = std::fs::remove_file("secret.stuff");
Ok(())
}
#[cfg(feature = "derive")]
#[test]
fn test_derive_encrypt_decrypt() -> Result<(), CryptError> {
let mut rng = rand::rng();
let secret = Secret {
message: "super secret".into(),
};
let encrypted = secret.encrypt("password", &mut rng)?;
let decrypted = Secret::decrypt(&encrypted, "password")?;
assert_eq!(decrypted.message, secret.message);
Ok(())
}
#[cfg(feature = "derive")]
#[test]
fn test_load_value() -> Result<(), CryptError> {
let mut rng = rand::rng();
let secret = Secret {
message: "loaded secret".into(),
};
let encrypted = secret.encrypt("password", &mut rng)?;
save_file("secret.stuff", &encrypted)?;
let loaded: Secret = load_value("secret.stuff", "password")?;
assert_eq!(loaded.message, secret.message);
let _ = std::fs::remove_file("secret.stuff");
Ok(())
}
}