Merge pull request #527 from Narrat/optim/kdf

Improve KDF handling
This commit is contained in:
Jaromil 2024-12-31 00:13:08 +01:00 committed by GitHub
commit 212b0e38a2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 157 additions and 112 deletions

View File

@ -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 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 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 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 forging a key. Two KDF are currently supported: \fIargon2\fR and \fIpbkdf2\fR.
and installed on the system. \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 .B
.IP "lock" .IP "lock"
@ -283,24 +285,45 @@ 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 can be one or more GPG key ID, comma separated. All GPG keys must be
trusted keys in GPG. trusted keys in GPG.
.B .B
.IP "--kdf \fI<itertime>\fR" .IP "--kdf \fI[argon2 | pbkdf2]\fR"
Activate the KDF feature against dictionary attacks when creating a key: forces Enable the KDF feature against dictionary attacks when creating a key.
a delay of \fI<itertime>\fR times every time this key is used. The actual time The required argument currently allows to choose between \fIargon2\fR
to wait depends on the CPU speed (default) or the RAM size (argon2) of the or \fIpbkdf2\fR.
computer where the key is used. Using 5 or 10 is a sane amount for modern \fIargon2\fR is using a mix of RAM capacity, number of threads and
computers, the value is multiplied by 1 million. iterations to achieve a time cost.
.B \fIpbkdf2\fR is only about calculation speed to achieve a time cost.
.IP "--kdftype \fIargon2 | pbkdf2\fR" Due to a low memory footprint and no restrictions regarding threads, this
Adopt the \fIargon2\fR algorithm for KDF, stressing the RAM capacity rather time cost can be somewhat negated due to parallelization. Especially on
than the CPU speed of the computer decrypting the tomb. Requires the GPUs with their high number of cores.
\fIargon2\fR binary by P-H-C to be installed, as packaged by most distros. \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. Default is \fIpbkdf2\fR.
.B .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" .IP "--kdfmem \fI<memory>\fR"
In case of \fIargon2\fR KDF algorithm, this value specifies the size of RAM 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. 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). Default is 18 which is 250 MiB (2^18 = 262,144 kilobytes).
.B .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" .IP "--sudo \fI<executable>\fR"
Select a different tool than sudo for privilege escalation. Select a different tool than sudo for privilege escalation.
Alternatives supported so far are: pkexec, doas, sup, sud. For any Alternatives supported so far are: pkexec, doas, sup, sud. For any

View File

@ -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 pbkdf2 &&
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

View File

@ -1,58 +0,0 @@
#!/usr/bin/env zsh
export test_description="Testing tomb with KDF keys"
source ./setup
if test_have_prereq KDF; then
test_export "kdf"
test_expect_success 'Testing KDF: tomb creation' '
tt_dig -s 20 &&
tt_forge --tomb-pwd $DUMMYPASS --kdf 1 &&
print $DUMMYPASS \
| gpg --batch --passphrase-fd 0 --no-tty --no-options -d $tomb_key \
| xxd &&
tt_lock --tomb-pwd $DUMMYPASS
'
test_expect_success 'Testing 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 KDF: tomb open & close' '
tt_open --tomb-pwd $DUMMYPASS &&
tt_close
'
fi
# clean to avoid overwrite errors
# rm -f "$tomb_key" "$tomb"
if test_have_prereq ARGON2; then
test_export "argon2"
test_expect_success 'Testing KDF ARGON2: tomb creation' '
tt_dig -s 20 &&
tt_forge --tomb-pwd $DUMMYPASS --kdftype argon2 --kdfmem 18 --kdf 1 &&
print $DUMMYPASS \
| gpg --batch --passphrase-fd 0 --no-tty --no-options -d $tomb_key \
| xxd &&
tt_lock --tomb-pwd $DUMMYPASS
'
test_expect_success 'Testing KDF ARGON2: 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 KDF ARGON2: tomb open & close' '
tt_open --tomb-pwd $DUMMYPASS &&
tt_close
'
fi
test_done

View File

@ -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

View File

