on the way to a usable version

This commit is contained in:
2024-02-14 22:23:57 +01:00
parent 668e726c21
commit ad03e176c3
7 changed files with 437 additions and 201 deletions
+57 -42
View File
@@ -1,54 +1,61 @@
// SPDX-License-Identifier: GPL-3.0-only
use chacha20poly1305::{
aead::{stream, NewAead},
ChaCha20Poly1305,
};
use chacha20poly1305::{aead::stream, KeyInit, XChaCha20Poly1305};
use rand::{rngs::OsRng, RngCore};
use crate::error::*;
use crate::reader::ReadInfo;
use crate::utils::BUFSIZE;
use crate::utils::*;
const BUFSIZE: usize = 64 * 1024; // 64 KiB
fn new_random_nonce() -> Result<[u8; 7], FcryError> {
let mut nonce = [0u8; 7];
OsRng.try_fill_bytes(&mut nonce)?;
Ok(nonce)
}
pub fn encrypt<S: AsRef<str>>(
input_file: Option<S>,
output_file: Option<S>,
key: [u8; 32],
) -> Result<(), FcryError> {
let mut f_plain = read_from_file_or_stdin(input_file);
let mut f_plain = read_from_file_or_stdin(input_file, BUFSIZE);
let mut f_encrypted = write_to_file_or_stdout(output_file);
let nonce = new_random_nonce()?;
let mut nonce = [0u8; 19];
OsRng.fill_bytes(&mut nonce);
// let key = XChaCha20Poly1305::generate_key(&mut OsRng);
f_encrypted.write_all(&nonce)?;
let aead = ChaCha20Poly1305::new(&key.into());
let aead = XChaCha20Poly1305::new(&key.into());
let mut stream_encryptor = stream::EncryptorBE32::from_aead(aead, &nonce.into());
let mut buf = vec![0; BUFSIZE];
let mut read_bytes;
loop {
read_bytes = f_plain.read(&mut buf)?;
let read_result = f_plain.read_ahead(&mut buf)?;
if read_bytes < BUFSIZE {
break;
match read_result {
ReadInfo::NormalChunk(n) => {
assert_eq!(n, BUFSIZE);
assert_eq!(buf.len(), BUFSIZE);
println!("[encrypt]: read normal chunk");
stream_encryptor.encrypt_next_in_place(&[], &mut buf)?;
f_encrypted.write_all(&buf)?;
// buf grows after encrypt_next_in_place because of tag that is added
// we shrink it to the BUFSIZE in order to read the correct size
buf.truncate(BUFSIZE);
}
ReadInfo::LastChunk(n) => {
println!("[encrypt]: read last chunk");
buf.truncate(n);
stream_encryptor.encrypt_last_in_place(&[], &mut buf)?;
f_encrypted.write_all(&buf)?;
break;
}
ReadInfo::EmptyChunk => {
println!("[encrypt]: read empty chunk");
panic!("[ERROR] Empty Chunk while reading");
}
}
stream_encryptor.encrypt_next_in_place(&[], &mut buf)?;
f_encrypted.write_all(&buf)?;
buf.truncate(BUFSIZE);
}
buf.truncate(read_bytes);
stream_encryptor.encrypt_last_in_place(&[], &mut buf)?;
f_encrypted.write_all(&buf)?;
Ok(())
}
@@ -57,33 +64,41 @@ pub fn decrypt<S: AsRef<str>>(
output_file: Option<S>,
key: [u8; 32],
) -> Result<(), FcryError> {
let mut f_encrypted = read_from_file_or_stdin(input_file);
let mut f_encrypted = read_from_file_or_stdin(input_file, BUFSIZE + 16);
let mut f_plain = write_to_file_or_stdout(output_file);
let mut nonce = [0u8; 7];
let mut nonce = [0u8; 19];
f_encrypted.read_exact(&mut nonce)?;
let aead = ChaCha20Poly1305::new(&key.into());
let aead = XChaCha20Poly1305::new(&key.into());
let mut stream_decryptor = stream::DecryptorBE32::from_aead(aead, &nonce.into());
let mut buf = vec![0; BUFSIZE + 16];
let mut read_bytes;
loop {
read_bytes = f_encrypted.read(&mut buf)?;
let read_result = f_encrypted.read_ahead(&mut buf)?;
if read_bytes < BUFSIZE + 16 {
break;
match read_result {
ReadInfo::NormalChunk(n) => {
assert_eq!(n, BUFSIZE + 16);
println!("[decrypt]: read normal chunk");
stream_decryptor.decrypt_next_in_place(&[], &mut buf)?;
f_plain.write_all(&buf)?;
buf.resize(BUFSIZE + 16, 0);
}
ReadInfo::LastChunk(n) => {
println!("[decrypt]: read last chunk");
buf.truncate(n);
stream_decryptor.decrypt_last_in_place(&[], &mut buf)?;
f_plain.write_all(&buf)?;
break;
}
ReadInfo::EmptyChunk => {
println!("[decrypt]: read empty chunk");
panic!("Empty Chunk while reading");
}
}
stream_decryptor.decrypt_next_in_place(&[], &mut buf)?;
f_plain.write_all(&buf)?;
buf.resize(BUFSIZE + 16, 0);
}
buf.truncate(read_bytes);
stream_decryptor.decrypt_last_in_place(&[], &mut buf)?;
f_plain.write_all(&buf)?;
Ok(())
}
+1
View File
@@ -1,4 +1,5 @@
// SPDX-License-Identifier: GPL-3.0-only
use chacha20poly1305::aead;
use std::io;
+34 -32
View File
@@ -1,47 +1,48 @@
// SPDX-License-Identifier: GPL-3.0-only
mod crypto;
mod error;
mod reader;
mod utils;
use clap::{Arg, ArgMatches, Command};
use crypto::*;
use error::FcryError;
fn build_cmdline_args() -> ArgMatches {
Command::new("fcry - [f]ile[cry]pt").version(env!("CARGO_PKG_VERSION"))
.about("A file en-/decryption tool for easy use.")
.arg(Arg::new("decrypt")
.short('d')
.long("decrypt")
.help("Decrypt instead of encrypt. Encrypting is the default.")
.takes_value(false))
.arg(Arg::new("input_file")
.short('i')
.long("input-file")
.help("The input file to en-/decrypt")
.takes_value(true))
.arg(Arg::new("output_file")
.short('o')
.long("output-file")
.help("The output file. If not specified, en-/decrypted bytes will be sent to stdout.")
.takes_value(true))
.arg(Arg::new("raw-key")
.required(true)
.long("raw-key")
.help("The raw bytes of the crypto key. Has to be exactly 32 bytes.\n*** DANGEROUS, use for testing purposes only! ***")
.takes_value(true))
.get_matches()
use clap::Parser;
/// fcry - [f]ile[cry]pt: A file en-/decryption tool for easy use
#[derive(Parser, Debug)]
#[clap(author, version, about)]
struct Cli {
/// decrypt instead of encrypt (encryption is the default)
#[clap(short, long)]
decrypt: bool,
/// The input file to en-/decrypt
#[clap(short, long)]
input_file: Option<String>,
/// The output file.
/// If not specified, en-/decrypted bytes will be sent to stdout
#[clap(short, long)]
output_file: Option<String>,
/// The raw bytes of the crypto key.
/// Has to be exactly 32 bytes
/// *** DANGEROUS, use for testing purposes only! ***
#[clap(short, long)]
raw_key: String,
}
fn run(args: ArgMatches) -> Result<(), FcryError> {
let input_file = args.value_of("input_file");
let output_file = args.value_of("output_file");
fn run(cli: Cli) -> Result<(), FcryError> {
let input_file = cli.input_file;
let output_file = cli.output_file;
let mut key = [0u8; 32];
key.clone_from_slice(args.value_of("raw-key").unwrap().as_bytes());
dbg!(&cli.raw_key);
key.clone_from_slice(cli.raw_key.as_bytes());
if args.is_present("decrypt") {
if cli.decrypt {
decrypt(input_file, output_file, key)?
} else {
encrypt(input_file, output_file, key)?
@@ -51,7 +52,8 @@ fn run(args: ArgMatches) -> Result<(), FcryError> {
}
fn main() {
if let Err(e) = run(build_cmdline_args()) {
let cli = Cli::parse();
if let Err(e) = run(cli) {
println!("Error: {:?}", e);
}
}
+100
View File
@@ -0,0 +1,100 @@
// SPDX-License-Identifier: GPL-3.0-only
use std::io;
use std::io::{BufRead, Read};
pub enum ReadInfo {
NormalChunk(usize),
LastChunk(usize),
EmptyChunk,
}
pub struct AheadReader {
inner: Box<dyn BufRead>,
buf: Vec<u8>,
bufsz: usize,
capacity: usize,
}
impl AheadReader {
pub fn from(reader: Box<dyn BufRead>, capacity: usize) -> Self {
Self {
inner: reader,
buf: vec![0; capacity],
bufsz: 0,
capacity,
}
}
fn read_until_full(&mut self, mut buf: &mut [u8]) -> io::Result<usize> {
let mut total = 0;
loop {
match self.inner.read(buf) {
Ok(0) => break,
Ok(n) => {
total += n;
let tmp = buf;
buf = &mut tmp[n..];
}
Err(e) => match e.kind() {
io::ErrorKind::Interrupted => continue,
_ => return Err(e),
},
}
}
Ok(total)
}
pub fn read_ahead(&mut self, userbuf: &mut [u8]) -> io::Result<ReadInfo> {
// 1st read
if self.bufsz == 0 {
println!("[reader] first read");
return self.first_read(userbuf);
}
println!("[reader] normal read");
// normal read (not the 1st one)
self.normal_read(userbuf)
}
pub fn read_exact(&mut self, userbuf: &mut [u8]) -> io::Result<()> {
self.inner.read_exact(userbuf)
}
fn first_read(&mut self, userbuf: &mut [u8]) -> io::Result<ReadInfo> {
// 1st read directly to userbuf (we have no cached data yet)
let n = self.read_until_full(userbuf)?;
if n == 0 {
return Ok(ReadInfo::EmptyChunk);
}
// 2nd read directly into our internal buf
let mut tmp = vec![0u8; self.capacity];
let n2 = self.read_until_full(&mut tmp)?;
self.buf = tmp;
self.bufsz = n2;
if n2 == 0 {
return Ok(ReadInfo::LastChunk(n));
}
Ok(ReadInfo::NormalChunk(n))
}
fn normal_read(&mut self, userbuf: &mut [u8]) -> io::Result<ReadInfo> {
// copy internal buf to userbuf
userbuf.copy_from_slice(&self.buf);
let userbuf_sz = self.bufsz;
// 2nd read directly into our internal buf
let mut tmp = vec![0u8; self.capacity];
let n2 = self.read_until_full(&mut tmp)?;
self.buf = tmp;
self.bufsz = n2;
if n2 == 0 {
return Ok(ReadInfo::LastChunk(userbuf_sz));
}
Ok(ReadInfo::NormalChunk(userbuf_sz))
}
}
+16 -4
View File
@@ -1,13 +1,25 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::reader::AheadReader;
use std::io::BufReader;
use std::{
fs::File,
io::{self, Read, Write},
io::{self, Write},
};
pub(crate) fn read_from_file_or_stdin<S: AsRef<str>>(input_file: Option<S>) -> Box<dyn Read> {
pub const BUFSIZE: usize = 64 * 1024; // 64 KiB
pub(crate) fn read_from_file_or_stdin<S: AsRef<str>>(
input_file: Option<S>,
bufsz: usize,
) -> AheadReader {
match input_file {
Some(f) => Box::new(File::open(f.as_ref()).unwrap()),
None => Box::new(io::stdin()),
Some(f) => AheadReader::from(
Box::new(BufReader::new(File::open(f.as_ref()).unwrap())),
bufsz,
),
None => AheadReader::from(Box::new(io::stdin().lock()), bufsz),
}
}