pub mod decrypt;
pub mod encrypt;
pub mod errors;
mod noise;
use getrandom::getrandom;
use chacha20poly1305::aead::{Aead, KeyInit, Payload};
use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce};
use hmac::{Hmac, Mac};
use sha2::{Digest, Sha256};
use zeroize::Zeroize;
use errors::ChaPolyDecryptError;
use noise::HandshakeState;
const CHUNK_SIZE: u32 = 65536;
const SCRYPT_N: u32 = 32768;
const SCRYPT_R: u32 = 8;
const SCRYPT_P: u32 = 1;
#[non_exhaustive]
pub enum AsymFileFormat {
V1,
}
#[non_exhaustive]
pub enum PassFileFormat {
V1,
}
pub type PayloadKey = [u8; 32];
#[derive(Clone, Zeroize)]
pub struct PublicKey {
key: [u8; 32],
}
#[derive(Clone, Zeroize)]
pub struct PrivateKey {
key: [u8; 32],
}
impl PublicKey {
pub fn as_bytes(&self) -> &[u8] {
self.key.as_ref()
}
}
impl From<&[u8]> for PublicKey {
fn from(raw_key: &[u8]) -> PublicKey {
let pk: [u8; 32] = raw_key.try_into().unwrap();
PublicKey { key: pk }
}
}
impl PrivateKey {
pub fn generate() -> PrivateKey {
let key = secure_random(32);
let key: [u8; 32] = key.try_into().unwrap();
PrivateKey { key }
}
pub fn as_bytes(&self) -> &[u8] {
self.key.as_ref()
}
pub fn to_public(&self) -> PublicKey {
PublicKey::from(&x25519_derive_public(&self.key)[..])
}
pub fn diffie_hellman(&self, public_key: &PublicKey) -> [u8; 32] {
x25519(self.as_bytes(), public_key.as_bytes())
}
}
impl From<&[u8]> for PrivateKey {
fn from(raw_key: &[u8]) -> PrivateKey {
let sk: [u8; 32] = raw_key.try_into().expect("Key must be 32 bytes");
PrivateKey { key: sk }
}
}
pub fn x25519(k: &[u8], u: &[u8]) -> [u8; 32] {
let sk: [u8; 32] = k.try_into().expect("Private key must be 32 bytes");
let pk: [u8; 32] = u.try_into().expect("Public key must be 32 bytes");
x25519_dalek::x25519(sk, pk)
}
pub fn x25519_derive_public(private_key: &[u8]) -> [u8; 32] {
let sk: [u8; 32] = private_key
.try_into()
.expect("Private key must be 32 bytes");
x25519_dalek::x25519(sk, x25519_dalek::X25519_BASEPOINT_BYTES)
}
pub fn noise_encrypt(
sender: &PrivateKey,
recipient: &PublicKey,
ephemeral: Option<&PrivateKey>,
prologue: &[u8],
payload_key: &PayloadKey,
) -> Vec<u8> {
let mut handshake_state =
HandshakeState::init_x(true, prologue, sender, ephemeral, Some(recipient.clone()));
let (ciphertext, _) = handshake_state.write_message(payload_key);
ciphertext
}
pub fn noise_decrypt(
recipient: &PrivateKey,
prologue: &[u8],
handshake_message: &[u8],
) -> Result<(PayloadKey, PublicKey), ChaPolyDecryptError> {
let initiator = false;
let mut handshake_state =
noise::HandshakeState::init_x(initiator, prologue, recipient, None, None);
let (payload_key, _) = handshake_state.read_message(handshake_message)?;
let payload_key: [u8; 32] = payload_key.try_into().unwrap();
let sender_pubkey = handshake_state
.get_pubkey()
.expect("Expected to send the sender's public key");
Ok((payload_key, sender_pubkey))
}
#[allow(clippy::let_and_return)]
pub(crate) fn chapoly_encrypt_noise(
key: &[u8],
nonce: u64,
ad: &[u8],
plaintext: &[u8],
) -> Vec<u8> {
let nonce_bytes = nonce.to_le_bytes();
let mut final_nonce_bytes = [0u8; 12];
final_nonce_bytes[4..].copy_from_slice(&nonce_bytes);
chapoly_encrypt_ietf(key, &final_nonce_bytes, plaintext, ad)
}
#[allow(clippy::let_and_return, clippy::redundant_field_names)]
pub fn chapoly_encrypt_ietf(key: &[u8], nonce: &[u8], plaintext: &[u8], aad: &[u8]) -> Vec<u8> {
let secret_key = Key::from_slice(key);
let the_nonce = Nonce::from_slice(nonce);
let cipher = ChaCha20Poly1305::new(secret_key);
let pt_and_aad = Payload {
msg: plaintext,
aad: aad,
};
let ct_and_tag = cipher
.encrypt(the_nonce, pt_and_aad)
.expect("ChaCha20-Poly1305 encryption failed.");
ct_and_tag
}
pub(crate) fn chapoly_decrypt_noise(
key: &[u8],
nonce: u64,
ad: &[u8],
ciphertext: &[u8],
) -> Result<Vec<u8>, ChaPolyDecryptError> {
assert_eq!(key.len(), 32);
let nonce_bytes = nonce.to_le_bytes();
let mut final_nonce_bytes = [0u8; 12];
final_nonce_bytes[4..].copy_from_slice(&nonce_bytes);
chapoly_decrypt_ietf(key, &final_nonce_bytes, ciphertext, ad)
}
#[allow(clippy::redundant_field_names)]
pub fn chapoly_decrypt_ietf(
key: &[u8],
nonce: &[u8],
ciphertext: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, ChaPolyDecryptError> {
let cipher = ChaCha20Poly1305::new_from_slice(key).unwrap();
let ct_and_aad = Payload {
msg: ciphertext,
aad: aad,
};
match cipher.decrypt(Nonce::from_slice(nonce), ct_and_aad) {
Ok(pt) => Ok(pt),
Err(_) => Err(ChaPolyDecryptError),
}
}
pub fn sha256(data: &[u8]) -> [u8; 32] {
let res: [u8; 32] = Sha256::digest(data).as_slice().try_into().unwrap();
res
}
pub fn hmac_sha256(key: &[u8], data: &[u8]) -> [u8; 32] {
let mut mac = <Hmac<Sha256> as Mac>::new_from_slice(key).unwrap();
mac.update(data);
let res = mac.finalize();
res.into_bytes().try_into().unwrap()
}
fn hkdf_noise(chaining_key: &[u8], ikm: &[u8]) -> ([u8; 32], [u8; 32]) {
let counter1: [u8; 1] = [0x01];
let mut counter2: [u8; 33] = [0u8; 33];
let temp_key = hmac_sha256(chaining_key, ikm);
let output1 = hmac_sha256(&temp_key, &counter1);
counter2[..32].copy_from_slice(&output1);
counter2[32..].copy_from_slice(&[0x02]);
let output2 = hmac_sha256(&temp_key, &counter2);
(output1, output2)
}
pub fn scrypt(password: &[u8], salt: &[u8], n: u32, r: u32, p: u32, dk_len: usize) -> Vec<u8> {
assert!(n > 1, "n must be >1");
let n: u8 = (n as f64).log2().round() as u8;
let scrypt_params = scrypt::Params::new(n, r, p).unwrap();
let mut key = vec![0u8; dk_len];
scrypt::scrypt(password, salt, &scrypt_params, &mut key).expect("scrypt kdf failed");
key
}
pub fn secure_random(len: usize) -> Vec<u8> {
let mut data = vec![0u8; len];
getrandom(&mut data).expect("CSPRNG gen failed");
data
}
#[cfg(test)]
mod tests {
use super::{
chapoly_decrypt_ietf, chapoly_decrypt_noise, chapoly_encrypt_ietf, chapoly_encrypt_noise,
hmac_sha256, scrypt, sha256, x25519,
};
use super::{PrivateKey, PublicKey};
#[test]
fn test_chapoly_encrypt() {
let expected =
hex::decode("cc459a8b9d29617bb70791e7b158dfaf36585f656aec0ada3899fdcd").unwrap();
let pt = b"Hello world!";
let key: [u8; 32] = [
0x77, 0x07, 0x6d, 0x0a, 0x73, 0x18, 0xa5, 0x7d, 0x3c, 0x16, 0xc1, 0x72, 0x51, 0xb2,
0x66, 0x45, 0xdf, 0x4c, 0x2f, 0x87, 0xeb, 0xc0, 0x99, 0x2a, 0xb1, 0x77, 0xfb, 0xa5,
0x1d, 0xb9, 0x2c, 0x2a,
];
let nonce: u64 = 0;
let ad = [0x00, 0x00, 0x00, 0x0C];
let ct_and_tag = chapoly_encrypt_noise(&key, nonce, &ad, pt);
assert_eq!(&expected[..], &ct_and_tag[..]);
}
#[test]
fn test_chapoly_enc_empty_pt() {
let expected_ct = hex::decode("c7a7077a5e9d774b510100904c7dc805").unwrap();
let key = hex::decode("68301045a4494999d59ffa818ee5fafc2878bf96c32acf5fa40dbe93e8ac98ce")
.unwrap();
let nonce = [0u8; 12];
let aad: [u8; 1] = [0x01];
let ct = chapoly_encrypt_ietf(key.as_slice(), &nonce, &[], &aad);
assert_eq!(expected_ct.as_slice(), ct.as_slice());
}
#[test]
fn test_decrypt() {
let key = hex::decode("77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a")
.unwrap();
let nonce: u64 = 0;
let ad = hex::decode("0000000C").unwrap();
let expected = b"Hello world!";
let ct_and_tag =
hex::decode("cc459a8b9d29617bb70791e7b158dfaf36585f656aec0ada3899fdcd").unwrap();
let pt = chapoly_decrypt_noise(&key, nonce, &ad, &ct_and_tag).unwrap();
assert_eq!(expected, pt.as_slice());
}
#[test]
fn test_chapoly_dec_empty_pt() {
let ct = hex::decode("c7a7077a5e9d774b510100904c7dc805").unwrap();
let key = hex::decode("68301045a4494999d59ffa818ee5fafc2878bf96c32acf5fa40dbe93e8ac98ce")
.unwrap();
let nonce = [0u8; 12];
let aad: [u8; 1] = [0x01];
let pt = chapoly_decrypt_ietf(key.as_slice(), &nonce, ct.as_slice(), &aad).unwrap();
let expected_pt: [u8; 0] = [];
assert_eq!(&expected_pt, pt.as_slice());
}
#[test]
fn test_sha256() {
let data = b"hello";
let got = sha256(data);
let expected =
hex::decode("2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824")
.unwrap();
assert_eq!(&got, expected.as_slice());
}
#[test]
fn test_scrypt() {
let password = b"hackme";
let salt = b"yellowsubmarine.";
let result = scrypt(password, salt, 32768, 8, 1, 32);
let expected =
hex::decode("3ebb9ac0d1da595f755407fe8fc246fe67fe6075730fc6e853351c2834bd6157")
.unwrap();
assert_eq!(&expected, &result);
}
#[test]
fn test_hmac_sha256() {
let key = b"yellowsubmarine.yellowsubmarine.";
let message = b"Hello, world!";
let expected =
hex::decode("3cb82dc71c26dfe8be75805f6438027d5170f3fdcd8057f0a55d1c7c1743224c")
.unwrap();
let result = hmac_sha256(key, message);
assert_eq!(&expected, &result);
}
#[test]
fn test_rfc7748_diffie_hellman_vectors() {
let alice_private_expected =
hex::decode("77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a")
.unwrap();
let alice_public_expected =
hex::decode("8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a")
.unwrap();
let bob_private_expected =
hex::decode("5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb")
.unwrap();
let bob_public_expected =
hex::decode("de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f")
.unwrap();
let expected_shared_secret =
hex::decode("4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742")
.unwrap();
let alice_private = PrivateKey::from(alice_private_expected.as_slice());
let alice_public = PublicKey::from(alice_public_expected.as_slice());
assert_eq!(
&alice_public_expected,
&alice_private.to_public().as_bytes()
);
let bob_private = PrivateKey::from(bob_private_expected.as_slice());
let bob_public = PublicKey::from(bob_public_expected.as_slice());
let alice_to_bob = x25519(alice_private.as_bytes(), bob_public.as_bytes());
let bob_to_alice = x25519(bob_private.as_bytes(), alice_public.as_bytes());
let alice_to_bob2 = alice_private.diffie_hellman(&bob_public);
assert_eq!(&alice_to_bob, &bob_to_alice);
assert_eq!(&alice_to_bob, &alice_to_bob2);
assert_eq!(&alice_to_bob, expected_shared_secret.as_slice());
}
#[test]
fn test_private_to_public() {
let alice_private_expected =
hex::decode("77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a")
.unwrap();
let alice_public_expected =
hex::decode("8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a")
.unwrap();
let got_public = PrivateKey::from(&alice_private_expected[..]).to_public();
assert_eq!(&alice_public_expected[..], got_public.as_bytes());
}
}