@ -50,6 +50,7 @@ MEDIA="/media"
command -v steghide > /dev/null && test_set_prereq STEGHIDE command -v steghide > /dev/null && test_set_prereq STEGHIDE
command -v e2fsck resize2fs > /dev/null && test_set_prereq RESIZER command -v e2fsck resize2fs > /dev/null && test_set_prereq RESIZER
command -v tomb-kdb-pbkdf2 > /dev/null && test_set_prereq KDF 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 qrencode > /dev/null && test_set_prereq QRENCODE
command -v lsof > /dev/null && test_set_prereq LSOF command -v lsof > /dev/null && test_set_prereq LSOF
command -v python3 > /dev/null && test_set_prereq PYTHON3 command -v python3 > /dev/null && test_set_prereq PYTHON3

95
tomb
View File

@ -66,6 +66,7 @@ typeset -i RECOLL=1
typeset -i QRENCODE=1 typeset -i QRENCODE=1
typeset -i LSOF=1 typeset -i LSOF=1
typeset -i ACL=1 typeset -i ACL=1
typeset -i ARGON2=1
# Default mount options # Default mount options
typeset MOUNTOPTS="rw,noatime,nodev" typeset MOUNTOPTS="rw,noatime,nodev"
@ -749,8 +750,13 @@ usage() {
_print " -R provide GnuPG hidden recipients (separated by comma)" _print " -R provide GnuPG hidden recipients (separated by comma)"
_print " --sudo super user exec alternative to sudo (doas or none)" _print " --sudo super user exec alternative to sudo (doas or none)"
[[ $KDF == 1 ]] && { [[ $KDF == 1 ]] || [[ $ARGON2 == 1 ]] && {
_print " --kdf forge keys armored against dictionary attacks" _print " --kdf forge keys armored against dictionary attacks (pbkdf2, 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 echo
@ -1225,11 +1231,14 @@ get_lukskey() {
kdf_salt="${firstline[(ws:_:)3]}" kdf_salt="${firstline[(ws:_:)3]}"
kdf_ic="${firstline[(ws:_:)4]}" kdf_ic="${firstline[(ws:_:)4]}"
kdf_mem="${firstline[(ws:_:)5]}" kdf_mem="${firstline[(ws:_:)5]}"
kdf_par="${firstline[(ws:_:)6]}"
# ToDo also parse kdf_len?
_message "Unlocking KDF key protection (::1 kdf::)" $kdf_hash _message "Unlocking KDF key protection (::1 kdf::)" $kdf_hash
_verbose "KDF salt: $kdf_salt" _verbose "KDF salt: $kdf_salt"
_verbose "KDF ic: $kdf_ic" _verbose "KDF ic: $kdf_ic"
_verbose "KDF mem: $kdf_mem" _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)
;; ;;
*) *)
@ -1470,50 +1479,58 @@ gen_key() {
fi fi
header="" header=""
[[ $KDF == 1 ]] && { [[ $KDF == 1 ]] || [[ $ARGON2 == 1 ]] && {
{ option_is_set --kdf } && { { option_is_set --kdf } && {
# KDF is a new key strengthening technique against brute forcing # KDF is a key strengthening technique against brute forcing
# see: https://github.com/dyne/Tomb/issues/82 # see: https://github.com/dyne/Tomb/issues/82
itertime="`option_value --kdf`" # Two KDF are currently supported:
# removing support of floating points because they can't be type checked well # * pbkdf2 (time restrictive)
# if [[ "$itertime" != <-> ]]; then # * argon2 (memory, parallelismn restrictive and through those time)
# 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`" # --kdfiter takes one integer value as parameter
kdftype=${kdftype:-pbkdf2} # 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 parameter: what KDF
kdftype="`option_value --kdf`"
case ${kdftype} in case ${kdftype} in
pbkdf2) pbkdf2)
local -i microseconds local -i microseconds
microseconds=$(( itertime * 1000000 )) microseconds=$(( itertime * 1000000 ))
_success "Using KDF, iteration time: ::1 microseconds::" $microseconds _success "Using pbkdf2 as KDF"
_message "generating salt" _message "iteration time: ::1 microseconds::" $microseconds
pbkdf2_salt=`tomb-kdb-pbkdf2-gensalt`
_message "calculating iterations"
pbkdf2_iter=`tomb-kdb-pbkdf2-getiter $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!?) # We use a length of 64bytes = 512bits (more than needed!?)
tombpass=`tomb-kdb-pbkdf2 $pbkdf2_salt $pbkdf2_iter 64 <<<"${tombpass}"` tombpass=`tomb-kdb-pbkdf2 $kdfsalt $pbkdf2_iter 64 <<<"${tombpass}"`
header="_KDF_pbkdf2sha1_${kdfsalt}_${pbkdf2_iter}_64\n"
header="_KDF_pbkdf2sha1_${pbkdf2_salt}_${pbkdf2_iter}_64\n"
;; ;;
argon2) argon2)
_success "Using KDF Argon2" _success "Using Argon2 as KDF"
_message "iterations: ::1 kdfiterations::" $itertime
kdfmem="`option_value --kdfmem`" kdfmem="`option_value --kdfmem`"
kdfmem=${kdfmem:-18} kdfmem=${kdfmem:-18}
_message "memory used: 2^::1 kdfmemory::" $kdfmem _message "memory used: 2^::1 kdfmemory::" $kdfmem
itertime="`option_value --kdf`" kdfpar="`option_value --kdfpar`"
itertime=${itertime:-3} kdfpar=${kdfpar:-1}
kdfsalt=`tomb-kdb-pbkdf2-gensalt` _message "parallelismn: ::1 kdfparallel::" $kdfpar
_message "kdf salt: ::1 kdfsalt::" $kdfsalt tombpass=`argon2 $kdfsalt -m $kdfmem -t $itertime -p $kdfpar -l 64 -r <<<"${tombpass}"`
_message "kdf iterations: ::1 kdfiterations::" $itertime header="_KDF_argon2_${kdfsalt}_${itertime}_${kdfmem}_${kdfpar}_64\n"
tombpass=`argon2 $kdfsalt -m $kdfmem -t $itertime -l 64 -r <<<"${tombpass}"` ;;
header="_KDF_argon2_${kdfsalt}_${itertime}_${kdfmem}_64\n" *)
_warning "unrecognized KDF ::1::" $kdftype
_warning "key won\'t be protected via a KDF implementation"
_warning "only pbkdf2 and argon2 are valid arguments"
;; ;;
esac esac
} }
@ -1976,7 +1993,7 @@ forge_key() {
$destkey $algo $destkey $algo
[[ $KDF == 1 ]] && { ! option_is_set -g } && { [[ $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 TOMBKEYFILE="$destkey" # Set global variable
@ -3129,19 +3146,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:) 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]="" subcommands_opts[__default]=""
# -o in open and mount is used to pass alternate mount options # -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: p -preserve-ownership=p" subcommands_opts[open]="n -nohook=n k: o: -ignore-swap -tomb-pwd: r: R: p -preserve-ownership=p"
subcommands_opts[mount]=${subcommands_opts[open]} subcommands_opts[mount]=${subcommands_opts[open]}
subcommands_opts[create]="" # deprecated, will issue warning subcommands_opts[create]="" # deprecated, will issue warning
# -o in forge and lock is used to pass an alternate cipher. # -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: " subcommands_opts[forge]="-ignore-swap k: -kdf: -kdfiter: -kdfmem: -kdfpar: o: -tomb-pwd: -use-random r: R: "
subcommands_opts[dig]="-ignore-swap s: -size=s " subcommands_opts[dig]="-ignore-swap s: -size=s "
subcommands_opts[lock]="-ignore-swap k: -kdf: -kdftype: -kdfmem: o: -tomb-pwd: r: R: -filesystem: " subcommands_opts[lock]="-ignore-swap k: o: -tomb-pwd: r: R: -filesystem: "
subcommands_opts[setkey]="k: -ignore-swap -kdf: -kdftype: -kdfmem: -tomb-old-pwd: -tomb-pwd: r: R: " subcommands_opts[setkey]="k: -ignore-swap -tomb-old-pwd: -tomb-pwd: r: R: "
subcommands_opts[engrave]="k: " subcommands_opts[engrave]="k: "
subcommands_opts[passwd]="k: -ignore-swap -kdf: -kdftype: -kdfmem: -tomb-old-pwd: -tomb-pwd: r: R: " subcommands_opts[passwd]="k: -ignore-swap -kdf: -kdfiter: -kdfmem: -kdfpar: -tomb-old-pwd: -tomb-pwd: r: R: "
subcommands_opts[close]="" subcommands_opts[close]=""
subcommands_opts[help]="" subcommands_opts[help]=""
subcommands_opts[slam]="" subcommands_opts[slam]=""