Add KDF support #82

Include pbkdf2 tools inside tomb
It also supports parameters (itertime).
This commit is contained in:
boyska 2012-08-04 18:34:10 +02:00 committed by Jaromil
parent 6bb655df0b
commit 2e6a3df756
14 changed files with 504 additions and 38 deletions

36
KEY_SPECIFICATIONS.txt Normal file
View File

@ -0,0 +1,36 @@
Overview
=========
What's a key?
It basicly is a gpg simmetrically encrypted, ascii-armored file.
It's encryption key is a function (see below, on KDF section) of your tomb
passphrase.
Layout
======
Before coming to the gpg part, there could be some "header" lines specifying
metatada. They're done like this:
_FIELD_params_params_and_more_params_
where FIELD should be the description for the header.
Pay much attention to the fact that there should ONLY be ASCII characters there,
to avoid encoding issues and whatever. Needs something more? Use base64encode.
(Of course, you're free to pack params into a single field, base64encoding
whatever you want).
And every header field should be in only one line.
KDF
===
Key Derivation Functions, are functions which will make your key stronger
spending some CPU time: the basic idea is that you have to compute that function
just once in a while, but an attacker that wants to bruteforce has to compute it
for every passphrase he's checking. This will make the bruteforce much more
expensive.
The header line format is _KDF_$method_$params_$params_... where $method is the
method we are using (ie: scrypt) and params is something that it needs (ie:
salt).

View File

@ -1 +1 @@
SUBDIRS = src share doc
SUBDIRS = src src/kdf share doc

View File

@ -89,6 +89,7 @@ dnl ---------------------------------------------------------------
PKG_CHECK_MODULES(GTK2, [gtk+-2.0 >= 2.16], :,
AC_MSG_ERROR([*** Gtk+2 >=2.16 development files not found!]))
AM_PATH_LIBGCRYPT([1.5.0], :, AC_MSG_ERROR([gcrypt development files not found]))
AC_SUBST([GTK2_CFLAGS])
AC_SUBST([GTK2_LIBS])
@ -130,6 +131,7 @@ dnl alphabetic order on dir/subdir, but Makefile sorts before everything
AC_CONFIG_FILES([
Makefile
src/Makefile
src/kdf/Makefile
doc/Makefile
share/Makefile
])

View File

@ -111,7 +111,7 @@ the size of the new \fIfile\fR to be created, in megabytes.
.IP "-k \fI<keyfile>\fR"
When opening a tomb, this option can be used to specify the location
of the key to use. Keys are created with the same name of the tomb
file adding a '.gpg' suffix, but can be later renamed and transported
file adding a '.key' suffix, but can be later renamed and transported
on other media. When a key is not found, the program asks to insert a
USB storage device and it will look for the key file inside it.
If \fI<keyfile>\fR is "-" (dash), it will read stdin
@ -123,6 +123,31 @@ tomb create -s 100 tombname -k /media/usb/tombname
.EE
to put the key on a usb pendrive
.B
.IP "--kdf \fI<method>\fR"
This will specify the KDF method to use for the tomb we're creating.
Please note that no stable release of tomb supports KDF; if you use it,
your tomb might be unusable with an older version of tomb.
You can specify parameters with --kdf=method:param. That is, for example,
\fI--kdf=pbkdf2:2.5\fR will use pbkdf2 with an itertime of 2.5 seconds
Supported methods are: pbkdf2, null
.B pbkdf2
is probably the most used kdf in security applications, so it's a good choice.
It accepts one parameter, that is the seconds it will take on this computer to
derive the key. The default is 1.
.B null
is just the same as not using --kdf at all: it will stick to the "classic"
behaviour
.B
.IP "--kdf \fI<method>\fR"
This will specify the KDF method to use for the tomb we're creating.
Please note that no stable release of tomb supports KDF; if you use it,
your tomb might be unusable with an older version of tomb.
.B
.IP "-n"
Skip processing of post-hooks and bind-hooks if found inside the tomb.

4
src/kdf/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
tomb-kdf-pbkdf2
tomb-kdf-pbkdf2-gensalt
tomb-kdf-pbkdf2-getiter
tomb-utils-hexencode

13
src/kdf/Makefile.am Normal file
View File

