Compare commits

...

3 Commits

Author SHA1 Message Date
Matéo Duparc 2baece0122
Man page 2021-12-01 14:23:49 +01:00
Matéo Duparc dbd4563e77
Shell completions 2021-11-30 16:25:47 +01:00
Matéo Duparc 89be84860d
Better error messages 2021-11-30 13:29:21 +01:00
13 changed files with 225 additions and 14 deletions

2
Cargo.lock generated
View File

@ -210,7 +210,7 @@ dependencies = [
[[package]]
name = "doby"
version = "0.2.0"
version = "0.3.0"
dependencies = [
"aes",
"argon2",

View File

@ -1,10 +1,10 @@
[package]
name = "doby"
version = "0.2.0"
edition = "2018"
version = "0.3.0"
edition = "2021"
authors = ["Hardcore Sushi <hardcore.sushi@disroot.org>"]
license = "GPL-3.0-or-later"
description = "Secure symmetric encryption from the command line"
description = "Simple, secure and lightweight symmetric encryption from the command line"
readme = "README.md"
[profile.release]

View File

@ -2,7 +2,7 @@
Secure symmetric encryption from the command line.
doby started as a fork of [aef](https://github.com/wyhaya/aef) by [wyhaya](https://github.com/wyhaya) with the goal of becoming the fastest and most lightweight CLI utility for symmetric encryption. It aims to replace the old [ccrypt](http://ccrypt.sourceforge.net) tool which doesn't seem to be very secure.
doby started as a fork of [aef](https://github.com/wyhaya/aef) by [wyhaya](https://github.com/wyhaya) with the goal of becoming a simple, fast and lightweight CLI utility for symmetric encryption. It aims to be an alternative to the old [ccrypt](http://ccrypt.sourceforge.net) tool by using modern cryptography and authenticated encryption.
# Features
@ -52,7 +52,7 @@ doby --password "first password" my-super-secret-database.db | doby -f - double-
Increase password brute-force resistance:
```bash
echo "you-will-never-break-this" | doby --memory-cost 524288 --parallelism 16 --time-cost 40 > my-super-secret-password.doby
echo "you-will-never-break-this" | doby --memory-cost 524288 --parallelism 16 --time-cost 40 > my-super-secret-data.doby
```
## Full Options
@ -120,6 +120,8 @@ cargo build --release --bin doby #outputs to ./target/release/doby
# Cryptographic details
The following explanations are illustrated with pseudo rust code to simplify understanding. If you want to see how it's exactly implemented in doby, you can always check the source code.
### Encryption
doby first derives your password with Argon2 (version 19) in Argon2id mode with a 64 bytes long random salt. A `master_key` of 32 bytes is thus generated.

63
completions/bash Normal file
View File

@ -0,0 +1,63 @@
#/usr/bin/env bash
_remove_opts() {
local opt new_opts
for opt in ${available_opts}; do
if [[ $opt != $1 && $opt != $2 ]]; then
new_opts+="$opt "
fi
done
available_opts=$new_opts
}
_doby_completion() {
local cur="${COMP_WORDS[COMP_CWORD]}"
local opts="-f --force-encrypt -i --interactive -h --help -V --version --password -t --time-cost -m --memory-cost -p --parallelism -b --block-size -c --cipher"
if [[ ${cur} == -* ]]; then
local i available_opts=$opts
for i in ${COMP_WORDS[@]}; do
if [[ ${opts[*]} =~ $i ]]; then
case $i in
"-f"|"--force-encrypt")
_remove_opts "-f" "--force-encrypt"
;;
"-i"|"--interactive")
_remove_opts "-i" "--interactive"
;;
"-h"|"--help")
_remove_opts "-h" "--help"
;;
"-V"|"--version")
_remove_opts "-V" "--version"
;;
"--password")
_remove_opts "--password"
;;
"-t"|"--time-cost")
_remove_opts "-t" "--time-cost"
;;
"-m"|"--memory-cost")
_remove_opts "-m" "--memory-cost"
;;
"-p"|"--parallelism")
_remove_opts "-p" "--parallelism"
;;
"-b"|"--block-size")
_remove_opts "-b" "--block-size"
;;
"-c"|"--cipher")
_remove_opts "-c" "--cipher"
;;
esac
fi
done
COMPREPLY=($(compgen -W "${available_opts}" -- "${cur}"))
else
local prev="${COMP_WORDS[COMP_CWORD-1]}"
if [[ ${prev} == "-c" || ${prev} == "--cipher" ]]; then
COMPREPLY=($(compgen -W "aes xchacha20" -- "${cur}"))
fi
fi
}
complete -F _doby_completion -o bashdefault -o default doby

