pub mod decrypt;
pub mod encrypt;
pub mod errors;
mod noise;
use orion::hazardous::aead::chacha20poly1305 as chapoly;
use orion::hazardous::ecc::x25519 as orion_x25519;
use orion::hazardous::hash::sha2::sha256::Sha256;
use orion::hazardous::kdf::hkdf::sha256 as hkdf;
use orion::hazardous::mac::hmac::sha256 as hmac;
use orion::hazardous::kdf::scrypt::derive_key;
use zeroize::{Zeroize, ZeroizeOnDrop};
use errors::{ChaPolyDecryptError, DhError, NoiseError};
use noise::HandshakeState;
const CHUNK_SIZE: u32 = 65536;
const SCRYPT_N: u32 = 32768;
const SCRYPT_R: u32 = 8;
const SCRYPT_P: u32 = 1;
const TAG_SIZE: usize = 16;
#[derive(Copy, Clone, PartialEq)]
#[non_exhaustive]
pub enum AsymFileFormat {
V1,
}
#[derive(Copy, Clone, PartialEq)]
#[non_exhaustive]
pub enum PassFileFormat {
V1,
}
#[derive(Copy, Clone, PartialEq)]
#[non_exhaustive]
pub enum FileFormat {
AsymV1,
PassV1,
}
#[derive(Clone)]
pub struct PayloadKey {
key: [u8; 32],
}
impl PayloadKey {
pub fn new(key: &[u8]) -> Self {
Self {
key: key.try_into().expect("Keys must be 32 bytes"),
}
}
pub fn as_bytes(&self) -> &[u8] {
self.key.as_slice()
}
}
impl Drop for PayloadKey {
fn drop(&mut self) {
self.zeroize();
}
}
impl Zeroize for PayloadKey {
fn zeroize(&mut self) {
self.key.zeroize();
}
}
impl ZeroizeOnDrop for PayloadKey {}
#[derive(Clone)]
pub struct PublicKey {
key: Vec<u8>,
}
#[derive(Clone)]
pub struct PrivateKey {
key: Vec<u8>,
}
impl PublicKey {
pub fn as_bytes(&self) -> &[u8] {
self.key.as_ref()
}
}
impl TryFrom<&[u8]> for PublicKey {
type Error = &'static str;
fn try_from(raw_key: &[u8]) -> Result<PublicKey, Self::Error> {
if raw_key.len() != 32 {
return Err("Public keys must be 32 bytes");
}
let pk = raw_key.to_vec();
Ok(PublicKey { key: pk })
}
}
impl PrivateKey {
pub fn generate() -> PrivateKey {
let key = secure_random(32);
PrivateKey { key }
}
pub fn as_bytes(&self) -> &[u8] {
self.key.as_ref()
}
pub fn to_public(&self) -> Result<PublicKey, DhError> {
let pk = x25519_derive_public(&self.key)?;
Ok(PublicKey::try_from(pk.as_slice()).unwrap())
}
pub fn diffie_hellman(&self, public_key: &PublicKey) -> Result<Vec<u8>, DhError> {
x25519(self.as_bytes(), public_key.as_bytes())
}
}
impl TryFrom<&[u8]> for PrivateKey {
type Error = &'static str;
fn try_from(raw_key: &[u8]) -> Result<PrivateKey, Self::Error> {
if raw_key.len() != 32 {
return Err("Private keys must be 32 bytes");
}
let sk = raw_key.to_vec();
Ok(PrivateKey { key: sk })
}
}
impl Drop for PrivateKey {
fn drop(&mut self) {
self.zeroize();
}
}
impl Zeroize for PrivateKey {
fn zeroize(&mut self) {
self.key.as_mut_slice().zeroize();
}
}
impl ZeroizeOnDrop for PrivateKey {}
pub fn x25519(k: &[u8], u: &[u8]) -> Result<Vec<u8>, DhError> {
let mut 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");
let private_key = orion_x25519::PrivateKey::from_slice(&sk).unwrap();
let public_key: orion_x25519::PublicKey = orion_x25519::PublicKey::from_slice(&pk).unwrap();
let shared_secret =
orion_x25519::key_agreement(&private_key, &public_key).map_err(|_| DhError)?;
let res = shared_secret.unprotected_as_bytes().to_vec();
sk.zeroize();
Ok(res)
}
pub fn x25519_derive_public(private_key: &[u8]) -> Result<Vec<u8>, DhError> {
let sk = orion_x25519::PrivateKey::from_slice(private_key).unwrap();
let pk = orion_x25519::PublicKey::try_from(&sk).map_err(|_| DhError)?;
Ok(pk.to_bytes().as_ref().to_vec())
}
pub struct NoiseEncryptMsg {
pub ciphertext: Vec<u8>,
pub handshake_hash: [u8; 32],
}
pub fn noise_encrypt(
sender: &PrivateKey,
sender_public: &PublicKey,
recipient: &PublicKey,
ephemeral: Option<&PrivateKey>,
ephemeral_public: Option<&PublicKey>,
prologue: &[u8],
payload_key: &PayloadKey,
) -> Result<NoiseEncryptMsg, NoiseError> {
let mut handshake_state = HandshakeState::init_x(
true,
prologue,
sender.clone(),
sender_public.clone(),
ephemeral.cloned(),
ephemeral_public.cloned(),
Some(recipient.clone()),
);
let noise_handshake = handshake_state.write_message(payload_key.as_bytes())?;
let handshake_hash = noise_handshake.handshake_hash;
let ciphertext = noise_handshake.message;
Ok(NoiseEncryptMsg {
ciphertext,
handshake_hash,
})
}
pub struct NoiseDecryptMsg {
pub payload_key: PayloadKey,
pub public_key: PublicKey,
pub handshake_hash: [u8; 32],
}
pub fn noise_decrypt(
recipient: &PrivateKey,
recipient_public: &PublicKey,
prologue: &[u8],
handshake_message: &[u8],
) -> Result<NoiseDecryptMsg, NoiseError> {
let initiator = false;
let mut handshake_state = noise::HandshakeState::init_x(
initiator,
prologue,
recipient.clone(),
recipient_public.clone(),
None,
None,
None,
);
let noise_handshake = handshake_state.read_message(handshake_message)?;
let handshake_hash = noise_handshake.handshake_hash;
if noise_handshake.message.len() != 32 {
return Err(NoiseError::Other(
"Expected payload key to be 32 bytes.".to_string(),
));
}
let payload_key = PayloadKey::new(noise_handshake.message.as_slice());
let sender_pubkey = handshake_state
.get_pubkey()
.expect("Expected to get the sender's public key");
Ok(NoiseDecryptMsg {
payload_key,
public_key: sender_pubkey,
handshake_hash,
})
}
#[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 nonce = chapoly::Nonce::from_slice(nonce).expect("Nonce must be 12 bytes");
let mut ct_and_tag = vec![0u8; plaintext.len() + TAG_SIZE];
let key = chapoly::SecretKey::from_slice(key).expect("Key must be 32 bytes");
chapoly::seal(
&key,
&nonce,
plaintext,
Some(aad),
ct_and_tag.as_mut_slice(),
)
.expect("ChaCha20-Poly11305 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 nonce = chapoly::Nonce::from_slice(nonce).expect("Nonce must be 12 bytes");
let key = chapoly::SecretKey::from_slice(key).expect("Key must be 32 bytes");
let pt_size = std::cmp::max(ciphertext.len() - TAG_SIZE, 0);
let mut plaintext = vec![0u8; pt_size];
chapoly::open(
&key,
&nonce,
ciphertext,
Some(aad),
plaintext.as_mut_slice(),
)
.map_err(|_| ChaPolyDecryptError)?;
Ok(plaintext)
}
pub fn sha256(data: &[u8]) -> Vec<u8> {
Sha256::digest(data).unwrap().as_ref().to_vec()
}
pub fn hmac_sha256(key: &[u8], data: &[u8]) -> Vec<u8> {
let sk = hmac::SecretKey::from_slice(key).unwrap();
hmac::HmacSha256::hmac(&sk, data)
.unwrap()
.unprotected_as_bytes()
.to_vec()
}
fn hkdf_noise(chaining_key: &[u8], ikm: &[u8]) -> (Vec<u8>, Vec<u8>) {
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);
counter2.zeroize();
(output1, output2)
}
pub fn hkdf_sha256(salt: &[u8], ikm: &[u8], info: &[u8], len: usize) -> Vec<u8> {
let mut okm = vec![0u8; len];
hkdf::derive_key(salt, ikm, Some(info), okm.as_mut_slice()).unwrap();
okm
}
pub fn scrypt(password: &[u8], salt: &[u8], n: u32, r: u32, p: u32, dk_len: usize) -> Vec<u8> {
let mut okm = vec![0u8; dk_len];
derive_key(password, salt, n, r, p, &mut okm).expect("invalid scrypt parameters");
okm
}
pub fn secure_random(len: usize) -> Vec<u8> {
let mut data = vec![0u8; len];
getrandom::fill(&mut data).expect("CSPRNG gen failed");
data
}
#[cfg(test)]
mod tests {
use super::{PrivateKey, PublicKey};
use super::{
chapoly_decrypt_ietf, chapoly_decrypt_noise, chapoly_encrypt_ietf, chapoly_encrypt_noise,
hkdf_sha256, hmac_sha256, scrypt, sha256, x25519,
};
use ct_codecs::{Decoder, Hex};
#[test]
fn test_chapoly_encrypt() {
let expected = Hex::decode_to_vec(
"cc459a8b9d29617bb70791e7b158dfaf36585f656aec0ada3899fdcd",
None,
)
.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_to_vec("c7a7077a5e9d774b510100904c7dc805", None).unwrap();
let key = Hex::decode_to_vec(
"68301045a4494999d59ffa818ee5fafc2878bf96c32acf5fa40dbe93e8ac98ce",
None,
)
.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_to_vec(
"77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a",
None,
)
.unwrap();
let nonce: u64 = 0;
let ad = Hex::decode_to_vec("0000000C", None).unwrap();
let expected = b"Hello world!";
let ct_and_tag = Hex::decode_to_vec(
"cc459a8b9d29617bb70791e7b158dfaf36585f656aec0ada3899fdcd",
None,
)
.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_to_vec("c7a7077a5e9d774b510100904c7dc805", None).unwrap();
let key = Hex::decode_to_vec(
"68301045a4494999d59ffa818ee5fafc2878bf96c32acf5fa40dbe93e8ac98ce",
None,
)
.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_to_vec(
"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
None,
)
.unwrap();
assert_eq!(&got, expected.as_slice());
}
#[test]
fn test_hmac_sha256() {
let key = b"yellowsubmarine.yellowsubmarine.";
let message = b"Hello, world!";
let expected = Hex::decode_to_vec(
"3cb82dc71c26dfe8be75805f6438027d5170f3fdcd8057f0a55d1c7c1743224c",
None,
)
.unwrap();
let result = hmac_sha256(key, message);
assert_eq!(&expected, &result);
}
#[test]
fn test_hkdf_sha256() {
let ikm = Hex::decode_to_vec("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", None).unwrap();
let salt = Hex::decode_to_vec("000102030405060708090a0b0c", None).unwrap();
let info = Hex::decode_to_vec("f0f1f2f3f4f5f6f7f8f9", None).unwrap();
let length = 42;
let expected_okm = Hex::decode_to_vec(
"3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865",
None,
)
.unwrap();
let result_okm = hkdf_sha256(&salt, &ikm, &info, length);
assert_eq!(&expected_okm, &result_okm);
let ikm = Hex::decode_to_vec("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", None).unwrap();
let salt = &[];
let info = &[];
let length = 42;
let expected_okm = Hex::decode_to_vec(
"8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8",
None,
)
.unwrap();
let result_okm = hkdf_sha256(salt, &ikm, info, length);
assert_eq!(&expected_okm, &result_okm);
}
#[test]
fn test_scrypt() {
let password = b"hackme";
let salt = b"yellowsubmarine.";
let expected1 = Hex::decode_to_vec(
"3ebb9ac0d1da595f755407fe8fc246fe67fe6075730fc6e853351c2834bd6157",
None,
)
.unwrap();
let result1 = scrypt(password, salt, 32768, 8, 1, 32);
assert_eq!(&expected1, &result1);
let expected2 = Hex::decode_to_vec("3ebb9ac0d1da595f", None).unwrap();
let result2 = scrypt(password, salt, 32768, 8, 1, 8);
assert_eq!(&expected2, &result2);
let expected3 = Hex::decode_to_vec("87b33dba57a7633a3df7741eabee3de0", None).unwrap();
let result3 = scrypt(password, salt, 1024, 8, 1, 16);
assert_eq!(&expected3, &result3);
}
#[test]
fn test_rfc7748_diffie_hellman_vectors() {
let alice_private_expected = Hex::decode_to_vec(
"77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a",
None,
)
.unwrap();
let alice_public_expected = Hex::decode_to_vec(
"8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a",
None,
)
.unwrap();
let bob_private_expected = Hex::decode_to_vec(
"5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb",
None,
)
.unwrap();
let bob_public_expected = Hex::decode_to_vec(
"de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f",
None,
)
.unwrap();
let expected_shared_secret = Hex::decode_to_vec(
"4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742",
None,
)
.unwrap();
let alice_private = PrivateKey::try_from(alice_private_expected.as_slice()).unwrap();
let alice_public = PublicKey::try_from(alice_public_expected.as_slice()).unwrap();
assert_eq!(
&alice_public_expected,
&alice_private.to_public().unwrap().as_bytes()
);
let bob_private = PrivateKey::try_from(bob_private_expected.as_slice()).unwrap();
let bob_public = PublicKey::try_from(bob_public_expected.as_slice()).unwrap();
let alice_to_bob = x25519(alice_private.as_bytes(), bob_public.as_bytes()).unwrap();
let bob_to_alice = x25519(bob_private.as_bytes(), alice_public.as_bytes()).unwrap();
let alice_to_bob2 = alice_private.diffie_hellman(&bob_public).unwrap();
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_to_vec(
"77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a",
None,
)
.unwrap();
let alice_public_expected = Hex::decode_to_vec(
"8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a",
None,
)
.unwrap();
let got_public = PrivateKey::try_from(&alice_private_expected[..])
.unwrap()
.to_public()
.unwrap();
assert_eq!(&alice_public_expected[..], got_public.as_bytes());
}
}