@ -0,0 +1,13 @@
bin_PROGRAMS = tomb-kdf-pbkdf2 tomb-kdf-pbkdf2-gensalt tomb-kdf-pbkdf2-getiter hexencode
tomb_kdf_pbkdf2_SOURCES = pbkdf2/pbkdf2.c
tomb_kdf_pbkdf2_CFLAGS = $(LIBGCRYPT_CFLAGS)
tomb_kdf_pbkdf2_LDADD = $(LIBGCRYPT_LIBS)
tomb_kdf_pbkdf2_gensalt_SOURCES = pbkdf2/gen_salt.c
tomb_kdf_pbkdf2_getiter_SOURCES = pbkdf2/benchmark.c
tomb_kdf_pbkdf2_getiter_CFLAGS = $(LIBGCRYPT_CFLAGS)
tomb_kdf_pbkdf2_getiter_LDADD = $(LIBGCRYPT_LIBS)
hexencode_SOURCES = hexencode.c

20
src/kdf/README Normal file
View File

@ -0,0 +1,20 @@
PLANS
------
While this can be useful for general purpose, it specially fits tomb, and it's designed for easy integration and compilation.
Binary name will then be:
tomb-kdf-${algo}
tomb-kdf-${algo}-gensalt
tomb-kdf-${algo}-getiter
hexencode (or similar utils, should they be developed), go with:
tomb-utils-hexencode
Base64 vs hexencode
-------------------
While base64 is easier to use (shell command, more compact), pbkdf2 use hex
in its specifications.
This could be solved with an option (-x for hex, defaults to base64)

49
src/kdf/hexencode.c Normal file
View File

@ -0,0 +1,49 @@
/*
* A simple utility that reads from stdin and output the hexencoding (on a single line) of the input
*/
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
static int decode_mode = 0;
int main(int argc, char *argv[]) {
char c;
char buf[3];
int read_bytes;
int opt;
static struct option long_options[] =
{
{"decode", no_argument, &decode_mode, 1},
{"encode", no_argument, &decode_mode, 0},
{0,0,0,0}
};
int option_index = 0;
while(1) {
option_index = 0;
opt = getopt_long(argc, argv, "", long_options, &option_index);
if(opt == -1)
break;
switch(opt) {
case 0:
break;
case '?':
return 127;
default:
abort();
}
}
if(decode_mode == 0) {
while(( c = (char)getchar() ) != EOF)
printf("%02x", c);
return 0;
} else {
while( (read_bytes=fread(buf, sizeof(char), 2, stdin)) != 0) {
if(read_bytes == 1) buf[1]='\0';
sscanf(buf, "%x", &c);
printf("%c", c);
}
return 0;
}
}

View File

@ -0,0 +1,59 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <gcrypt.h>
static long bench(int ic) {
char *pass = "mypass";
unsigned char *salt = "abcdefghijklmno";
int salt_len = strlen(salt);
int result_len = 64;
unsigned char *result = calloc(result_len, sizeof(char));
struct timeval start, end;
long microtime;
gettimeofday(&start, NULL);
gcry_kdf_derive( pass, strlen(pass), GCRY_KDF_PBKDF2, GCRY_MD_SHA1, salt, salt_len, ic, result_len, result);
gettimeofday(&end, NULL);
microtime = 1000000*end.tv_sec+end.tv_usec - (1000000*start.tv_sec+start.tv_usec);
return (long)microtime;
}
int main(int argc, char *argv[])
{
long desired_time = 1000000;
long microtime;
int ic=100;
int tries=0;
if(argc >= 2)
sscanf(argv[1], "%ld", &desired_time);
if (!gcry_check_version ("1.5.0")) {
fputs ("libgcrypt version mismatch\n", stderr);
exit (2);
}
/* Allocate a pool of 16k secure memory. This make the secure memory
available and also drops privileges where needed. */
gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
/* It is now okay to let Libgcrypt complain when there was/is
a problem with the secure memory. */
gcry_control (GCRYCTL_RESUME_SECMEM_WARN);
/* Tell Libgcrypt that initialization has completed. */
gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
microtime = bench(ic);
while( abs(desired_time-microtime) > (desired_time/10) /*little difference */
&& tries++ <= 5) {
float ratio = (float)desired_time/microtime;
if(ratio > 1000) ratio=1000.0;
ic*=ratio;
if(ic<1) ic=1;
microtime = bench(ic);
}
printf("%d\n", ic);
return 0;
}

39
src/kdf/pbkdf2/gen_salt.c Normal file
View File

