use sodiumoxide::crypto::pwhash::{
MEMLIMIT_INTERACTIVE, OPSLIMIT_INTERACTIVE,
Salt, OpsLimit,
gen_salt, derive_key
};
use sodiumoxide::crypto::box_::{
NONCEBYTES, MACBYTES,
Nonce, PrecomputedKey,
gen_nonce
};
use sodiumoxide::crypto::hash::sha256;
use sodiumoxide::utils::memzero;
pub use sodiumoxide::crypto::pwhash::SALTBYTES as SALT_LENGTH;
pub use sodiumoxide::crypto::box_::PRECOMPUTEDKEYBYTES as KEY_LENGTH;
use ::toxcore::crypto_core;
#[cfg(test)]
use quickcheck::{QuickCheck, TestResult};
pub const MAGIC_LENGTH: usize = 8;
pub const MAGIC_NUMBER: &[u8; MAGIC_LENGTH] = b"toxEsave";
pub const EXTRA_LENGTH: usize = MAGIC_LENGTH + SALT_LENGTH + NONCEBYTES + MACBYTES;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PassKey {
salt: Box<Salt>,
key: Box<PrecomputedKey>
}
impl PassKey {
pub fn new(passphrase: &[u8]) -> Result<PassKey, KeyDerivationError> {
PassKey::with_salt(passphrase, gen_salt())
}
pub fn with_salt(passphrase: &[u8], salt: Salt) -> Result<PassKey, KeyDerivationError> {
if passphrase.is_empty() { return Err(KeyDerivationError::Null) };
let sha256::Digest(passhash) = sha256::hash(passphrase);
let OpsLimit(ops) = OPSLIMIT_INTERACTIVE;
let mut key = [0; KEY_LENGTH];
let maybe_key = PrecomputedKey::from_slice(try!(
derive_key(
&mut key,
&passhash,
&salt,
OpsLimit(ops * 2),
MEMLIMIT_INTERACTIVE
).or(Err(KeyDerivationError::Failed))
));
memzero(&mut key);
let salt = Box::new(salt);
let key = Box::new(try!(maybe_key.ok_or(KeyDerivationError::Failed)));
Ok(PassKey { salt, key })
}
pub fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>, EncryptionError> {
if data.is_empty() { return Err(EncryptionError::Null) };
let mut output = Vec::with_capacity(EXTRA_LENGTH + data.len());
let nonce = gen_nonce();
output.extend_from_slice(MAGIC_NUMBER);
output.extend_from_slice(&self.salt.0);
output.extend_from_slice(&nonce.0);
output.append(&mut crypto_core::encrypt_data_symmetric(
&self.key,
&nonce,
data
));
Ok(output)
}
pub fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, DecryptionError> {
if data.is_empty() { return Err(DecryptionError::Null) };
if data.len() <= EXTRA_LENGTH { return Err(DecryptionError::InvalidLength) };
if !is_encrypted(data) { return Err(DecryptionError::BadFormat) };
let nonce = try!(Nonce::from_slice(&data[
MAGIC_LENGTH+SALT_LENGTH..MAGIC_LENGTH+SALT_LENGTH+NONCEBYTES
]).ok_or(DecryptionError::BadFormat));
let output = try!(crypto_core::decrypt_data_symmetric(
&self.key,
&nonce,
&data[MAGIC_LENGTH+SALT_LENGTH+NONCEBYTES..]
).or(Err(DecryptionError::Failed)));
Ok(output)
}
}
#[inline]
pub fn is_encrypted(data: &[u8]) -> bool {
data.starts_with(MAGIC_NUMBER)
}
pub fn pass_encrypt(data: &[u8], passphrase: &[u8]) -> Result<Vec<u8>, EncryptionError> {
try!(PassKey::new(passphrase)).encrypt(data)
}
pub fn pass_decrypt(data: &[u8], passphrase: &[u8]) -> Result<Vec<u8>, DecryptionError> {
if data.is_empty() { return Err(DecryptionError::Null) }
if data.len() <= EXTRA_LENGTH { return Err(DecryptionError::InvalidLength) }
if !is_encrypted(data) { return Err(DecryptionError::BadFormat) }
let salt = try!(get_salt(data).ok_or(KeyDerivationError::Failed));
try!(PassKey::with_salt(passphrase, salt)).decrypt(data)
}
pub fn get_salt(data: &[u8]) -> Option<Salt> {
if is_encrypted(data)
&& data.len() >= MAGIC_LENGTH + SALT_LENGTH
{
Salt::from_slice(&data[MAGIC_LENGTH..MAGIC_LENGTH+SALT_LENGTH])
} else {
None
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum KeyDerivationError {
Null,
Failed
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum EncryptionError {
Null,
KeyDerivation(KeyDerivationError),
}
impl From<KeyDerivationError> for EncryptionError {
fn from(err: KeyDerivationError) -> EncryptionError {
EncryptionError::KeyDerivation(err)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DecryptionError {
Null,
InvalidLength,
BadFormat,
KeyDerivation(KeyDerivationError),
Failed
}
impl From<KeyDerivationError> for DecryptionError {
fn from(err: KeyDerivationError) -> DecryptionError {
DecryptionError::KeyDerivation(err)
}
}
#[test]
fn pass_key_new_test() {
fn with_pw(passwd: Vec<u8>) -> TestResult {
if passwd.is_empty() { return TestResult::discard() }
let pk = PassKey::new(&passwd).expect("Failed to unwrap PassKey!");
assert!(pk.salt.0.as_ref() != passwd.as_slice());
assert!(pk.salt.0.as_ref() != [0; SALT_LENGTH].as_ref());
assert!(pk.key.0.as_ref() != passwd.as_slice());
assert!(pk.key.0 != [0; KEY_LENGTH]);
TestResult::passed()
}
QuickCheck::new().max_tests(20).quickcheck(with_pw as fn(Vec<u8>) -> TestResult);
}
#[test]
fn pass_key_with_salt_test() {
fn with_pw(passwd: Vec<u8>) -> TestResult {
if passwd.is_empty() { return TestResult::discard() }
let salt = gen_salt();
let pk = PassKey::with_salt(&passwd, salt).unwrap();
assert_eq!(&*pk.salt, &salt);
assert!(pk.key.0.as_ref() != passwd.as_slice());
assert!(pk.key.0 != [0; KEY_LENGTH]);
TestResult::passed()
}
QuickCheck::new().max_tests(20).quickcheck(with_pw as fn(Vec<u8>) -> TestResult);
}