use anyhow::{bail, ensure, Context, Result};
use orion::aead::SecretKey;
pub fn nonce() -> Result<[u8; 24]> {
let mut result = [0u8; 24];
getrandom::getrandom(&mut result).unwrap();
Ok(result)
}
fn get_key_from_password(password: &str, salt: &[u8]) -> Result<SecretKey> {
use orion::hazardous::stream::chacha20::CHACHA_KEYSIZE;
use orion::kdf::{derive_key, Password, Salt};
let password = Password::from_slice(password.as_bytes()).with_context(|| "Password error")?;
let salt = Salt::from_slice(salt).with_context(|| "Salt is too short")?;
let kdf_key = derive_key(&password, &salt, 15, 1024, CHACHA_KEYSIZE as u32)
.with_context(|| "Could not derive key from password")?;
let key = SecretKey::from_slice(kdf_key.unprotected_as_bytes())
.with_context(|| "Could not convert key")?;
Ok(key)
}
pub fn encrypt(
plaintext: impl AsRef<[u8]>,
password: impl AsRef<str>,
nonce: impl AsRef<[u8]>,
) -> Result<Vec<u8>> {
use orion::hazardous::{
aead::xchacha20poly1305::{seal, Nonce, SecretKey as XSecretKey},
mac::poly1305::POLY1305_OUTSIZE,
stream::xchacha20::XCHACHA_NONCESIZE,
};
let plaintext = plaintext.as_ref();
let password = password.as_ref();
let nonce = nonce.as_ref();
let key = get_key_from_password(password, nonce)?;
let key =
XSecretKey::from_slice(key.unprotected_as_bytes()).with_context(|| "Key is invalid")?;
let nonce = Nonce::from_slice(nonce).with_context(|| "Nonce is too short")?;
let output_len = match plaintext
.len()
.checked_add(XCHACHA_NONCESIZE + POLY1305_OUTSIZE)
{
Some(min_output_len) => min_output_len,
None => bail!("Plaintext is too long"),
};
let mut output = vec![0u8; output_len];
output[..XCHACHA_NONCESIZE].copy_from_slice(nonce.as_ref());
seal(
&key,
&nonce,
plaintext,
None,
&mut output[XCHACHA_NONCESIZE..],
)
.with_context(|| "Could not convert key")?;
Ok(output)
}
pub fn decrypt(ciphertext: impl AsRef<[u8]>, password: impl AsRef<str>) -> Result<Vec<u8>> {
use orion::aead::open;
use orion::hazardous::stream::xchacha20::XCHACHA_NONCESIZE;
let ciphertext = ciphertext.as_ref();
let password = password.as_ref();
ensure!(
ciphertext.len() > XCHACHA_NONCESIZE,
"Ciphertext is too short"
);
let key = get_key_from_password(password, &ciphertext[..XCHACHA_NONCESIZE])?;
open(&key, ciphertext).with_context(|| "Ciphertext was tampered with")
}