diff --git a/doc/tomb.1 b/doc/tomb.1 index 014c8d1..c7703ce 100644 --- a/doc/tomb.1 +++ b/doc/tomb.1 @@ -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" @@ -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 trusted keys in GPG. .B -.IP "--kdf \fI\fR" -Activate the KDF feature against dictionary attacks when creating a key: forces -a delay of \fI\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. +The required argument currently 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\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\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\fR" Select a different tool than sudo for privilege escalation. Alternatives supported so far are: pkexec, doas, sup, sud. For any diff --git a/extras/test/30_kdf-pbkdf2.sh b/extras/test/30_kdf-pbkdf2.sh new file mode 100644 index 0000000..f89cc5d --- /dev/null +++ b/extras/test/30_kdf-pbkdf2.sh @@ -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 diff --git a/extras/test/30_kdf.sh b/extras/test/30_kdf.sh deleted file mode 100644 index 3262862..0000000 --- a/extras/test/30_kdf.sh +++ /dev/null @@ -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 diff --git a/extras/test/31_kdf-argon2.sh b/extras/test/31_kdf-argon2.sh new file mode 100644 index 0000000..9aa7157 --- /dev/null +++ b/extras/test/31_kdf-argon2.sh @@ -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 diff --git a/extras/test/setup b/extras/test/setup index 91b5f5b..d939019 100755 --- a/extras/test/setup +++ b/extras/test/setup @@ -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 diff --git a/tomb b/tomb index 742ba37..5eb643d 100755 --- a/tomb +++ b/tomb @@ -66,6 +66,7 @@ typeset -i RECOLL=1 typeset -i QRENCODE=1 typeset -i LSOF=1 typeset -i ACL=1 +typeset -i ARGON2=1 # Default mount options typeset MOUNTOPTS="rw,noatime,nodev" @@ -749,8 +750,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, 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 @@ -1225,11 +1231,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) ;; *) @@ -1470,50 +1479,58 @@ gen_key() { fi header="" - [[ $KDF == 1 ]] && { + [[ $KDF == 1 ]] || [[ $ARGON2 == 1 ]] && { { 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 - 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 + # Two KDF are currently supported: + # * pbkdf2 (time restrictive) + # * argon2 (memory, parallelismn restrictive and through those time) - kdftype="`option_value --kdftype`" - kdftype=${kdftype:-pbkdf2} + # --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 parameter: what KDF + kdftype="`option_value --kdf`" 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" + _warning "only pbkdf2 and argon2 are valid arguments" ;; esac } @@ -1976,7 +1993,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 @@ -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:) 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: 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[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: " + 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[lock]="-ignore-swap k: -kdf: -kdftype: -kdfmem: o: -tomb-pwd: r: R: -filesystem: " - subcommands_opts[setkey]="k: -ignore-swap -kdf: -kdftype: -kdfmem: -tomb-old-pwd: -tomb-pwd: r: R: " + subcommands_opts[lock]="-ignore-swap k: o: -tomb-pwd: r: R: -filesystem: " + subcommands_opts[setkey]="k: -ignore-swap -tomb-old-pwd: -tomb-pwd: r: R: " 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[help]="" subcommands_opts[slam]=""