Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve KDF handling #527

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 35 additions & 13 deletions doc/tomb.1
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,10 @@ more recipient GPG ids can be indicated (comma separated). The default cipher
to protect the key is AES256, a custom one can be specified using the \fI-o\fR
option, for a list of supported ciphers use \fI-v\fR. For additional protection
against dictionary attacks on keys, the \fI--kdf\fR option can be used when
forging a key, making sure that the binaries in \fIextras/kdf\fR were compiled
and installed on the system.
forging a key. Two KDF are currently supported: \fIargon2\fR and \fIpbkdf2\fR.
\fIpbkdf2\fR is available from \fIextras/kdf\fR and needs to be compiled and
installed on the system. \fIargon2\fR is generally available from distribution
repositories.

.B
.IP "lock"
Expand Down Expand Up @@ -283,24 +285,44 @@ Provide a new set of recipient(s) to encrypt a tomb key. \fIgpg_ids\fR
can be one or more GPG key ID, comma separated. All GPG keys must be
trusted keys in GPG.
.B
.IP "--kdf \fI<itertime>\fR"
Activate the KDF feature against dictionary attacks when creating a key: forces
a delay of \fI<itertime>\fR times every time this key is used. The actual time
to wait depends on the CPU speed (default) or the RAM size (argon2) of the
computer where the key is used. Using 5 or 10 is a sane amount for modern
computers, the value is multiplied by 1 million.
.B
.IP "--kdftype \fIargon2 | pbkdf2\fR"
Adopt the \fIargon2\fR algorithm for KDF, stressing the RAM capacity rather
than the CPU speed of the computer decrypting the tomb. Requires the
\fIargon2\fR binary by P-H-C to be installed, as packaged by most distros.
.IP "--kdf \fI[argon2 | pbkdf2]\fR"
Enable the KDF feature against dictionary attacks when creating a key.
An optional argument allows to choose between \fIargon2\fR or \fIpbkdf2\fR.
\fIargon2\fR is using a mix of RAM capacity, number of threads and
iterations to achieve a time cost.
\fIpbkdf2\fR is only about calculation speed to achieve a time cost.
Due to a low memory footprint and no restrictions regarding threads, this
time cost can be somewhat negated due to parallelization. Especially on
GPUs with their high number of cores.
\fIargon2\fR requires the respective binary by P-H-C to be installed, as
packaged by most distros. \fIpbkdf2\fR is available from the \fItomb\fR
sources and is a custom implementation of the algorithm.
Default is \fIpbkdf2\fR.
.B
.IP "--kdfiter \fI<itertime>\fR"
Available for \fIargon2\fR and \fIpbkdf2\fR. In general this controls how
often the algorithm will be run. In case of \fIpbkdf2\fR the argument will
be interpret as an interval in seconds. The actual number of iterations to
achieve this delay will be calculated with \fItomb-kdb-pbkdf2-getiter\fR,
which needs to available (normally installed alongside tomb's pbkdf2 tools).
Reason being that the actual time to wait depends on the CPU speed.
OWASP recommendations from 2023 suggest a minimal iteration count of 600000
for \fIpbkdf2\fR, which should be achieved with the current default value.
Default is 3 (based on the \fIargon2\fR default).
.B
.IP "--kdfmem \fI<memory>\fR"
In case of \fIargon2\fR KDF algorithm, this value specifies the size of RAM
used: it consists of a number which is the elevated power of two in kilobytes.
Default is 18 which is 250 MiB (2^18 = 262,144 kilobytes).
.B
.IP "--kdfpar \fI<# of threads>\fR"
In case of \fIargon2\fR KDF algorithm, this value specifies the number of
threads that should be used. This helps to remedy the effects of an increased
time cost for your system whereas setups of ASICs or GPUs don't profit. Only
increase if memory or iteration got increased that much, that key decryption
takes massively longer on regular systems.
Default is 1 thread (based on the \fIargon2\fR default).
.B
.IP "--sudo \fI<executable>\fR"
Select a different tool than sudo for privilege escalation.
Alternatives supported so far are: pkexec, doas, sup, sud. For any
Expand Down
31 changes: 31 additions & 0 deletions extras/test/30_kdf-pbkdf2.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env zsh

export test_description="Testing tomb with pbkdf2 KDF key"

source ./setup

if test_have_prereq KDF; then
test_export "kdf"
test_expect_success 'Testing pbkdf2 KDF: tomb creation' '
tt_dig -s 20 &&
tt_forge --tomb-pwd $DUMMYPASS --kdf &&
print $DUMMYPASS \
| gpg --batch --passphrase-fd 0 --no-tty --no-options -d $tomb_key \
| xxd &&
tt_lock --tomb-pwd $DUMMYPASS
'

test_expect_success 'Testing pbkdf2 KDF: tomb passwd' '
tt passwd -k $tomb_key \
--unsafe --tomb-old-pwd $DUMMYPASS --tomb-pwd $DUMMYPASSNEW &&
tt passwd -k $tomb_key \
--unsafe --tomb-old-pwd $DUMMYPASSNEW --tomb-pwd $DUMMYPASS
'

test_expect_success 'Testing pbkdf2 KDF: tomb open & close' '
tt_open --tomb-pwd $DUMMYPASS &&
tt_close
'
fi

test_done
58 changes: 0 additions & 58 deletions extras/test/30_kdf.sh

This file was deleted.

31 changes: 31 additions & 0 deletions extras/test/31_kdf-argon2.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env zsh

export test_description="Testing tomb with argon2 KDF key"

source ./setup

if test_have_prereq ARGON; then
test_export "argon"
test_expect_success 'Testing argon2 KDF: tomb creation' '
tt_dig -s 20 &&
tt_forge --tomb-pwd $DUMMYPASS --kdf argon2 &&
print $DUMMYPASS \
| gpg --batch --passphrase-fd 0 --no-tty --no-options -d $tomb_key \
| xxd &&
tt_lock --tomb-pwd $DUMMYPASS
'

test_expect_success 'Testing argon2 KDF: tomb passwd' '
tt passwd -k $tomb_key --kdf argon2 \
--unsafe --tomb-old-pwd $DUMMYPASS --tomb-pwd $DUMMYPASSNEW &&
tt passwd -k $tomb_key --kdf argon2 \
--unsafe --tomb-old-pwd $DUMMYPASSNEW --tomb-pwd $DUMMYPASS
'

test_expect_success 'Testing argon2 KDF: tomb open & close' '
tt_open --tomb-pwd $DUMMYPASS &&
tt_close
'
fi

test_done
1 change: 1 addition & 0 deletions extras/test/setup
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ MEDIA="/media"
command -v steghide > /dev/null && test_set_prereq STEGHIDE
command -v e2fsck resize2fs > /dev/null && test_set_prereq RESIZER
command -v tomb-kdb-pbkdf2 > /dev/null && test_set_prereq KDF
command -v argon2 > /dev/null && test_set_prereq ARGON
command -v qrencode > /dev/null && test_set_prereq QRENCODE
command -v lsof > /dev/null && test_set_prereq LSOF
command -v python3 > /dev/null && test_set_prereq PYTHON3
Expand Down
100 changes: 59 additions & 41 deletions tomb
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ typeset -i DECLOAKIFY=1
typeset -i RESIZER=1
typeset -i RECOLL=1
typeset -i QRENCODE=1
typeset -i ARGON2=1

# Default mount options
typeset MOUNTOPTS="rw,noatime,nodev"
Expand Down Expand Up @@ -747,8 +748,13 @@ usage() {
_print " -R provide GnuPG hidden recipients (separated by comma)"
_print " --sudo super user exec alternative to sudo (doas or none)"

[[ $KDF == 1 ]] && {
_print " --kdf forge keys armored against dictionary attacks"
[[ $KDF == 1 ]] || [[ $ARGON2 == 1 ]] && {
_print " --kdf forge keys armored against dictionary attacks (pbkdf2 (default), argon2)"
_print " --kdfiter Number of iterations (meaning depending on KDF algorithm) (pbkdf2, argon2)"
}
[[ $ARGON2 == 1 ]] && {
_print " --kdfmem memory to be used (argon2)"
_print " --kdfpar number of threads (argon2)"
}

echo
Expand Down Expand Up @@ -1221,11 +1227,14 @@ get_lukskey() {
kdf_salt="${firstline[(ws:_:)3]}"
kdf_ic="${firstline[(ws:_:)4]}"
kdf_mem="${firstline[(ws:_:)5]}"
kdf_par="${firstline[(ws:_:)6]}"
# ToDo also parse kdf_len?
_message "Unlocking KDF key protection (::1 kdf::)" $kdf_hash
_verbose "KDF salt: $kdf_salt"
_verbose "KDF ic: $kdf_ic"
_verbose "KDF mem: $kdf_mem"
_password=$(argon2 $kdf_salt -m $kdf_mem -t $kdf_ic -l 64 -r 2>/dev/null <<<$_password)
_verbose "KDF # threads: $kdf_par"
_password=$(argon2 $kdf_salt -m $kdf_mem -t $kdf_ic -p $kdf_par -l 64 -r 2>/dev/null <<<$_password)
;;

*)
Expand Down Expand Up @@ -1468,50 +1477,59 @@ gen_key() {
fi

header=""
[[ $KDF == 1 ]] && {
[[ $KDF == 1 ]] || [[ $ARGON2 == 1 ]] && {
{ option_is_set --kdf } && {
# KDF is a new key strenghtening technique against brute forcing
# KDF is a key strengthening technique against brute forcing
# see: https://github.com/dyne/Tomb/issues/82
itertime="`option_value --kdf`"
# removing support of floating points because they can't be type checked well
# if [[ "$itertime" != <-> ]]; then
# unset tombpass
# unset tombpasstmp
# _warning "Wrong argument for --kdf: must be an integer number (iteration seconds)."
# _failure "Depending on the speed of machines using this tomb, use 1 to 10, or more"
# return 1
# fi
# # --kdf takes one parameter: iter time (on present machine) in seconds

kdftype="`option_value --kdftype`"
# Two KDF are currently supported:
# * pbkdf2 (time restrictive)
# * argon2 (memory, parallelismn restrictive and through those time)

# --kdfiter takes one integer value as parameter
# argon2: # of iterations (default of 3);
# pbkdf2: calculates # of iterations to reach this as time cost in seconds
itertime="`option_value --kdfiter`"
itertime=${itertime:-3}

# Generating salt (either via tomb-kdb-pbkdf2 or a shell fallback)
if $(command -v tomb-kdb-pbkdf2-gensalt 1>/dev/null 2>/dev/null); then
kdfsalt=`tomb-kdb-pbkdf2-gensalt`
else
kdfsalt=$(LC_CTYPE=C tr -cd 'a-f0-9' < /dev/random | head -c 64)
fi
_message "kdf salt: ::1 kdfsalt::" $kdfsalt

# --kdf takes one optional parameter: what KDF
# (pbkdf2 being the default)
kdftype="`option_value --kdf`"
kdftype=${kdftype:-pbkdf2}
case ${kdftype} in
pbkdf2)
pbkdf2)
local -i microseconds
microseconds=$(( itertime * 1000000 ))
_success "Using KDF, iteration time: ::1 microseconds::" $microseconds
_message "generating salt"
pbkdf2_salt=`tomb-kdb-pbkdf2-gensalt`
_message "calculating iterations"
_success "Using pbkdf2 as KDF"
_message "iteration time: ::1 microseconds::" $microseconds
pbkdf2_iter=`tomb-kdb-pbkdf2-getiter $microseconds`
_message "encoding the password"
_message "iterations: ::1 pbkdf2_iter::" $pbkdf2_iter
# We use a length of 64bytes = 512bits (more than needed!?)
tombpass=`tomb-kdb-pbkdf2 $pbkdf2_salt $pbkdf2_iter 64 <<<"${tombpass}"`

header="_KDF_pbkdf2sha1_${pbkdf2_salt}_${pbkdf2_iter}_64\n"
tombpass=`tomb-kdb-pbkdf2 $kdfsalt $pbkdf2_iter 64 <<<"${tombpass}"`
header="_KDF_pbkdf2sha1_${kdfsalt}_${pbkdf2_iter}_64\n"
;;
argon2)
_success "Using KDF Argon2"
argon2)
_success "Using Argon2 as KDF"
_message "iterations: ::1 kdfiterations::" $itertime
kdfmem="`option_value --kdfmem`"
kdfmem=${kdfmem:-18}
_message "memory used: 2^::1 kdfmemory::" $kdfmem
itertime="`option_value --kdf`"
itertime=${itertime:-3}
kdfsalt=`tomb-kdb-pbkdf2-gensalt`
_message "kdf salt: ::1 kdfsalt::" $kdfsalt
_message "kdf iterations: ::1 kdfiterations::" $itertime
tombpass=`argon2 $kdfsalt -m $kdfmem -t $itertime -l 64 -r <<<"${tombpass}"`
header="_KDF_argon2_${kdfsalt}_${itertime}_${kdfmem}_64\n"
kdfpar="`option_value --kdfpar`"
kdfpar=${kdfpar:-1}
_message "parallelismn: ::1 kdfparallel::" $kdfpar
tombpass=`argon2 $kdfsalt -m $kdfmem -t $itertime -p $kdfpar -l 64 -r <<<"${tombpass}"`
header="_KDF_argon2_${kdfsalt}_${itertime}_${kdfmem}_${kdfpar}_64\n"
;;
*)
_warning "unrecognized KDF ::1::" $kdftype
_warning "key won\'t be protected via a KDF implementation"
;;
esac
}
Expand Down Expand Up @@ -1974,7 +1992,7 @@ forge_key() {
$destkey $algo

[[ $KDF == 1 ]] && { ! option_is_set -g } && {
_message "Using KDF to protect the key password (`option_value --kdf` rounds)"
_message "Using KDF to protect the key password"
}

TOMBKEYFILE="$destkey" # Set global variable
Expand Down Expand Up @@ -3110,19 +3128,19 @@ main() {
main_opts=(q -quiet=q D -debug=D h -help=h v -version=v f -force=f -tmp: U: G: T: -no-color -unsafe g -gpgkey=g -sudo:)
subcommands_opts[__default]=""
# -o in open and mount is used to pass alternate mount options
subcommands_opts[open]="n -nohook=n k: -kdf: -kdftype: -kdfmem: o: -ignore-swap -tomb-pwd: r: R: -sphx-host: -sphx-user: p -preserve-ownership=p"
subcommands_opts[open]="n -nohook=n k: o: -ignore-swap -tomb-pwd: r: R: -sphx-host: -sphx-user: p -preserve-ownership=p"
subcommands_opts[mount]=${subcommands_opts[open]}

subcommands_opts[create]="" # deprecated, will issue warning

# -o in forge and lock is used to pass an alternate cipher.
subcommands_opts[forge]="-ignore-swap k: -kdf: -kdftype: -kdfmem: o: -tomb-pwd: -use-random r: R: -sphx-host: -sphx-user: "
subcommands_opts[forge]="-ignore-swap k: -kdf:: -kdfiter: -kdfmem: -kdfpar: o: -tomb-pwd: -use-random r: R: -sphx-host: -sphx-user: "
subcommands_opts[dig]="-ignore-swap s: -size=s "
subcommands_opts[lock]="-ignore-swap k: -kdf: -kdftype: -kdfmem: o: -tomb-pwd: r: R: -sphx-host: -sphx-user: -filesystem: "
subcommands_opts[setkey]="k: -ignore-swap -kdf: -kdftype: -kdfmem: -tomb-old-pwd: -tomb-pwd: r: R: -sphx-host: -sphx-user: "
subcommands_opts[lock]="-ignore-swap k: o: -tomb-pwd: r: R: -sphx-host: -sphx-user: -filesystem: "
subcommands_opts[setkey]="k: -ignore-swap -tomb-old-pwd: -tomb-pwd: r: R: -sphx-host: -sphx-user: "
subcommands_opts[engrave]="k: "

subcommands_opts[passwd]="k: -ignore-swap -kdf: -kdftype: -kdfmem: -tomb-old-pwd: -tomb-pwd: r: R: -sphx-host: -sphx-user: "
subcommands_opts[passwd]="k: -ignore-swap -kdf:: -kdfiter: -kdfmem: -kdfpar: -tomb-old-pwd: -tomb-pwd: r: R: -sphx-host: -sphx-user: "
subcommands_opts[close]=""
subcommands_opts[help]=""
subcommands_opts[slam]=""
Expand Down
Loading