use sodiumoxide::crypto::pwhash::{argon2id13, argon2id13::Salt, argon2id13::SALTBYTES};
use sodiumoxide::crypto::secretbox::{self, Key, Nonce, KEYBYTES, MACBYTES, NONCEBYTES};
use thiserror::Error;
pub fn encrypt(data: &[u8], passphrase: &[u8]) -> Vec<u8> {
sodiumoxide::init().expect("unable to init sodium");
let (key, salt) = derive_new_key(passphrase);
let nonce = secretbox::gen_nonce();
let encrypted_data = secretbox::seal(data, &nonce, &key);
{
let mut output = Vec::new();
output.extend_from_slice(salt.0.as_ref());
output.extend_from_slice(nonce.0.as_ref());
output.extend_from_slice(encrypted_data.as_slice());
output
}
}
pub fn decrypt(data: &[u8], passphrase: &[u8]) -> Result<Vec<u8>, DecryptionError> {
sodiumoxide::init().expect("unable to init sodium");
if data.len() < SALTBYTES {
return Err(DecryptionError::IncompleteSalt(data.len()));
}
if data.len() < SALTBYTES + NONCEBYTES {
return Err(DecryptionError::IncompleteNonce(data.len()));
}
if data.len() < SALTBYTES + NONCEBYTES + MACBYTES {
return Err(DecryptionError::IncompleteMac(data.len()));
}
let salt = Salt::from_slice(&data[..SALTBYTES]).unwrap();
let nonce = Nonce::from_slice(&data[SALTBYTES..SALTBYTES + NONCEBYTES]).unwrap();
let data = &data[SALTBYTES + NONCEBYTES..];
let key = derive_key(passphrase, salt);
secretbox::open(data, &nonce, &key).map_err(|_| DecryptionError::Decryption)
}
#[derive(Error, Debug, Eq, PartialEq)]
pub enum DecryptionError {
#[error("expected a {} byte at index {} but only got {0} bytes", 0, SALTBYTES)]
IncompleteSalt(usize),
#[error(
"expected a {} byte nonce at index {} but only got {} bytes",
NONCEBYTES,
SALTBYTES,
.0 - SALTBYTES
)]
IncompleteNonce(usize),
#[error(
"expected a {} byte nonce at index {} but only got {} bytes",
MACBYTES,
NONCEBYTES + SALTBYTES,
.0 - NONCEBYTES - SALTBYTES
)]
IncompleteMac(usize),
#[error("invalid data or secret key")]
Decryption,
}
fn derive_new_key(passphrase: &[u8]) -> (Key, Salt) {
let salt = argon2id13::gen_salt();
let key = derive_key(passphrase, salt);
(key, salt)
}
fn derive_key(passphrase: &[u8], salt: Salt) -> Key {
let mut key = Key([0u8; KEYBYTES]);
let Key(ref mut buffer) = key;
argon2id13::derive_key(
buffer,
passphrase,
&salt,
argon2id13::OPSLIMIT_INTERACTIVE,
argon2id13::MEMLIMIT_INTERACTIVE,
)
.unwrap();
key
}
#[cfg(test)]
mod tests {
use crate::*;
#[test]
pub fn test_encrypt() {
let text = "lord ferris says: you shall not use Go";
let key = "lul no generics";
let _encrypted_data = encrypt(text.as_bytes(), key.as_bytes());
}
#[test]
pub fn test_encrypt_empty_data() {
let data = Vec::new();
let key = "some key";
let encrypted_data = encrypt(data.as_slice(), key.as_bytes());
assert_eq!(SALTBYTES + NONCEBYTES + MACBYTES, encrypted_data.len());
}
#[test]
pub fn test_decrypt() {
let encrypted_data = [
169, 41, 29, 81, 36, 11, 117, 33, 247, 2, 145, 245, 198, 17, 216, 16, 67, 46, 223, 109,
57, 110, 209, 163, 185, 122, 239, 245, 174, 208, 142, 227,
139, 139, 32, 147, 90, 92, 168, 229, 127, 92, 65, 153, 127, 38, 125, 144, 115, 104,
101, 187, 207, 130, 203, 39,
109, 12, 45, 42, 204, 139, 17, 130, 30, 97, 142, 213, 183, 126, 152, 226, 251, 225,
134, 201, 192, 202, 226, 71, 115, 95, 152, 71, 69, 246, 165, 147, 251, 106, 86, 47, 89,
30,
];
assert_eq!(
Ok("lord ferris says: you shall not use Go".as_bytes().to_vec()),
decrypt(&encrypted_data, "lul no generics".as_bytes())
);
}
#[test]
pub fn test_decrypt_with_incomplete_salt() {
const DATA_LENGTH: usize = 12;
let data = [11u8; DATA_LENGTH];
assert_eq!(
Err(DecryptionError::IncompleteSalt(DATA_LENGTH)),
decrypt(&data, "foo".as_bytes())
)
}
#[test]
pub fn test_decrypt_with_incomplete_nonce() {
const DATA_LENGTH: usize = 30;
let data = [11u8; DATA_LENGTH];
assert_eq!(
Err(DecryptionError::IncompleteNonce(DATA_LENGTH)),
decrypt(&data, "foo".as_bytes())
)
}
#[test]
pub fn test_decrypt_with_incomplete_mac() {
const DATA_LENGTH: usize = 50;
let data = [11u8; DATA_LENGTH];
assert_eq!(
Err(DecryptionError::IncompleteMac(DATA_LENGTH)),
decrypt(&data, "foo".as_bytes())
)
}
}