use std::collections::HashMap;
use std::fs::{self, File};
use std::io;
use std::io::prelude::*;
use std::path::PathBuf;
use rand::distributions::Alphanumeric;
use rand::rngs::OsRng;
use rand::Rng;
use aes_gcm_siv::aead::{generic_array::GenericArray, Aead, NewAead};
use aes_gcm_siv::Aes256GcmSiv;
use chacha20poly1305::XChaCha20Poly1305;
use sha2::{Digest, Sha256, Sha512};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Cipher {
len: usize,
rand_string: String,
ciphertext: Vec<u8>,
}
pub fn encrypt_chacha(
cleartext: Vec<u8>,
key: &str,
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let key = GenericArray::clone_from_slice(key.as_bytes());
let aead = XChaCha20Poly1305::new(&key);
let rand_string: String = OsRng
.sample_iter(&Alphanumeric)
.take(24)
.collect::<String>();
let nonce = GenericArray::from_slice(rand_string.as_bytes());
let ciphertext: Vec<u8> = aead
.encrypt(nonce, cleartext.as_ref())
.expect("encryption failure!");
let ciphertext_to_send = Cipher {
len: ciphertext.len(),
rand_string,
ciphertext,
};
let encoded: Vec<u8> = bincode::serialize(&ciphertext_to_send)?;
Ok(encoded)
}
pub fn decrypt_chacha(enc: Vec<u8>, key: &str) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let key = GenericArray::clone_from_slice(key.as_bytes());
let aead = XChaCha20Poly1305::new(&key);
let decoded: Cipher = bincode::deserialize(&enc[..])?;
let (ciphertext2, len_ciphertext, rand_string2) =
(decoded.ciphertext, decoded.len, decoded.rand_string);
if ciphertext2.len() != len_ciphertext {
panic!("length of received ciphertext not ok")
};
let nonce = GenericArray::from_slice(rand_string2.as_bytes());
let plaintext: Vec<u8> = aead
.decrypt(nonce, ciphertext2.as_ref())
.expect("decryption failure!");
Ok(plaintext)
}
pub fn encrypt_aes(cleartext: Vec<u8>, key: &str) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let key = GenericArray::clone_from_slice(key.as_bytes());
let aead = Aes256GcmSiv::new(&key);
let rand_string: String = OsRng
.sample_iter(&Alphanumeric)
.take(12)
.collect::<String>();
let nonce = GenericArray::from_slice(rand_string.as_bytes());
let ciphertext: Vec<u8> = aead
.encrypt(nonce, cleartext.as_ref())
.expect("encryption failure!");
let ciphertext_to_send = Cipher {
len: ciphertext.len(),
rand_string,
ciphertext,
};
let encoded: Vec<u8> = bincode::serialize(&ciphertext_to_send)?;
Ok(encoded)
}
pub fn decrypt_aes(enc: Vec<u8>, key: &str) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let key = GenericArray::clone_from_slice(key.as_bytes());
let aead = Aes256GcmSiv::new(&key);
let decoded: Cipher = bincode::deserialize(&enc[..])?;
let (ciphertext2, len_ciphertext, rand_string2) =
(decoded.ciphertext, decoded.len, decoded.rand_string);
if ciphertext2.len() != len_ciphertext {
panic!("length of received ciphertext not ok")
};
let nonce = GenericArray::from_slice(rand_string2.as_bytes());
let plaintext: Vec<u8> = aead
.decrypt(nonce, ciphertext2.as_ref())
.expect("decryption failure!");
Ok(plaintext)
}
pub fn get_input_string() -> Result<String, Box<dyn std::error::Error>> {
let mut input = String::new();
io::stdin().read_line(&mut input)?;
let trimmed = input.trim().to_string();
Ok(trimmed)
}
pub fn read_file(path: &PathBuf) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut f = File::open(path)?;
let mut buffer: Vec<u8> = Vec::new();
f.read_to_end(&mut buffer)?;
Ok(buffer)
}
pub fn save_file(data: Vec<u8>, path: &PathBuf) -> std::io::Result<()> {
let mut file = File::create(path)?;
file.write_all(&data)?;
Ok(())
}
pub fn get_blake3_hash(data: Vec<u8>) -> Result<blake3::Hash, Box<dyn std::error::Error>> {
let hash = blake3::hash(&data);
Ok(hash)
}
pub fn get_sha256_hash(data: Vec<u8>) -> Result<String, Box<dyn std::error::Error>> {
let mut hasher = Sha256::new();
hasher.update(data);
let hash = hasher.finalize();
Ok(format!("{:?}", hash))
}
pub fn get_sha512_hash(data: Vec<u8>) -> Result<String, Box<dyn std::error::Error>> {
let mut hasher = Sha512::new();
hasher.update(data);
let hash = hasher.finalize();
Ok(format!("{:?}", hash))
}
pub fn choose_hashing_function() -> Result<(), Box<dyn std::error::Error>> {
println!("Please choose type of Hash:\n1 Blake3\n2 SHA256\n3 SHA5212");
let answer = get_input_string()?;
if answer == "1" {
println!("Calculating Blake3 Hash: please enter file path ");
let path = PathBuf::from(get_input_string()?);
let hash = get_blake3_hash(read_file(&path)?)?;
println!("Hash Blake3: {:?}", hash);
} else if answer == "2" {
println!("Calculating SHA256 Hash: please enter file path ");
let path = PathBuf::from(get_input_string()?);
let hash = get_sha256_hash(read_file(&path)?)?;
println!("Hash SHA256: {:?}", hash);
} else if answer == "3" {
println!("Calculating SHA512 Hash: please enter file path ");
let path = PathBuf::from(get_input_string()?);
let hash = get_sha512_hash(read_file(&path)?)?;
println!("Hash SHA512: {:?}", hash);
} else {
println!("Please choose a corresponding number betwenn 1 and 3")
}
Ok(())
}
pub fn decrypt_file(
keymap_plaintext: HashMap<String, String>,
enc: &str,
) -> Result<(), Box<dyn std::error::Error>> {
if keymap_plaintext.is_empty() {
panic!("No keys avaible. Please first add a key.")
}
println!("Decrypting file: please enter file path ");
let path = PathBuf::from(get_input_string()?);
let ciphertext = read_file(&path)?;
let new_filename = PathBuf::from(
&path
.to_str()
.expect("Unable to parse filename!")
.replace(r#".crpt"#, r#""#),
);
println!("Existing keynames");
for (entry, _) in &keymap_plaintext {
println!("{}", entry)
}
println!("Please provide keyname to decrypt: ");
let answer = get_input_string()?;
let key = keymap_plaintext
.get(&answer)
.expect("No key with that name");
let plaintext = if enc == "chacha" {
decrypt_chacha(ciphertext, key)?
} else if enc == "aes" {
decrypt_aes(ciphertext, key)?
} else {
panic!()
};
save_file(plaintext, &new_filename)?;
Ok(())
}
pub fn encrypt_file(
keymap_plaintext: HashMap<String, String>,
enc: &str,
) -> Result<(), Box<dyn std::error::Error>> {
if keymap_plaintext.is_empty() {
panic!("No keys avaible. Please first add a key.")
}
println!("Encrypting file: please enter file path ");
let path = PathBuf::from(get_input_string()?);
let new_filename = PathBuf::from(
path.clone()
.into_os_string()
.into_string()
.expect("Unable to parse filename!")
+ r#".crpt"#,
);
println!("Existing keynames");
for (entry, _) in &keymap_plaintext {
println!("{}", entry)
}
let cleartext = read_file(&path)?;
println!("Please provide keyname to encrypt: ");
let answer = get_input_string()?;
let key = keymap_plaintext
.get(&answer)
.expect("No key with that name");
let ciphertext = if enc == "chacha" {
encrypt_chacha(cleartext, key)?
} else if enc == "aes" {
encrypt_aes(cleartext, key)?
} else {
panic!()
};
save_file(ciphertext, &new_filename)?;
Ok(())
}
pub fn remove_key(
mut keymap_plaintext: HashMap<String, String>,
password: String,
) -> Result<(), Box<dyn std::error::Error>> {
if keymap_plaintext.is_empty() {
panic!("No keys avaible. Please first add a key.")
}
println!("Existing keynames");
for (entry, _) in &keymap_plaintext {
println!("{}", entry)
}
println!("Please provide keyname to delete: ");
let answer = get_input_string()?;
match keymap_plaintext.remove(&answer) {
Some(_) => println!("Key removed"),
None => println!("No key of this name"),
}
if keymap_plaintext.is_empty() {
println!("Warning: No keys available. Please create a new entry")
}
let encoded: Vec<u8> = encrypt_hashmap(keymap_plaintext, &password)?;
fs::write("key.file", encoded)?;
Ok(())
}
pub fn add_key(
mut keymap_plaintext: HashMap<String, String>,
password: String,
) -> Result<(), Box<dyn std::error::Error>> {
println!("Please choose name for new key: ");
let key_name = get_input_string()?;
println!("Create new random key (r) or manually enter a key (m). Key needs to be valid 32-long char-utf8");
let answer = get_input_string()?;
let mut key = String::new();
if answer == "r" {
let key_rand: String = OsRng
.sample_iter(&Alphanumeric)
.take(32)
.collect::<String>();
key.push_str(&key_rand);
} else if answer == "m" {
println!("Please enter key. Must be valid 32-long char-utf8");
let answer = get_input_string()?;
if answer.len() == 32 {
key.push_str(&answer);
} else {
println!("Please provide a valid 32-long char-utf8")
}
} else {
panic!();
}
keymap_plaintext.insert(key_name.trim().to_string(), key.trim().to_string());
let encoded: Vec<u8> = encrypt_hashmap(keymap_plaintext, &password)?;
fs::write("key.file", encoded)?;
Ok(())
}
pub fn create_new_keyfile(
) -> Result<(String, HashMap<String, String>, bool), Box<dyn std::error::Error>> {
println!("No keyfile found. Create a new one? Y/N");
let answer = get_input_string()?;
if answer == "y" {
println!("Please enter a password (lenth > 8) to encrypt the keyfile: ");
let mut password = String::new();
io::stdin()
.read_line(&mut password)
.expect("Failed to read line");
if password.len() < 8 {
panic!("Password too short!")
}
let mut file = File::create("key.file")?;
println!("Please choose name for new key: ");
let key_name = get_input_string()?;
println!("Create new random key (r) or manually enter a key (m). Key needs to be valid 32-long char-utf8");
let answer = get_input_string()?;
let mut key = String::new();
if answer == "r" {
let key_rand: String = OsRng
.sample_iter(&Alphanumeric)
.take(32)
.collect::<String>();
key.push_str(&key_rand);
} else if answer == "m" {
println!("Please enter key. Must be valid 32-long char-utf8");
let answer = get_input_string()?;
if answer.len() == 32 {
key.push_str(&answer);
} else {
println!("Please provide a valid 32-long char-utf8")
}
} else {
panic!();
}
let mut new_key_map = HashMap::new();
new_key_map.insert(key_name, key);
let encoded: Vec<u8> = encrypt_hashmap(new_key_map.clone(), &password)?;
file.write_all(&encoded)?;
Ok((password, new_key_map, true))
} else {
panic!()
}
}
pub fn read_keyfile() -> Result<(String, HashMap<String, String>, bool), Box<dyn std::error::Error>>
{
println!("Enter password: ");
let password = get_input_string()?;
let hashed_password = blake3::hash(&password.trim().as_bytes());
let mut f = File::open("key.file").expect("Could not open key.file");
let mut buffer = Vec::new();
f.read_to_end(&mut buffer)?;
let key = GenericArray::clone_from_slice(hashed_password.as_bytes());
let decoded: Cipher = bincode::deserialize(&buffer[..])?;
let (ciphertext, len_ciphertext, rand_string) =
(decoded.ciphertext, decoded.len, decoded.rand_string);
if ciphertext.len() != len_ciphertext {
panic!("length of received ciphertext not ok")
};
let nonce = GenericArray::from_slice(rand_string.as_bytes());
let aead = XChaCha20Poly1305::new(&key);
let plaintext: Vec<u8> = aead
.decrypt(nonce, ciphertext.as_ref())
.expect("decryption failure!");
let keymap_plaintext: HashMap<String, String> = bincode::deserialize(&plaintext[..])?;
println!("Keys found in key.file:\n{:?}\n", &keymap_plaintext);
Ok((password, keymap_plaintext, false))
}
pub fn encrypt_hashmap(
keymap_plaintext: HashMap<String, String>,
password: &String,
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let encoded: Vec<u8> = bincode::serialize(&keymap_plaintext).expect("Unable to encode keymap!");
let rand_string: String = OsRng
.sample_iter(&Alphanumeric)
.take(24)
.collect::<String>();
let nonce = GenericArray::from_slice(rand_string.as_bytes());
let hashed_password = blake3::hash(&password.trim().as_bytes());
let key = GenericArray::clone_from_slice(hashed_password.as_bytes());
let aead = XChaCha20Poly1305::new(&key);
let ciphertext: Vec<u8> = aead
.encrypt(nonce, encoded.as_ref())
.expect("encryption failure!");
let ciphertext_to_send = Cipher {
len: ciphertext.len(),
rand_string,
ciphertext,
};
let encoded: Vec<u8> =
bincode::serialize(&ciphertext_to_send).expect("Unable to encode keymap!");
Ok(encoded)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::remove_file;
#[test]
fn test_save_read_file() {
let content: Vec<u8> = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let path: PathBuf = PathBuf::from("test_abcdefg.file");
save_file(content.clone(), &path).unwrap();
let content_read: Vec<u8> = read_file(&path).unwrap();
remove_file(&path).unwrap();
assert_eq!(content, content_read);
}
#[test]
fn test_encryt_decrypt_aes() {
let text = b"This a test";
let key: &str = "an example very very secret key.";
let text_vec = text.to_vec();
let ciphertext = encrypt_aes(text_vec, key).unwrap();
assert_ne!(&ciphertext, &text);
let plaintext = decrypt_aes(ciphertext, key).unwrap();
assert_eq!(format!("{:?}", text), format!("{:?}", plaintext));
}
#[test]
fn test_encryt_decrypt_chacha() {
let text = b"This a test";
let key: &str = "an example very very secret key.";
let text_vec = text.to_vec();
let ciphertext = encrypt_chacha(text_vec, key).unwrap();
assert_ne!(&ciphertext, &text);
let plaintext = decrypt_chacha(ciphertext, key).unwrap();
assert_eq!(format!("{:?}", text), format!("{:?}", plaintext));
}
#[test]
fn test_multiple_encrypt_unequal_chacha() {
use rand::{distributions::Uniform, Rng};
let mut rng = rand::thread_rng();
let range = Uniform::new(0, 255);
let mut i = 1;
while i < 1000 {
let key: String = OsRng
.sample_iter(&Alphanumeric)
.take(32)
.collect::<String>();
let content: Vec<u8> = (0..100).map(|_| rng.sample(&range)).collect();
let ciphertext1 = encrypt_chacha(content.clone(), &key).unwrap();
let ciphertext2 = encrypt_chacha(content.clone(), &key).unwrap();
let ciphertext3 = encrypt_chacha(content.clone(), &key).unwrap();
let ciphertext4 = encrypt_chacha(content.clone(), &key).unwrap();
let ciphertext5 = encrypt_chacha(content, &key).unwrap();
assert_ne!(&ciphertext1, &ciphertext2);
assert_ne!(&ciphertext1, &ciphertext3);
assert_ne!(&ciphertext1, &ciphertext4);
assert_ne!(&ciphertext1, &ciphertext5);
assert_ne!(&ciphertext2, &ciphertext3);
assert_ne!(&ciphertext2, &ciphertext4);
assert_ne!(&ciphertext2, &ciphertext5);
assert_ne!(&ciphertext3, &ciphertext4);
assert_ne!(&ciphertext3, &ciphertext5);
assert_ne!(&ciphertext4, &ciphertext5);
i += 1;
}
}
#[test]
fn test_multiple_encrypt_unequal_aes() {
use rand::{distributions::Uniform, Rng};
let mut rng = rand::thread_rng();
let range = Uniform::new(0, 255);
let mut i = 1;
while i < 1000 {
let key: String = OsRng
.sample_iter(&Alphanumeric)
.take(32)
.collect::<String>();
let content: Vec<u8> = (0..100).map(|_| rng.sample(&range)).collect();
let ciphertext1 = encrypt_aes(content.clone(), &key).unwrap();
let ciphertext2 = encrypt_aes(content.clone(), &key).unwrap();
let ciphertext3 = encrypt_aes(content.clone(), &key).unwrap();
let ciphertext4 = encrypt_aes(content.clone(), &key).unwrap();
let ciphertext5 = encrypt_aes(content, &key).unwrap();
assert_ne!(&ciphertext1, &ciphertext2);
assert_ne!(&ciphertext1, &ciphertext3);
assert_ne!(&ciphertext1, &ciphertext4);
assert_ne!(&ciphertext1, &ciphertext5);
assert_ne!(&ciphertext2, &ciphertext3);
assert_ne!(&ciphertext2, &ciphertext4);
assert_ne!(&ciphertext2, &ciphertext5);
assert_ne!(&ciphertext3, &ciphertext4);
assert_ne!(&ciphertext3, &ciphertext5);
assert_ne!(&ciphertext4, &ciphertext5);
i += 1;
}
}
#[test]
fn test_hash_blake3() {
let filename = PathBuf::from("cargo.toml");
let hash1 = get_blake3_hash(read_file(&filename).unwrap()).unwrap();
let hash2 = get_blake3_hash(read_file(&filename).unwrap()).unwrap();
assert_eq!(hash1, hash2);
}
#[test]
fn test_hash_sha256() {
let filename = PathBuf::from("cargo.toml");
let hash1 = get_sha256_hash(read_file(&filename).unwrap()).unwrap();
let hash2 = get_sha256_hash(read_file(&filename).unwrap()).unwrap();
assert_eq!(hash1, hash2);
}
#[test]
fn test_hash_sha512() {
let filename = PathBuf::from("cargo.toml");
let hash1 = get_sha512_hash(read_file(&filename).unwrap()).unwrap();
let hash2 = get_sha512_hash(read_file(&filename).unwrap()).unwrap();
assert_eq!(hash1, hash2);
}
#[test]
fn test_multiple_random_chacha() {
use rand::{distributions::Uniform, Rng};
let mut rng = rand::thread_rng();
let range = Uniform::new(0, 255);
let mut i = 1;
while i < 1000 {
let key: String = OsRng
.sample_iter(&Alphanumeric)
.take(32)
.collect::<String>();
let content: Vec<u8> = (0..100).map(|_| rng.sample(&range)).collect();
let ciphertext = encrypt_chacha(content.clone(), &key).unwrap();
assert_ne!(&ciphertext, &content);
let plaintext = decrypt_chacha(ciphertext, &key).unwrap();
assert_eq!(format!("{:?}", content), format!("{:?}", plaintext));
i += 1;
}
}
#[test]
fn test_multiple_random_aes() {
use rand::{distributions::Uniform, Rng};
let mut rng = rand::thread_rng();
let range = Uniform::new(0, 255);
let mut i = 1;
while i < 1000 {
let key: String = OsRng
.sample_iter(&Alphanumeric)
.take(32)
.collect::<String>();
let content: Vec<u8> = (0..100).map(|_| rng.sample(&range)).collect();
let ciphertext = encrypt_aes(content.clone(), &key).unwrap();
assert_ne!(&ciphertext, &content);
let plaintext = decrypt_aes(ciphertext, &key).unwrap();
assert_eq!(format!("{:?}", content), format!("{:?}", plaintext));
i += 1;
}
}
#[test]
fn test_example() {
let text = b"This a test";
let key: &str = "an example very very secret key.";
let text_vec = text.to_vec();
let ciphertext = encrypt_chacha(text_vec, key).unwrap();
assert_ne!(&ciphertext, &text);
let plaintext = decrypt_chacha(ciphertext, key).unwrap();
assert_eq!(format!("{:?}", text), format!("{:?}", plaintext));
}
#[test]
fn test_example_hash() {
let test = b"Calculating the BLAKE3 Hash of this text";
let test_vec = test.to_vec();
let hash1 = get_blake3_hash(test_vec.clone()).unwrap();
let hash2 = get_blake3_hash(test_vec).unwrap();
assert_eq!(hash1, hash2);
}
}