@ -0,0 +1,39 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void print_hex(unsigned char *buf, int len)
{
int i;
for(i=0;i<len;i++){
printf("%02x", buf[i]);
}
}
int main(int argc, char **argv) {
int len=32;
int res;
unsigned char *buf;
FILE *rand;
if(argc>=2) {
if(sscanf(argv[1], "%d", &len) != 1) {
fprintf(stderr, "Error: len must be an integer\n");
return 1;
}
}
buf = calloc(len, sizeof(char));
memset(buf, 9, len);
rand = fopen("/dev/random", "r");
res = fread(buf, sizeof(char), len, rand);
if( res != len) {
fprintf(stderr, "Error reading /dev/random: %d != %d, \n", res, len);
fclose(rand);
free(buf);
return 2;
}
fclose(rand);
print_hex(buf, len);
free(buf);
return 0;
}

144
src/kdf/pbkdf2/pbkdf2.c Normal file
View File

@ -0,0 +1,144 @@
/*
** SYNOPSIS
** echo "passphrase" | pbkdf2 salt_hex count > 48_byte_hex_key_and_iv
**
** DESCRIPTION
**
** Make the "Password-Based Key Derivation Function v2" function found in
** the openssl library available to the command line, as it is not available
** for use from the "openssl" command. At the time of writing the "openssl"
** command only encrypts using the older, 'fast' pbkdf1.5 method.
**
** The 'salt_hex' is the salt to be used, as a hexadecimal string. Typically
** this is 8 bytes (64 bit), and is an assigned randomly during encryption.
**
** The 'count' is iteration count used to make the calculation of the key
** from the passphrase longer so as to take 1/2 to 2 seconds to generate.
** This complexity prevents slows down brute force attacks enormously.
**
** The output of the above is a 48 bytes in hexadeximal, which is typically
** used for 32 byte encryption key KEY and a 16 byte IV as needed by
** Crypt-AES-256 (or some other encryption method).
**
** NOTE: While the "openssl" command can accept a hex encoded 'key' and 'iv'
** it only does so on the command line, which is insecure. As such I
** recommend that the output only be used with API access to the "OpenSSL"
** cryptography libraries.
**
*************
**
** Anthony Thyssen 4 November 2009 A.Thyssen@griffith.edu.au
**
** Based on a test program "pkcs5.c" found on
** http://www.mail-archive.com/openssl-users@openssl.org
** which uses openssl to perform PBKDF2 (RFC2898) iteritive (slow) password
** hashing.
**
** Build
** gcc -o pbkdf2 pbkdf2.c -lcrypto
**
*/
#include <stdio.h>
#include <string.h>
#include <gcrypt.h>
/* TODO: move print_hex and hex_to_binary to utils.h, with separate compiling */
void print_hex(unsigned char *buf, int len)
{
int i;
for(i=0;i<len;i++)
printf("%02x", buf[i]);
printf("\n");
}
int hex_to_binary(unsigned char *buf, char *hex)
{
int ret;
int count=0;
while(*hex) {
if( hex[1] ) {
ret = sscanf( hex, "%2x", (unsigned int*) buf++ );
hex += 2;
}
else {
ret = sscanf( hex++, "%1x", (unsigned int*)buf++ );
}
count++;
if( ret != 1)
return -1;
}
*buf = 0; // null terminate -- precaution
return count;
}
int main(int argc, char *argv[])
{
char *pass = NULL;
unsigned char *salt;
int salt_len; // salt length in bytes
int ic=0; // iterative count
int result_len;
unsigned char *result; // result (binary - 32+16 chars)
int i;
if ( argc != 4 ) {
fprintf(stderr, "usage: %s salt count len <passwd >binary_key_iv\n", argv[0]);
exit(10);
}
//TODO: move to base64decode
salt=calloc(strlen(argv[1])/2+3, sizeof(char));
salt_len=hex_to_binary(salt, argv[1]);
if( salt_len <= 0 ) {
fprintf(stderr, "Error: %s is not a valid salt (it must be a hexadecimal string)\n", argv[1]);
exit(1);
}
if( sscanf(argv[2], "%d", &ic) == 0 || ic<=0) {
fprintf(stderr, "Error: count must be a positive integer\n");
exit(1);
}
if( sscanf(argv[3], "%d", &result_len) == 0 || result_len<=0) {
fprintf(stderr, "Error: result_len must be a positive integer\n");
exit(1);
}
fscanf(stdin, "%ms", &pass);
if ( pass[strlen(pass)-1] == '\n' )
pass[strlen(pass)-1] = '\0';
// PBKDF 2
result = calloc(result_len, sizeof(unsigned char*));
if (!gcry_check_version ("1.5.0")) {
fputs ("libgcrypt version mismatch\n", stderr);
exit (2);
}
/* Allocate a pool of 16k secure memory. This make the secure memory
available and also drops privileges where needed. */
gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
/* It is now okay to let Libgcrypt complain when there was/is
a problem with the secure memory. */
gcry_control (GCRYCTL_RESUME_SECMEM_WARN);
/* Tell Libgcrypt that initialization has completed. */
gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
gcry_kdf_derive( pass, strlen(pass), GCRY_KDF_PBKDF2, GCRY_MD_SHA1, salt, salt_len, ic, result_len, result);
print_hex(result, result_len); // Key + IV (as hex string)
//clear and free everything
for(i=0; i<result_len;i++)
result[i]=0;
free(result);
for(i=0; i<strlen(pass); i++) //blank
pass[i]=0;
free(pass);
for(i=0; i<strlen(argv[1])/2+3; i++) //blank
salt[i]=0;
free(salt);
return(0);
}
/* vim: set noexpandtab ts=4 sw=4: */

