From 3e93f7154749f8920d59a0aefa377291ef51a828 Mon Sep 17 00:00:00 2001 From: Avril Date: Sun, 21 Mar 2021 21:33:36 +0000 Subject: [PATCH] better write efficiency update README update help message remove planned formatting support in TODO in favour of outside tools --- Cargo.lock | 2 +- Cargo.toml | 11 +++------ README.md | 21 ++++++++++++++--- TODO | 1 - src/main.rs | 18 ++++++++++----- src/stream.rs | 64 +++++++++++++++++++++++++++++---------------------- test.sh | 2 +- 7 files changed, 71 insertions(+), 48 deletions(-) delete mode 100644 TODO diff --git a/Cargo.lock b/Cargo.lock index 8645a9e..7d509e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,7 +34,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chacha20" -version = "1.0.0" +version = "1.0.1" dependencies = [ "base64", "getrandom", diff --git a/Cargo.toml b/Cargo.toml index 15491a8..3a02907 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,18 +1,13 @@ [package] name = "chacha20" description = "chacha20_poly1305 encryption tool" -version = "1.0.0" +version = "1.0.1" authors = ["Avril "] edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +license = "gpl-3.0-or-later" [dependencies] base64 = "0.13" getrandom = "0.2" openssl = "0.10" -smallvec = {version = "1.6", features=["write", "union"]} - - -[dev-dependencies] -#khash = {version = "2.0.4", default-features=false} +smallvec = {version = "1.6", features=["write", "union"]} \ No newline at end of file diff --git a/README.md b/README.md index d86dae4..0afdb96 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A simple chacha20_poly1305 CLI encryption tool ## Building -Requires Rust and Cargo to build. +Requires Rust and Cargo to build; also requires OpenSSL v1.1.0 or higher. Run `cargo build --release`, the binary will be built to `./target/release/chacha20`. ### Testing @@ -10,22 +10,37 @@ Run `cargo test && cargo build && ./test.sh debug` to test the program. Alternatively, run `./test.sh` after building to test the release build's correctness. # Usage -Copies stdin to stdout while encrypting or decrypting with the stream cipher. +Copies stdin to stdout while encrypting or decrypting with the stream cipher `chacha20_poly1305`. ## Modes * Encrypt - Encrypt stdin to stdout * Decrypt - Decrypt stdin to stdout * Keygen - Generate a random key and IV and print them to stdout -To see a more detailed explenation run `chacha20` with no arguments. +To see a more detailed explenation run `chacha20 help`. + +## Formats +The key and IV is expected/generated in base64 format. +The key and IV sizes respectively are 32 and 12 bytes. + +The ciphertext input and output is raw binary data. You can encode this to text formats if you want with whatever tool you choose (Example with `base64` below.) ## Example +Encrypting and decrypting a string to binary with randomly generated keys ```shell $ echo "Hello world!" | chacha20 e 2>keys.cck > output.cc20 $ chacha20 d $(cat keys.cck) < output.cc20 Hello world! ``` +The same but with text instead of binary ciphertexts + +``` shell +$ echo "Hello world!" | chacha20 e 2>keys.cck | base64 > output.cc20.b64 +$ base64 --decode output.cc20.b64 | chacha20 d $(cat keys.cck) +Hello world! +``` + # License GPL'd with <3 diff --git a/TODO b/TODO deleted file mode 100644 index 37f239d..0000000 --- a/TODO +++ /dev/null @@ -1 +0,0 @@ -Add `b-encrypt` mode to write the outputted bytes in base64 instead of raw bytes, and `b-decrypt` mode to read the inputted bytes in base64 instead of raw bytes diff --git a/src/main.rs b/src/main.rs index cd02001..b4858a7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,10 @@ +//#![feature(test)] #![allow(dead_code)] -mod ext; #[macro_use] use ext::*; +//extern crate test; + +#[macro_use] mod ext; #[allow(unused_imports)] use ext::*; mod key; mod cha; @@ -30,7 +33,7 @@ fn keys() -> Result<(Mode, Key, IV), base64::DecodeError> let (key, iv) = cha::keygen(); return Ok((Mode::Keygen, key, iv)); }, - _ => { + other => { eprintln!("{} (v{}) - chacha20_poly1305 command line encryption tool", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")); @@ -40,12 +43,15 @@ fn keys() -> Result<(Mode, Key, IV), base64::DecodeError> eprintln!("Usage: {} encrypt [] []", prog_name); eprintln!("Usage: {} decrypt [] []", prog_name); eprintln!("Usage: {} keygen", prog_name); + eprintln!("Usage: {} help", prog_name); eprintln!(); eprintln!("(Key size is {}, IV size is {})", cha::KEY_SIZE, cha::IV_SIZE); - eprintln!("\nencrypt/decrypt:\n\tIf key and/or IV are not provided, they are generated randomly and printed to stderr in order on one line each"); - eprintln!("\tIf the key and/or IV provided's size is lower than the cipher's key/IV size, the rest of the key/IV padded with 0s. If the size is higher, the extra bytes are ignored."); - eprintln!("\nkeygen:\n\tThe key/iv is printed in the same way as auto-generated keys for the en/decryption modes, but to stdout instead of stderr"); - std::process::exit(1) + eprintln!("(requires OpenSSL 1.1.0 or newer)"); + eprintln!("\nencrypt/decrypt:\n\tIf a key and/or IV are not provided, they are generated randomly and printed to stderr in order on one line each."); + eprintln!("\tIf the key and/or IV provided's size is lower than the cipher's key/IV size, the rest of the key/IV is padded with 0s. If the size is higher, the extra bytes are ignored."); + eprintln!("\nkeygen:\n\tThe key/iv is printed in the same way as auto-generated keys for the en/decryption modes, but to stdout instead of stderr."); + eprintln!("\nhelp:\n\tPrint this message to stderr then exit with code 0"); + std::process::exit(if other == Some('h') {0} else {1}) } }; diff --git a/src/stream.rs b/src/stream.rs index 54ed35f..62c9bf6 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -15,6 +15,10 @@ pub const BUFFER_SIZE: usize = 32; pub type Error = ErrorStack; /// ChaCha Sink +/// +/// # Note +/// When writing, a temporary buffer stored in the structure is used. This buffer is **not** cleared after a write, for efficiency reasons. This may leave sensitive information in the buffer after the write operation. +/// The `flush()` implementation *does* clear this buffer. //#[derive(Debug)] pub struct Sink { @@ -36,7 +40,7 @@ impl Sink where W: Write { /// Create a new Chacha Sink stream wrapper - pub fn new(stream: W, crypter: Crypter) -> Self + #[inline] fn new(stream: W, crypter: Crypter) -> Self { Self{stream, crypter, buffer: SmallVec::new()} } @@ -55,69 +59,72 @@ where W: Write /// Consume into the inner stream - pub fn into_inner(self) -> W + #[inline] pub fn into_inner(self) -> W { self.stream } /// Consume into the inner stream and crypter - pub fn into_parts(self) -> (W, Crypter) + #[inline] pub fn into_parts(self) -> (W, Crypter) { (self.stream, self.crypter) } /// The crypter of this instance - pub fn crypter(&self) -> &Crypter + #[inline] pub fn crypter(&self) -> &Crypter { &self.crypter } /// The crypter of this instance - pub fn crypter_mut(&mut self) -> &mut Crypter + #[inline] pub fn crypter_mut(&mut self) -> &mut Crypter { &mut self.crypter } /// The inner stream - pub fn inner(&self) -> &W + #[inline] pub fn inner(&self) -> &W { &self.stream } /// The inner stream - pub fn inner_mut(&mut self) -> &mut W + #[inline] pub fn inner_mut(&mut self) -> &mut W { &mut self.stream } + + /// Perform the cipher transform on this input to the inner buffer, returning the number of bytes updated. + fn transform(&mut self, buf: &[u8]) -> Result + { + if buf.len() > self.buffer.len() { + self.buffer.resize(buf.len(), 0); + } + + let n = self.crypter.update(&buf[..], &mut self.buffer[..])?; + let _f = self.crypter.finalize(&mut self.buffer[..n])?; // I don't know if this is needed. + debug_assert_eq!(_f, 0); + + Ok(n) + } } impl Write for Sink { - fn write(&mut self, buf: &[u8]) -> io::Result { - prog1!{ - { - self.buffer.write_all(buf).unwrap(); - let n = self.crypter.update(&buf[..], &mut self.buffer[..])?; - self.crypter.finalize(&mut self.buffer[..n])?; // I don't think this is needed - - self.stream.write(&self.buffer[..n]) - }, - self.buffer.clear(); - } + #[inline] fn write(&mut self, buf: &[u8]) -> io::Result { + let n = self.transform(buf)?; + + self.stream.write(&self.buffer[..n]) + } - fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - prog1!{ - { - self.buffer.write_all(buf).unwrap(); - let n = self.crypter.update(&buf[..], &mut self.buffer[..])?; - self.crypter.finalize(&mut self.buffer[..n])?; + #[inline] fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + let n = self.transform(buf)?; - self.stream.write_all(&self.buffer[..n]) - }, - self.buffer.clear(); - } + self.stream.write_all(&self.buffer[..n]) } #[inline] fn flush(&mut self) -> io::Result<()> { + self.buffer.clear(); + self.stream.flush() } } @@ -175,3 +182,4 @@ mod tests } } + diff --git a/test.sh b/test.sh index fb92480..1903026 100755 --- a/test.sh +++ b/test.sh @@ -17,7 +17,7 @@ else echo "" fi -INPUT_SIZE=4096 +INPUT_SIZE=${INPUT_SIZE:-4096} echo ">>> Generating random input ($INPUT_SIZE bytes)" dd if=/dev/urandom "bs=$INPUT_SIZE" count=1 | base64 > test.txt