diff --git a/Cargo.lock b/Cargo.lock index 22dcd2f..3234dab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -264,6 +264,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chacha20-poly1305-stream" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3de0436856104bf1d2e4d5ea07661cb1a16268157f2a130c48253237ef09a457" +dependencies = [ + "constant_time_eq", + "hex", +] + [[package]] name = "chrono" version = "0.4.31" @@ -353,6 +363,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -361,9 +377,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "3fbc60abd742b35f2492f808e1abbb83d45f72db402e14c55057edc9c7b1e9e4" dependencies = [ "libc", ] @@ -1653,18 +1669,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.49" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.49" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", @@ -1717,6 +1733,7 @@ version = "0.4.4" dependencies = [ "aes-gcm-stream", "base64", + "chacha20-poly1305-stream", "chrono", "clap", "flate2", diff --git a/Cargo.toml b/Cargo.toml index aa8b85e..9c7d84c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ repository = "https://git.hatter.ink/hatter/tiny-encrypt-rs" [dependencies] aes-gcm-stream = "0.2" base64 = "0.21" +chacha20-poly1305-stream = "0.1.0" chrono = "0.4" clap = { version = "4.4", features = ["derive"] } flate2 = "1.0" diff --git a/src/consts.rs b/src/consts.rs index 6488582..eee3fa6 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -8,6 +8,7 @@ pub const TINY_ENC_FILE_EXT: &str = ".tinyenc"; pub const TINY_ENC_CONFIG_FILE: &str = "~/.tinyencrypt/config-rs.json"; pub const TINY_ENC_AES_GCM: &str = "AES/GCM"; +pub const TINY_ENC_CHACHA20_POLY1305: &str = "CHACHA20/POLY1305"; // Tiny enc magic tag pub const TINY_ENC_MAGIC_TAG: u16 = 0x01; diff --git a/src/crypto_cryptor.rs b/src/crypto_cryptor.rs new file mode 100644 index 0000000..66edfb6 --- /dev/null +++ b/src/crypto_cryptor.rs @@ -0,0 +1,157 @@ +use aes_gcm_stream::{Aes256GcmStreamDecryptor, Aes256GcmStreamEncryptor}; +use chacha20_poly1305_stream::{ChaCha20Poly1305StreamDecryptor, ChaCha20Poly1305StreamEncryptor}; +use rust_util::{opt_result, simple_error, XResult}; +use zeroize::Zeroize; + +use crate::consts; + +#[derive(Debug, Copy, Clone)] +pub enum Cryptor { + Aes256Gcm, + ChaCha20Poly1305, +} + +impl Cryptor { + pub fn from(algorithm: &str) -> XResult { + match algorithm { + "aes256-gcm" | consts::TINY_ENC_AES_GCM => Ok(Cryptor::Aes256Gcm), + "chacha20-poly1305" | consts::TINY_ENC_CHACHA20_POLY1305 => Ok(Cryptor::ChaCha20Poly1305), + _ => simple_error!("Unknown altorighm: {}",algorithm), + } + } + + pub fn encryptor(self, key: &[u8], nonce: &[u8]) -> XResult> { + get_encryptor(self, key, nonce) + } + + pub fn decryptor(self, key: &[u8], nonce: &[u8]) -> XResult> { + get_decryptor(self, key, nonce) + } +} + +pub trait Encryptor { + fn update(&mut self, message: &[u8]) -> Vec; + + fn finalize(&mut self) -> (Vec, Vec); + + fn encrypt(&mut self, message: &[u8]) -> Vec { + let mut cipher_text = self.update(message); + let (last_block, tag) = self.finalize(); + cipher_text.extend_from_slice(&last_block); + cipher_text.extend_from_slice(&tag); + cipher_text + } +} + +pub trait Decryptor { + fn update(&mut self, message: &[u8]) -> Vec; + + fn finalize(&mut self) -> XResult>; + + fn decrypt(&mut self, message: &[u8]) -> XResult> { + let mut plaintext = self.update(message); + let last_block = self.finalize()?; + plaintext.extend_from_slice(&last_block); + Ok(plaintext) + } +} + +fn get_encryptor(crypto: Cryptor, key: &[u8], nonce: &[u8]) -> XResult> { + match crypto { + Cryptor::Aes256Gcm => { + let mut key: [u8; 32] = opt_result!(key.try_into(), "Bad AES 256 key: {}"); + let aes256_gcm_stream_encryptor = Aes256GcmStreamEncryptor::new(key, nonce); + key.zeroize(); + Ok(Box::new(Aes256GcmEncryptor { + aes256_gcm_stream_encryptor, + })) + } + Cryptor::ChaCha20Poly1305 => Ok(Box::new(ChaCha20Poly1305Encryptor { + chacha20_poly1305_stream_encryptor: ChaCha20Poly1305StreamEncryptor::new(key, nonce)?, + })) + } +} + +fn get_decryptor(crypto: Cryptor, key: &[u8], nonce: &[u8]) -> XResult> { + match crypto { + Cryptor::Aes256Gcm => { + let mut key: [u8; 32] = opt_result!(key.try_into(), "Bad AES 256 key: {}"); + let aes256_gcm_stream_decryptor = Aes256GcmStreamDecryptor::new(key, nonce); + key.zeroize(); + Ok(Box::new(Aes256GcmDecryptor { + aes256_gcm_stream_decryptor, + })) + } + Cryptor::ChaCha20Poly1305 => Ok(Box::new(ChaCha20Poly1305Decryptor { + chacha20_poly1305_stream_decryptor: ChaCha20Poly1305StreamDecryptor::new(key, nonce)?, + })) + } +} + +pub struct Aes256GcmEncryptor { + aes256_gcm_stream_encryptor: Aes256GcmStreamEncryptor, +} + +impl Encryptor for Aes256GcmEncryptor { + fn update(&mut self, message: &[u8]) -> Vec { + self.aes256_gcm_stream_encryptor.update(message) + } + + fn finalize(&mut self) -> (Vec, Vec) { + self.aes256_gcm_stream_encryptor.finalize() + } +} + +pub struct ChaCha20Poly1305Encryptor { + chacha20_poly1305_stream_encryptor: ChaCha20Poly1305StreamEncryptor, +} + +impl Encryptor for ChaCha20Poly1305Encryptor { + fn update(&mut self, message: &[u8]) -> Vec { + self.chacha20_poly1305_stream_encryptor.update(message) + } + + fn finalize(&mut self) -> (Vec, Vec) { + self.chacha20_poly1305_stream_encryptor.finalize() + } +} + +pub struct Aes256GcmDecryptor { + aes256_gcm_stream_decryptor: Aes256GcmStreamDecryptor, +} + +impl Decryptor for Aes256GcmDecryptor { + fn update(&mut self, message: &[u8]) -> Vec { + self.aes256_gcm_stream_decryptor.update(message) + } + + fn finalize(&mut self) -> XResult> { + Ok(self.aes256_gcm_stream_decryptor.finalize()?) + } +} + +pub struct ChaCha20Poly1305Decryptor { + chacha20_poly1305_stream_decryptor: ChaCha20Poly1305StreamDecryptor, +} + +impl Decryptor for ChaCha20Poly1305Decryptor { + fn update(&mut self, message: &[u8]) -> Vec { + self.chacha20_poly1305_stream_decryptor.update(message) + } + + fn finalize(&mut self) -> XResult> { + Ok(self.chacha20_poly1305_stream_decryptor.finalize()?) + } +} + +#[test] +fn test_cryptor() { + let key = [0u8; 32]; + let nonce = [0u8; 12]; + let ciphertext = Cryptor::Aes256Gcm.encryptor(&key, &nonce).unwrap() + .encrypt(b"hello world"); + let plaintext = Cryptor::Aes256Gcm.decryptor(&key, &nonce).unwrap() + .decrypt(&ciphertext).unwrap(); + + assert_eq!(b"hello world", plaintext.as_slice()); +} diff --git a/src/main.rs b/src/main.rs index 80ef2c0..5e4e65e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,6 +23,7 @@ mod config; mod spec; mod crypto_aes; mod crypto_rsa; +mod crypto_cryptor; mod wrap_key; mod util_envelop; mod util_file;