#![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::{Algorithm, Argon2, Params, Version};
use thiserror::Error;
use rand::{CryptoRng, Rng};
#[cfg(feature = "zeroize")]
use zeroize::Zeroizing;
#[cfg(feature = "zeroize")]
pub type KeyBuffer = Zeroizing<[u8; 32]>;
#[cfg(not(feature = "zeroize"))]
pub type KeyBuffer = [u8; 32];
#[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,
#[error("invalid salt length: expected 16 bytes, got {actual}")]
InvalidSaltLength { actual: usize },
#[error("invalid nonce length: expected 12 bytes, got {actual}")]
InvalidNonceLength { actual: usize },
#[error("invalid ciphertext length: expected at least 16 bytes, got {actual}")]
InvalidCiphertextLength { actual: usize },
#[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 {
pub fn from_bytes(data: &[u8]) -> Result<Self, CryptError> {
if data.len() < 44 {
return Err(CryptError::InvalidData(
"encrypted data is too short to contain a salt, nonce, and authentication tag",
));
}
Ok(Self {
salt: data[0..16].to_vec(),
nonce_bytes: data[16..28].to_vec(),
ciphertext: data[28..].to_vec(),
})
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut data =
Vec::with_capacity(self.salt.len() + self.nonce_bytes.len() + self.ciphertext.len());
data.extend_from_slice(&self.salt);
data.extend_from_slice(&self.nonce_bytes);
data.extend_from_slice(&self.ciphertext);
data
}
}
fn argon2_instance() -> Result<Argon2<'static>, CryptError> {
let params = Params::new(19456, 2, 1, Some(32)).map_err(|_| CryptError::HashingError)?;
Ok(Argon2::new(Algorithm::Argon2id, Version::V0x13, params))
}
pub fn derive_key(password: &str, salt: &[u8]) -> Result<KeyBuffer, CryptError> {
if salt.len() != 16 {
return Err(CryptError::InvalidSaltLength { actual: salt.len() });
}
#[cfg(feature = "zeroize")]
let mut key = Zeroizing::new([0u8; 32]);
#[cfg(not(feature = "zeroize"))]
let mut key = [0u8; 32];
argon2_instance()?
.hash_password_into(password.as_bytes(), salt, &mut key[..])
.map_err(|_| CryptError::HashingError)?;
Ok(key)
}
pub fn encrypt_with_key(
key: &[u8; 32],
salt: &[u8],
plaintext: &[u8],
rng: &mut (impl Rng + CryptoRng),
) -> Result<CryptData, CryptError> {
if salt.len() != 16 {
return Err(CryptError::InvalidSaltLength { actual: salt.len() });
}
let cipher = Aes256Gcm::new_from_slice(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: salt.to_vec(),
nonce_bytes,
})
}
pub fn decrypt_with_key(data: &CryptData, key: &[u8; 32]) -> Result<Vec<u8>, CryptError> {
if data.salt.len() != 16 {
return Err(CryptError::InvalidSaltLength {
actual: data.salt.len(),
});
}
if data.nonce_bytes.len() != 12 {
return Err(CryptError::InvalidNonceLength {
actual: data.nonce_bytes.len(),
});
}
if data.ciphertext.len() < 16 {
return Err(CryptError::InvalidCiphertextLength {
actual: data.ciphertext.len(),
});
}
let cipher = Aes256Gcm::new_from_slice(key).map_err(|_| CryptError::InvalidLength)?;
let nonce =
Nonce::try_from(data.nonce_bytes.as_slice()).map_err(|_| CryptError::InvalidLength)?;
Ok(cipher.decrypt(&nonce, data.ciphertext.as_slice())?)
}
pub fn decrypt_bytes_with_key(data: &[u8], key: &[u8; 32]) -> Result<Vec<u8>, CryptError> {
decrypt_with_key(&CryptData::from_bytes(data)?, key)
}
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);
let key = derive_key(password, &salt)?;
encrypt_with_key(&key, &salt, plaintext, rng)
}
pub fn decrypt_data(data: &CryptData, password: &str) -> Result<Vec<u8>, CryptError> {
let key = derive_key(password, &data.salt)?;
decrypt_with_key(data, &key)
}
#[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 encrypt_value_with_key<T: serde::Serialize>(
value: &T,
key: &[u8; 32],
salt: &[u8],
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_with_key(key, salt, &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)?;
deserialize_value(bytes)
}
#[cfg(feature = "derive")]
pub fn decrypt_value_with_key<T: serde::de::DeserializeOwned>(
data: &CryptData,
key: &[u8; 32],
) -> Result<T, CryptError> {
let bytes = decrypt_with_key(data, key)?;
deserialize_value(bytes)
}
#[cfg(feature = "derive")]
#[doc(hidden)]
pub fn deserialize_value<T: serde::de::DeserializeOwned>(bytes: Vec<u8>) -> Result<T, CryptError> {
#[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)
}
#[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.to_bytes())?;
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(feature = "std")]
pub fn decrypt_file_with_key(file_path: &str, key: &[u8; 32]) -> Result<Vec<u8>, CryptError> {
decrypt_with_key(&load_file(file_path)?, key)
}
#[cfg(all(feature = "std", feature = "derive"))]
pub fn load_value_with_key<T: serde::de::DeserializeOwned>(
file_path: &str,
key: &[u8; 32],
) -> Result<T, CryptError> {
decrypt_value_with_key(&load_file(file_path)?, key)
}
#[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_bytes_round_trip() -> Result<(), CryptError> {
let mut rng = rand::rng();
let encrypted = encrypt_data("password", b"serialized secret", &mut rng)?;
let bytes = encrypted.to_bytes();
let parsed = CryptData::from_bytes(&bytes)?;
assert_eq!(parsed.salt, encrypted.salt);
assert_eq!(parsed.nonce_bytes, encrypted.nonce_bytes);
assert_eq!(parsed.ciphertext, encrypted.ciphertext);
assert_eq!(decrypt_data(&parsed, "password")?, b"serialized secret");
Ok(())
}
#[test]
fn test_derive_and_key_round_trip() -> Result<(), CryptError> {
let salt = [7u8; 16];
let key = derive_key("password", &salt)?;
let mut rng = rand::rng();
let encrypted = encrypt_with_key(&key, &salt, b"key secret", &mut rng)?;
assert_eq!(decrypt_with_key(&encrypted, &key)?, b"key secret");
assert_eq!(
decrypt_bytes_with_key(&encrypted.to_bytes(), &key)?,
b"key secret"
);
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_derive_key_encrypt_decrypt() -> Result<(), CryptError> {
let salt = [9u8; 16];
let key = derive_key("password", &salt)?;
let mut rng = rand::rng();
let secret = Secret {
message: "key secret".into(),
};
let encrypted = secret.encrypt_with_key(&key, &salt, &mut rng)?;
let decrypted = Secret::decrypt_with_key(&encrypted, &key)?;
let decrypted_from_bytes = Secret::decrypt_bytes_with_key(&encrypted.to_bytes(), &key)?;
let decrypted_from_password_bytes =
Secret::decrypt_bytes(&encrypted.to_bytes(), "password")?;
assert_eq!(decrypted.message, secret.message);
assert_eq!(decrypted_from_bytes.message, secret.message);
assert_eq!(decrypted_from_password_bytes.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(())
}
}