22
src/kdf/test.sh Normal file
View File

@ -0,0 +1,22 @@
#!/usr/bin/env zsh
error=0
while read line; do
pass=`cut -f1 <<<$line`
salt=`cut -f2 <<<$line`
iter=`cut -f3 <<<$line`
keylen=`cut -f4 <<<$line`
expected=`cut -f5 <<<$line`
hexsalt=`cut -f6 <<<$line`
#TODO: check!
derived=`./pbkdf2 $hexsalt $iter $keylen <<<$pass`
if [[ $derived != $expected ]]; then
echo ./pbkdf2 $hexsalt $iter $keylen "<<<$pass"
echo "Expected $expected, got $derived" >&2
error=$((error + 1))
fi
done < test.txt
if [[ $error == 1 ]]; then
exit $error
fi

BIN
src/kdf/test.txt Normal file

Binary file not shown.

125
src/tomb
View File

@ -166,6 +166,17 @@ check_bin() {
# resize suite check bin!
which e2fsck > /dev/null || die "Cannot find e2fsck. Please install it." 1
which resize2fs > /dev/null || die "Cannot find resize2fs. Please install it." 1
if which tomb-kdf-pbkdf2 &> /dev/null; then
KDF_PBKDF2="tomb-kdf-pbkdf2"
else
local our_pbkdf2
our_pbkdf2="$(dirname $(readlink -f $TOMBEXEC))/kdf/tomb-kdf-pbkdf2"
if which $our_pbkdf2 &> /dev/null; then
KDF_PBKDF2=$our_pbkdf2
fi
fi
}
# }}}
@ -732,15 +743,15 @@ create_tomb() {
# here user is prompted for key password
for c in 1 2 3; do
# 3 tries to write two times a matching password
tombpass=`exec_as_user ${TOMBEXEC} askpass "Secure key for ${tombname}"`
tombpasstmp=$tombpass
tombpass=`exec_as_user ${TOMBEXEC} askpass "Secure key for ${tombname} (again)"`
if [ "$tombpasstmp" = "$tombpass" ]; then
break;
fi
unset tombpasstmp
unset tombpass
# 3 tries to write two times a matching password
tombpass=`exec_as_user ${TOMBEXEC} askpass "Secure key for ${tombname}"`
tombpasstmp=$tombpass
tombpass=`exec_as_user ${TOMBEXEC} askpass "Secure key for ${tombname} (again)"`
if [ "$tombpasstmp" = "$tombpass" ]; then
break;
fi
unset tombpasstmp
unset tombpass
done
if [ -z $tombpass ]; then
@ -751,9 +762,36 @@ create_tomb() {
fi
gpg \
--openpgp --batch --no-options --no-tty --passphrase-fd 0 2>/dev/null \
-o "${tombkey}" -c -a ${keytmp}/tomb.tmp <<< ${tombpass}
_verbose "KDF method chosen is: '`option_value --kdf`'"
kdf_method=$(cut -d: -f1 <<<`option_value --kdf` )
case $kdf_method in
pbkdf2)
#one parameter: iter time in seconds
seconds=$(cut -d: -f2 -s <<<`option_value --kdf`)
if [[ -z $seconds ]]; then
seconds=1
fi
local -i microseconds
microseconds=$((seconds*1000000))
_verbose "Microseconds: $microseconds"
pbkdf2_salt=`${KDF_PBKDF2}-gensalt`
pbkdf2_iter=`${KDF_PBKDF2}-getiter $microseconds`
tombpass=`${KDF_PBKDF2} $pbkdf2_salt $pbkdf2_iter 64 <<<${tombpass}` #64bytes=512bits is the key length (huge!)
header="_KDF_pbkdf2sha1_${pbkdf2_salt}_${pbkdf2_iter}_64\n"
;;
""|null)
header=""
;;
*)
_warning "KDF method non recognized"
return 1
header=""
;;
esac
( echo -n $header; gpg \
--openpgp --batch --no-options --no-tty --passphrase-fd 0 2>/dev/null \
-o - -c -a ${keytmp}/tomb.tmp <<< ${tombpass} ) > $tombkey
unset tombpass
chown ${_uid}:${_gid} ${tombkey}
@ -924,31 +962,46 @@ mount_tomb() {
_warning "Password is required for key ${keyname}"
for c in 1 2 3; do
if [ $c = 1 ]; then
tombpass=`exec_as_user ${TOMBEXEC} askpass "Open tomb ${keyname}"`
else
tombpass=`exec_as_user ${TOMBEXEC} askpass "Open tomb $keyname (retry $c)"`
fi
(gpg --batch --passphrase-fd 0 --no-tty --no-options \
-d "${tombkey}" 2> /dev/null <<< ${tombpass} ) \
| cryptsetup --key-file - luksOpen ${nstloop} ${mapper}
unset tombpass
if [ $c = 1 ]; then
tombpass=`exec_as_user ${TOMBEXEC} askpass "Open tomb ${keyname}"`
else
tombpass=`exec_as_user ${TOMBEXEC} askpass "Open tomb $keyname (retry $c)"`
fi
#TODO: read the first line: if it looks like a KDF, do KDF
firstline=`head -n1 < $tombkey`
if [[ $firstline =~ '^_KDF_' ]]; then
_verbose "KDF: `cut -d_ -f 3 <<<$firstline`"
case `cut -d_ -f 3 <<<$firstline` in
pbkdf2sha1)
pbkdf2_param=`cut -d_ -f 4- <<<$firstline | tr '_' ' '`
tombpass=$(${KDF_PBKDF2} ${=pbkdf2_param} 2> /dev/null <<<$tombpass)
;;
*)
_failure "No suitable program for KDF `cut -f 3 <<<$firstline`"
return 1
;;
esac
fi
(gpg --batch --passphrase-fd 0 --no-tty --no-options \
-d "${tombkey}" 2> /dev/null <<< ${tombpass} ) \
| cryptsetup --key-file - luksOpen ${nstloop} ${mapper}
unset tombpass
# if key was from stdin delete temp file and dir
if [ $tombkeydir ]; then
${=WIPE} ${tombkey}
rmdir $tombkeydir
fi
# if key was from stdin delete temp file and dir
if [ $tombkeydir ]; then
${=WIPE} ${tombkey}
rmdir $tombkeydir
fi
# if key was from stdin delete temp file and dir
if [ $tombkeydir ]; then
${WIPE[@]} ${tombkey}
rmdir $tombkeydir
fi
# if key was from stdin delete temp file and dir
if [ $tombkeydir ]; then
${WIPE[@]} ${tombkey}
rmdir $tombkeydir
fi
if [ -r /dev/mapper/${mapper} ]; then
break; # password was correct
fi
if [ -r /dev/mapper/${mapper} ]; then
break; # password was correct
fi
done
if ! [ -r /dev/mapper/${mapper} ]; then
@ -1679,7 +1732,7 @@ main() {
subcommands_opts[__default]=""
subcommands_opts[open]="f n -nohook=n k: -key=k U: -uid=U G: -gid=G o: -mount-options=o -ignore-swap"
subcommands_opts[mount]=${subcommands_opts[open]}
subcommands_opts[create]="f s: -size=s -force k: -key=k U: -uid=U G: -gid=G -ignore-swap"
subcommands_opts[create]="f s: -size=s -force k: -key=k U: -uid=U G: -gid=G -ignore-swap -kdf:"
subcommands_opts[passwd]="f -ignore-swap"
subcommands_opts[close]=""
subcommands_opts[help]=""
@ -1756,7 +1809,7 @@ main() {
continue #it shouldnt be appended to PARAM
elif [[ $arg[1] == '-' ]]; then
if [[ $ok == 0 ]]; then
die "unrecognized option $arg" 127
die "unrecognized option $arg for subcommand $subcommand" 127
fi
fi
PARAM+=$arg