niwl/niwl/src/encrypt.rs

129 lines
4.3 KiB
Rust

use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT;
use curve25519_dalek::digest::Digest;
use curve25519_dalek::ristretto::RistrettoPoint;
use curve25519_dalek::scalar::Scalar;
use fuzzytags::Tag;
use rand::rngs::OsRng;
use secretbox::CipherType::Salsa20;
use secretbox::SecretBox;
use serde::{Deserialize, Serialize};
use std::ops::Mul;
/// TaggedCiphertext is a wrapper around a Tag and an encrypted payload (in addition to a
/// nonce value).
#[derive(Serialize, Deserialize, Clone)]
pub struct TaggedCiphertext {
pub tag: Tag<24>,
nonce: RistrettoPoint,
ciphertext: Vec<u8>,
}
/// A Private Key used when encrypting to a niwl client
#[derive(Serialize, Deserialize)]
pub struct PrivateKey(Scalar);
/// A Public Key derived from a niwl PrivateKey
#[derive(Serialize, Deserialize)]
pub struct PublicKey(RistrettoPoint);
impl PublicKey {
/// Encrypt to Tag provides uni-directional encrypted
pub fn encrypt(&self, tag: &Tag<24>, message: &String) -> TaggedCiphertext {
let mut padded_message = message.clone();
if message.len() < 1024 {
for _i in message.len()..1024 {
padded_message += " "
}
}
// Generate a random point. We will use the public part as a nonce
// And the private part to generate a key.
let mut rng = OsRng::default();
let r = Scalar::random(&mut rng);
let z = RISTRETTO_BASEPOINT_POINT.mul(r);
// Compile our (public) nonce...we derive a new random nonce by hashing
// the public z parameter with the tag.
let mut nonce_hash = sha3::Sha3_256::new();
nonce_hash.update(z.compress().as_bytes());
nonce_hash.update(tag.compress());
let mut nonce = [0u8; 24];
nonce[..].copy_from_slice(&nonce_hash.finalize().as_slice()[0..24]);
// Calculate the key by multiplying part of the tagging key by our private 'r'
let mut hash = sha3::Sha3_256::new();
hash.update(self.0.mul(r).compress().as_bytes());
hash.update(tag.compress());
let key = hash.finalize().to_vec();
let secret_box = SecretBox::new(key, Salsa20).unwrap();
// TODO: Fixed Size Packets
let ciphertext = secret_box.seal(padded_message.as_bytes(), nonce);
TaggedCiphertext {
tag: tag.clone(),
nonce: z,
ciphertext,
}
}
}
impl PrivateKey {
pub fn generate() -> PrivateKey {
let mut rng = OsRng::default();
let r = Scalar::random(&mut rng);
PrivateKey { 0: r }
}
pub fn public_key(&self) -> PublicKey {
PublicKey {
0: RISTRETTO_BASEPOINT_POINT.mul(self.0),
}
}
/// Decrypt a tagged ciphertext
pub fn decrypt(&self, ciphertext: &TaggedCiphertext) -> Option<String> {
// Derive the public nonce...
let mut nonce_hash = sha3::Sha3_256::new();
nonce_hash.update(ciphertext.nonce.compress().as_bytes());
nonce_hash.update(ciphertext.tag.compress());
let mut nonce = [0u8; 24];
nonce[..].copy_from_slice(&nonce_hash.finalize().as_slice()[0..24]);
// Calculate the key by multiplying the public point with our private 'x'
let mut hash = sha3::Sha3_256::new();
hash.update(ciphertext.nonce.mul(self.0).compress().as_bytes());
hash.update(ciphertext.tag.compress());
let key = hash.finalize().to_vec();
let secret_box = SecretBox::new(key, Salsa20).unwrap();
match secret_box.unseal(ciphertext.ciphertext.as_slice(), nonce) {
Some(plaintext) => match String::from_utf8(plaintext) {
Ok(plaintext) => Some(String::from(plaintext.trim_end())),
Err(_) => None,
},
None => None,
}
}
}
#[cfg(test)]
mod tests {
use crate::encrypt::PrivateKey;
use fuzzytags::RootSecret;
#[test]
fn test_encrypt_to_tag() {
let secret = PrivateKey::generate();
let public_key = secret.public_key();
let root_secret = RootSecret::<24>::generate();
let tagging_key = root_secret.tagging_key();
let ciphertext =
public_key.encrypt(&tagging_key.generate_tag(), &String::from("Hello World"));
let plaintext = secret.decrypt(&ciphertext);
assert_eq!(plaintext.unwrap(), String::from("Hello World"))
}
}