Skip to content

Commit 212b0e3

Browse files
authored
Merge pull request #527 from Narrat/optim/kdf
Improve KDF handling
2 parents 9905840 + 6c034a4 commit 212b0e3

File tree

6 files changed

+158
-113
lines changed

6 files changed

+158
-113
lines changed

doc/tomb.1

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,10 @@ more recipient GPG ids can be indicated (comma separated). The default cipher
4949
to protect the key is AES256, a custom one can be specified using the \fI-o\fR
5050
option, for a list of supported ciphers use \fI-v\fR. For additional protection
5151
against dictionary attacks on keys, the \fI--kdf\fR option can be used when
52-
forging a key, making sure that the binaries in \fIextras/kdf\fR were compiled
53-
and installed on the system.
52+
forging a key. Two KDF are currently supported: \fIargon2\fR and \fIpbkdf2\fR.
53+
\fIpbkdf2\fR is available from \fIextras/kdf\fR and needs to be compiled and
54+
installed on the system. \fIargon2\fR is generally available from distribution
55+
repositories.
5456

5557
.B
5658
.IP "lock"
@@ -283,24 +285,45 @@ Provide a new set of recipient(s) to encrypt a tomb key. \fIgpg_ids\fR
283285
can be one or more GPG key ID, comma separated. All GPG keys must be
284286
trusted keys in GPG.
285287
.B
286-
.IP "--kdf \fI<itertime>\fR"
287-
Activate the KDF feature against dictionary attacks when creating a key: forces
288-
a delay of \fI<itertime>\fR times every time this key is used. The actual time
289-
to wait depends on the CPU speed (default) or the RAM size (argon2) of the
290-
computer where the key is used. Using 5 or 10 is a sane amount for modern
291-
computers, the value is multiplied by 1 million.
292-
.B
293-
.IP "--kdftype \fIargon2 | pbkdf2\fR"
294-
Adopt the \fIargon2\fR algorithm for KDF, stressing the RAM capacity rather
295-
than the CPU speed of the computer decrypting the tomb. Requires the
296-
\fIargon2\fR binary by P-H-C to be installed, as packaged by most distros.
288+
.IP "--kdf \fI[argon2 | pbkdf2]\fR"
289+
Enable the KDF feature against dictionary attacks when creating a key.
290+
The required argument currently allows to choose between \fIargon2\fR
291+
or \fIpbkdf2\fR.
292+
\fIargon2\fR is using a mix of RAM capacity, number of threads and
293+
iterations to achieve a time cost.
294+
\fIpbkdf2\fR is only about calculation speed to achieve a time cost.
295+
Due to a low memory footprint and no restrictions regarding threads, this
296+
time cost can be somewhat negated due to parallelization. Especially on
297+
GPUs with their high number of cores.
298+
\fIargon2\fR requires the respective binary by P-H-C to be installed, as
299+
packaged by most distros. \fIpbkdf2\fR is available from the \fItomb\fR
300+
sources and is a custom implementation of the algorithm.
297301
Default is \fIpbkdf2\fR.
298302
.B
303+
.IP "--kdfiter \fI<itertime>\fR"
304+
Available for \fIargon2\fR and \fIpbkdf2\fR. In general this controls how
305+
often the algorithm will be run. In case of \fIpbkdf2\fR the argument will
306+
be interpret as an interval in seconds. The actual number of iterations to
307+
achieve this delay will be calculated with \fItomb-kdb-pbkdf2-getiter\fR,
308+
which needs to available (normally installed alongside tomb's pbkdf2 tools).
309+
Reason being that the actual time to wait depends on the CPU speed.
310+
OWASP recommendations from 2023 suggest a minimal iteration count of 600000
311+
for \fIpbkdf2\fR, which should be achieved with the current default value.
312+
Default is 3 (based on the \fIargon2\fR default).
313+
.B
299314
.IP "--kdfmem \fI<memory>\fR"
300315
In case of \fIargon2\fR KDF algorithm, this value specifies the size of RAM
301316
used: it consists of a number which is the elevated power of two in kilobytes.
302317
Default is 18 which is 250 MiB (2^18 = 262,144 kilobytes).
303318
.B
319+
.IP "--kdfpar \fI<# of threads>\fR"
320+
In case of \fIargon2\fR KDF algorithm, this value specifies the number of
321+
threads that should be used. This helps to remedy the effects of an increased
322+
time cost for your system whereas setups of ASICs or GPUs don't profit. Only
323+
increase if memory or iteration got increased that much, that key decryption
324+
takes massively longer on regular systems.
325+
Default is 1 thread (based on the \fIargon2\fR default).
326+
.B
304327
.IP "--sudo \fI<executable>\fR"
305328
Select a different tool than sudo for privilege escalation.
306329
Alternatives supported so far are: pkexec, doas, sup, sud. For any

extras/test/30_kdf-pbkdf2.sh

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/usr/bin/env zsh
2+
3+
export test_description="Testing tomb with pbkdf2 KDF key"
4+
5+
source ./setup
6+
7+
if test_have_prereq KDF; then
8+
test_export "kdf"
9+
test_expect_success 'Testing pbkdf2 KDF: tomb creation' '
10+
tt_dig -s 20 &&
11+
tt_forge --tomb-pwd $DUMMYPASS --kdf pbkdf2 &&
12+
print $DUMMYPASS \
13+
| gpg --batch --passphrase-fd 0 --no-tty --no-options -d $tomb_key \
14+
| xxd &&
15+
tt_lock --tomb-pwd $DUMMYPASS
16+
'
17+
18+
test_expect_success 'Testing pbkdf2 KDF: tomb passwd' '
19+
tt passwd -k $tomb_key \
20+
--unsafe --tomb-old-pwd $DUMMYPASS --tomb-pwd $DUMMYPASSNEW &&
21+
tt passwd -k $tomb_key \
22+
--unsafe --tomb-old-pwd $DUMMYPASSNEW --tomb-pwd $DUMMYPASS
23+
'
24+
25+
test_expect_success 'Testing pbkdf2 KDF: tomb open & close' '
26+
tt_open --tomb-pwd $DUMMYPASS &&
27+
tt_close
28+
'
29+
fi
30+
31+
test_done

extras/test/30_kdf.sh

Lines changed: 0 additions & 58 deletions
This file was deleted.

extras/test/31_kdf-argon2.sh

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/usr/bin/env zsh
2+
3+
export test_description="Testing tomb with argon2 KDF key"
4+
5+
source ./setup
6+
7+
if test_have_prereq ARGON; then
8+
test_export "argon"
9+
test_expect_success 'Testing argon2 KDF: tomb creation' '
10+
tt_dig -s 20 &&
11+
tt_forge --tomb-pwd $DUMMYPASS --kdf argon2 &&
12+
print $DUMMYPASS \
13+
| gpg --batch --passphrase-fd 0 --no-tty --no-options -d $tomb_key \
14+
| xxd &&
15+
tt_lock --tomb-pwd $DUMMYPASS
16+
'
17+
18+
test_expect_success 'Testing argon2 KDF: tomb passwd' '
19+
tt passwd -k $tomb_key --kdf argon2 \
20+
--unsafe --tomb-old-pwd $DUMMYPASS --tomb-pwd $DUMMYPASSNEW &&
21+
tt passwd -k $tomb_key --kdf argon2 \
22+
--unsafe --tomb-old-pwd $DUMMYPASSNEW --tomb-pwd $DUMMYPASS
23+
'
24+
25+
test_expect_success 'Testing argon2 KDF: tomb open & close' '
26+
tt_open --tomb-pwd $DUMMYPASS &&
27+
tt_close
28+
'
29+
fi
30+
31+
test_done

extras/test/setup

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ MEDIA="/media"
5050
command -v steghide > /dev/null && test_set_prereq STEGHIDE
5151
command -v e2fsck resize2fs > /dev/null && test_set_prereq RESIZER
5252
command -v tomb-kdb-pbkdf2 > /dev/null && test_set_prereq KDF
53+
command -v argon2 > /dev/null && test_set_prereq ARGON
5354
command -v qrencode > /dev/null && test_set_prereq QRENCODE
5455
command -v lsof > /dev/null && test_set_prereq LSOF
5556
command -v python3 > /dev/null && test_set_prereq PYTHON3

tomb

Lines changed: 59 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ typeset -i RECOLL=1
6666
typeset -i QRENCODE=1
6767
typeset -i LSOF=1
6868
typeset -i ACL=1
69+
typeset -i ARGON2=1
6970

7071
# Default mount options
7172
typeset MOUNTOPTS="rw,noatime,nodev"
@@ -749,8 +750,13 @@ usage() {
749750
_print " -R provide GnuPG hidden recipients (separated by comma)"
750751
_print " --sudo super user exec alternative to sudo (doas or none)"
751752

752-
[[ $KDF == 1 ]] && {
753-
_print " --kdf forge keys armored against dictionary attacks"
753+
[[ $KDF == 1 ]] || [[ $ARGON2 == 1 ]] && {
754+
_print " --kdf forge keys armored against dictionary attacks (pbkdf2, argon2)"
755+
_print " --kdfiter Number of iterations (meaning depending on KDF algorithm) (pbkdf2, argon2)"
756+
}
757+
[[ $ARGON2 == 1 ]] && {
758+
_print " --kdfmem memory to be used (argon2)"
759+
_print " --kdfpar number of threads (argon2)"
754760
}
755761

756762
echo
@@ -1225,11 +1231,14 @@ get_lukskey() {
12251231
kdf_salt="${firstline[(ws:_:)3]}"
12261232
kdf_ic="${firstline[(ws:_:)4]}"
12271233
kdf_mem="${firstline[(ws:_:)5]}"
1234+
kdf_par="${firstline[(ws:_:)6]}"
1235+
# ToDo also parse kdf_len?
12281236
_message "Unlocking KDF key protection (::1 kdf::)" $kdf_hash
12291237
_verbose "KDF salt: $kdf_salt"
12301238
_verbose "KDF ic: $kdf_ic"
12311239
_verbose "KDF mem: $kdf_mem"
1232-
_password=$(argon2 $kdf_salt -m $kdf_mem -t $kdf_ic -l 64 -r 2>/dev/null <<<$_password)
1240+
_verbose "KDF # threads: $kdf_par"
1241+
_password=$(argon2 $kdf_salt -m $kdf_mem -t $kdf_ic -p $kdf_par -l 64 -r 2>/dev/null <<<$_password)
12331242
;;
12341243

12351244
*)
@@ -1470,50 +1479,58 @@ gen_key() {
14701479
fi
14711480

14721481
header=""
1473-
[[ $KDF == 1 ]] && {
1482+
[[ $KDF == 1 ]] || [[ $ARGON2 == 1 ]] && {
14741483
{ option_is_set --kdf } && {
1475-
# KDF is a new key strengthening technique against brute forcing
1484+
# KDF is a key strengthening technique against brute forcing
14761485
# see: https://github.com/dyne/Tomb/issues/82
1477-
itertime="`option_value --kdf`"
1478-
# removing support of floating points because they can't be type checked well
1479-
# if [[ "$itertime" != <-> ]]; then
1480-
# unset tombpass
1481-
# unset tombpasstmp
1482-
# _warning "Wrong argument for --kdf: must be an integer number (iteration seconds)."
1483-
# _failure "Depending on the speed of machines using this tomb, use 1 to 10, or more"
1484-
# return 1
1485-
# fi
1486-
# # --kdf takes one parameter: iter time (on present machine) in seconds
1487-
1488-
kdftype="`option_value --kdftype`"
1489-
kdftype=${kdftype:-pbkdf2}
1486+
# Two KDF are currently supported:
1487+
# * pbkdf2 (time restrictive)
1488+
# * argon2 (memory, parallelismn restrictive and through those time)
1489+
1490+
# --kdfiter takes one integer value as parameter
1491+
# argon2: # of iterations (default of 3);
1492+
# pbkdf2: calculates # of iterations to reach this as time cost in seconds
1493+
itertime="`option_value --kdfiter`"
1494+
itertime=${itertime:-3}
1495+
1496+
# Generating salt (either via tomb-kdb-pbkdf2 or a shell fallback)
1497+
if $(command -v tomb-kdb-pbkdf2-gensalt 1>/dev/null 2>/dev/null); then
1498+
kdfsalt=`tomb-kdb-pbkdf2-gensalt`
1499+
else
1500+
kdfsalt=$(LC_CTYPE=C tr -cd 'a-f0-9' < /dev/random | head -c 64)
1501+
fi
1502+
_message "kdf salt: ::1 kdfsalt::" $kdfsalt
1503+
1504+
# --kdf takes one parameter: what KDF
1505+
kdftype="`option_value --kdf`"
14901506
case ${kdftype} in
1491-
pbkdf2)
1507+
pbkdf2)
14921508
local -i microseconds
14931509
microseconds=$(( itertime * 1000000 ))
1494-
_success "Using KDF, iteration time: ::1 microseconds::" $microseconds
1495-
_message "generating salt"
1496-
pbkdf2_salt=`tomb-kdb-pbkdf2-gensalt`
1497-
_message "calculating iterations"
1510+
_success "Using pbkdf2 as KDF"
1511+
_message "iteration time: ::1 microseconds::" $microseconds
14981512
pbkdf2_iter=`tomb-kdb-pbkdf2-getiter $microseconds`
1499-
_message "encoding the password"
1513+
_message "iterations: ::1 pbkdf2_iter::" $pbkdf2_iter
15001514
# We use a length of 64bytes = 512bits (more than needed!?)
1501-
tombpass=`tomb-kdb-pbkdf2 $pbkdf2_salt $pbkdf2_iter 64 <<<"${tombpass}"`
1502-
1503-
header="_KDF_pbkdf2sha1_${pbkdf2_salt}_${pbkdf2_iter}_64\n"
1515+
tombpass=`tomb-kdb-pbkdf2 $kdfsalt $pbkdf2_iter 64 <<<"${tombpass}"`
1516+
header="_KDF_pbkdf2sha1_${kdfsalt}_${pbkdf2_iter}_64\n"
15041517
;;
1505-
argon2)
1506-
_success "Using KDF Argon2"
1518+
argon2)
1519+
_success "Using Argon2 as KDF"
1520+
_message "iterations: ::1 kdfiterations::" $itertime
15071521
kdfmem="`option_value --kdfmem`"
15081522
kdfmem=${kdfmem:-18}
15091523
_message "memory used: 2^::1 kdfmemory::" $kdfmem
1510-
itertime="`option_value --kdf`"
1511-
itertime=${itertime:-3}
1512-
kdfsalt=`tomb-kdb-pbkdf2-gensalt`
1513-
_message "kdf salt: ::1 kdfsalt::" $kdfsalt
1514-
_message "kdf iterations: ::1 kdfiterations::" $itertime
1515-
tombpass=`argon2 $kdfsalt -m $kdfmem -t $itertime -l 64 -r <<<"${tombpass}"`
1516-
header="_KDF_argon2_${kdfsalt}_${itertime}_${kdfmem}_64\n"
1524+
kdfpar="`option_value --kdfpar`"
1525+
kdfpar=${kdfpar:-1}
1526+
_message "parallelismn: ::1 kdfparallel::" $kdfpar
1527+
tombpass=`argon2 $kdfsalt -m $kdfmem -t $itertime -p $kdfpar -l 64 -r <<<"${tombpass}"`
1528+
header="_KDF_argon2_${kdfsalt}_${itertime}_${kdfmem}_${kdfpar}_64\n"
1529+
;;
1530+
*)
1531+
_warning "unrecognized KDF ::1::" $kdftype
1532+
_warning "key won\'t be protected via a KDF implementation"
1533+
_warning "only pbkdf2 and argon2 are valid arguments"
15171534
;;
15181535
esac
15191536
}
@@ -1976,7 +1993,7 @@ forge_key() {
19761993
$destkey $algo
19771994

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

19821999
TOMBKEYFILE="$destkey" # Set global variable
@@ -3129,19 +3146,19 @@ main() {
31293146
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:)
31303147
subcommands_opts[__default]=""
31313148
# -o in open and mount is used to pass alternate mount options
3132-
subcommands_opts[open]="n -nohook=n k: -kdf: -kdftype: -kdfmem: o: -ignore-swap -tomb-pwd: r: R: p -preserve-ownership=p"
3149+
subcommands_opts[open]="n -nohook=n k: o: -ignore-swap -tomb-pwd: r: R: p -preserve-ownership=p"
31333150
subcommands_opts[mount]=${subcommands_opts[open]}
31343151

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

31373154
# -o in forge and lock is used to pass an alternate cipher.
3138-
subcommands_opts[forge]="-ignore-swap k: -kdf: -kdftype: -kdfmem: o: -tomb-pwd: -use-random r: R: "
3155+
subcommands_opts[forge]="-ignore-swap k: -kdf: -kdfiter: -kdfmem: -kdfpar: o: -tomb-pwd: -use-random r: R: "
31393156
subcommands_opts[dig]="-ignore-swap s: -size=s "
3140-
subcommands_opts[lock]="-ignore-swap k: -kdf: -kdftype: -kdfmem: o: -tomb-pwd: r: R: -filesystem: "
3141-
subcommands_opts[setkey]="k: -ignore-swap -kdf: -kdftype: -kdfmem: -tomb-old-pwd: -tomb-pwd: r: R: "
3157+
subcommands_opts[lock]="-ignore-swap k: o: -tomb-pwd: r: R: -filesystem: "
3158+
subcommands_opts[setkey]="k: -ignore-swap -tomb-old-pwd: -tomb-pwd: r: R: "
31423159
subcommands_opts[engrave]="k: "
31433160

3144-
subcommands_opts[passwd]="k: -ignore-swap -kdf: -kdftype: -kdfmem: -tomb-old-pwd: -tomb-pwd: r: R: "
3161+
subcommands_opts[passwd]="k: -ignore-swap -kdf: -kdfiter: -kdfmem: -kdfpar: -tomb-old-pwd: -tomb-pwd: r: R: "
31453162
subcommands_opts[close]=""
31463163
subcommands_opts[help]=""
31473164
subcommands_opts[slam]=""

0 commit comments

Comments
 (0)