19
completions/zsh Normal file
View File

@ -0,0 +1,19 @@
#compdef doby
function _doby {
_arguments \
'(-f --force-encrypt)'{-f,--force-encrypt}'[Encrypt even if doby format is recognized]' \
'(-i --interactive)'{-i,--interactive}'[Prompt before overwriting files]' \
'(: * -)'{-h,--help}'[Prints help information]' \
'(: * -)'{-V,--version}'[Prints version information]' \
'--password=[Password used to derive encryption keys]' \
'(-t --time-cost)'{-t,--time-cost}'[Argon2 time cost]' \
'(-m --memory-cost)'{-m,--memory-cost}'[Argon2 memory cost (in kilobytes)]' \
'(-p --parallelism)'{-p,--parallelism}'[Argon2 parallelism cost]' \
'(-b --block-size)'{-b,--block-size}'[Size of the I/O buffer (in bytes)]' \
'(-c --cipher)'{-c,--cipher}'[Encryption cipher to use]: :(aes xchacha20)' \
':::_files' \
':::_files' \
}
_doby "$@"

1
man/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/*.gz

18
man/compile.sh Executable file
View File

@ -0,0 +1,18 @@
#!/bin/bash
cargo_toml="../Cargo.toml"
source_md="source.md"
if [ ! -f $cargo_toml ]; then
echo "Error: $cargo_toml not found." >&2;
exit 1;
elif [ ! -f $source_md ]; then
echo "Error: $source_md not found." >&2;
exit 1;
fi
version=$(grep "^version = " $cargo_toml | cut -d "\"" -f 2)
date=$(date +"%B %Y")
pandoc $source_md -s -t man | sed \
"s/^\.TH.*$/\.TH \"DOBY\" \"1\" \"$date\" \"doby v$version\" \"doby v$version\"/; \
s/^\.hy/\.ad l/" | gzip - > doby.1.gz

88
man/source.md Normal file
View File

@ -0,0 +1,88 @@
% DOBY(1)
# NAME
doby - Simple, secure and lightweight symmetric encryption from the command line
# SYNOPSIS
doby [**-fi**] [**\--password** password] [**-t** time_cost] [**-m** memory_cost] [**-p** parallelism] [**-b** block_size] [**-c**] {aes | xchacha20} [INPUT] [OUTPUT]
doby [**-h** | **\--help**]
doby [**-V** | **\--version**]
# DESCRIPTION
doby aims to be a small, fast and user-friendly command line tool for symmectric encryption of single files. It uses modern cryptography and (obviously) it's built in rust.
doby can operate with files larger than memory but also from stdout/stdin. In addition to encrypt files, doby also use HMAC cryptography to authenticate the data. This means that encrypted files can't be tampered. Encryptions keys are derived from the user password using Argon2, an expensive KDF function that slows down a lot brute force attacks. You can find more details about cryptography on the doby's repository: https://forge.chapril.org/hardcoresushi/doby#cryptographic-details
doby will add a header at the beginning of the encrypted files so that it can know whether it is encrypted or not. That's why you don't need to specify which operation should be performed. doby will detect this automatically.
# OPTIONS
**-h**, **\--help**
: Print help.
**-V**, **\--version**
: Print doby version.
**-f**, **\--force-encrypt**
: Perform encryption even if doby format is recognized in the input file.
**-i**, **\--interactive**
: Prompt before overwriting the output file if it already exists.
**\--password** *password*
: Specify the password which will be used to derive encryption keys. If omitted, the password will be prompted in the terminal.
**-t**, **\--time-cost** *iterations*
: Argon2 time cost used to derive the master key. Default: 10
**-m**, **\--memory-cost** *memory size*
: Argon2 memory cost used to derive the master key (in kilobytes). Default: 4096 KB
**-p,** **\--parallelism** *threads*
: Argon2 parallelism cost used to derive the master key. Default: 4
**-b,** **\--block-size** *blocksize*
: Size of the buffer used when reading the file (in bytes). Default: 65536 B
**-c,** **\--cipher** *cipher*
: Encryption cipher to use. Either "aes" or "xchacha20". If not specified, AES will be used if your CPU supports AES native instructions, XChaCha20 otherwise. Ignored when performing decryption.
**INPUT**
: The file doby will read as input. If it's omitted or set to "-", doby will read from stdin.
**OUTPUT**
: The file doby will write to. If it's omitted or set to "-", doby will write to stdout.
# EXAMPLES
doby my-super-secret-source-code.rs encrypted.doby
doby \--block-size 4096 encrypted.doby decrypted.rs
cat my-super-secret-music.flac | doby \--cipher xchacha20 > encrypted.doby
doby \--password "rockyou" encrypted.doby > decrypted.flac
cat my-super-secret-logs-file.log | doby \--interactive - logs.doby
echo "you-will-never-break-this" | doby \--memory-cost 524288 \--parallelism 16 \--time-cost 40 > my-super-secret-data.doby
# EXIT STATUS
**0**
: Success
**1**
: Error
# REPORTING BUGS
You can open an issues on Gitea (https://forge.chapril.org/hardcoresushi/doby) or on GitHub (https://github.com/hardcore-sushi/doby) if you find an issue or if you have any questions/suggestions.
If you prefer, you can also email me at hardcore.sushi@disroot.org. My PGP key is available on keyservers (fingerprint: 0x007F84120107191E).
# AUTHOR
Hardcore Sushi <hardcore.sushi@disroot.org>
# COPYRIGHT
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>. This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law.
# SEE ALSO
**ccrypt**(1), **age**(1), **gocryptfs**(1), **cryfs**(1)

17
src/bin/compgen.rs Normal file
View File

@ -0,0 +1,17 @@
use std::{env, io};
use clap::Shell;
use doby::cli;
fn main() {
let mut args = env::args().skip(1);
if let Some(shell) = args.next() {
if let Ok(shell) = shell.parse() {
cli::app().gen_completions_to("doby", shell, &mut io::stdout());
} else {
eprintln!("error: invalid shell: {}", shell);
eprintln!("shell variants: {:?}", Shell::variants());
}
} else {
eprintln!("usage: compgen <shell>");
}
}

View File

@ -18,7 +18,7 @@ fn main() -> io::Result<()> {
None => eprintln!("Invalid parameters")
}
} else {
eprintln!("Doby format not recognized.");
eprintln!("doby format not recognized.");
}
Ok(())
}

View File

@ -31,8 +31,8 @@ impl From<CliArgs> for ParseResult {
}
}
pub fn parse() -> Option<ParseResult> {
let app = App::new(crate_name!())
pub fn app<'a>() -> App<'a, 'a> {
App::new(crate_name!())
.version(crate_version!())
.setting(AppSettings::ColoredHelp)
.about("Secure symmetric encryption from the command line.")
@ -42,7 +42,7 @@ pub fn parse() -> Option<ParseResult> {
Arg::with_name("1_force_encrypt")
.short("f")
.long("force-encrypt")
.help(&format!("Encrypt even if {} format is recognized", crate_name!()))
.help(concat!("Encrypt even if ", crate_name!(), " format is recognized"))
)
.arg(
Arg::with_name("2_interactive")
@ -97,7 +97,10 @@ pub fn parse() -> Option<ParseResult> {
.possible_values(&["aes", "xchacha20"])
.case_insensitive(true)
)
.get_matches();
}
pub fn parse() -> Option<ParseResult> {
let app = app().get_matches();
let params = {
let t_cost = number(app.value_of("2_t_cost").unwrap())?;
@ -181,7 +184,7 @@ fn number<T: FromStr>(val: &str) -> Option<T> {
match val.parse::<T>() {
Ok(n) => Some(n),
Err(_) => {
eprintln!("Cannot parse '{}' to '{}'", val, std::any::type_name::<T>());
eprintln!("Error: '{}' is not a number", val);
None
}
}

View File

@ -21,7 +21,7 @@ impl WrappedPassword {
} else {
password.zeroize();
password_confirm.zeroize();
eprintln!("Passwords don't match");
eprintln!("Error: passwords don't match");
None
}
} else {

View File

@ -40,7 +40,7 @@ fn run() -> bool {
}
}
} else {
eprintln!("Invalid parameters")
eprintln!("Error: invalid encryption parameters")
}
}
Err(e) => eprintln!("I/O error while reading headers: {}", e)