diff --git a/KEY_SPECIFICATIONS.txt b/KEY_SPECIFICATIONS.txt new file mode 100644 index 0000000..80443fd --- /dev/null +++ b/KEY_SPECIFICATIONS.txt @@ -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). diff --git a/Makefile.am b/Makefile.am index 355f04a..1f0bee8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1 +1 @@ -SUBDIRS = src share doc +SUBDIRS = src src/kdf share doc diff --git a/configure.ac b/configure.ac index 73e2b42..a96f74f 100644 --- a/configure.ac +++ b/configure.ac @@ -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 ]) diff --git a/doc/tomb.1 b/doc/tomb.1 index 80bcb92..9e56ba7 100644 --- a/doc/tomb.1 +++ b/doc/tomb.1 @@ -111,7 +111,7 @@ the size of the new \fIfile\fR to be created, in megabytes. .IP "-k \fI\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\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\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\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. diff --git a/src/kdf/.gitignore b/src/kdf/.gitignore new file mode 100644 index 0000000..7b8e945 --- /dev/null +++ b/src/kdf/.gitignore @@ -0,0 +1,4 @@ +tomb-kdf-pbkdf2 +tomb-kdf-pbkdf2-gensalt +tomb-kdf-pbkdf2-getiter +tomb-utils-hexencode diff --git a/src/kdf/Makefile.am b/src/kdf/Makefile.am new file mode 100644 index 0000000..a5f0d8e --- /dev/null +++ b/src/kdf/Makefile.am @@ -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 + diff --git a/src/kdf/README b/src/kdf/README new file mode 100644 index 0000000..2ec8a09 --- /dev/null +++ b/src/kdf/README @@ -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) + diff --git a/src/kdf/hexencode.c b/src/kdf/hexencode.c new file mode 100644 index 0000000..7a97105 --- /dev/null +++ b/src/kdf/hexencode.c @@ -0,0 +1,49 @@ +/* + * A simple utility that reads from stdin and output the hexencoding (on a single line) of the input + */ + +#include +#include +#include + +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; + } +} diff --git a/src/kdf/pbkdf2/benchmark.c b/src/kdf/pbkdf2/benchmark.c new file mode 100644 index 0000000..1b2e6b1 --- /dev/null +++ b/src/kdf/pbkdf2/benchmark.c @@ -0,0 +1,59 @@ +#include +#include +#include + +#include + +#include + +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; + +} diff --git a/src/kdf/pbkdf2/gen_salt.c b/src/kdf/pbkdf2/gen_salt.c new file mode 100644 index 0000000..4de196a --- /dev/null +++ b/src/kdf/pbkdf2/gen_salt.c @@ -0,0 +1,39 @@ +#include +#include +#include + +void print_hex(unsigned char *buf, int len) +{ + int i; + + for(i=0;i=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; +} diff --git a/src/kdf/pbkdf2/pbkdf2.c b/src/kdf/pbkdf2/pbkdf2.c new file mode 100644 index 0000000..383fe2d --- /dev/null +++ b/src/kdf/pbkdf2/pbkdf2.c @@ -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 +#include + +#include + +/* 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;ibinary_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&2 + error=$((error + 1)) + fi +done < test.txt + +if [[ $error == 1 ]]; then + exit $error +fi diff --git a/src/kdf/test.txt b/src/kdf/test.txt new file mode 100644 index 0000000..3b5ff12 Binary files /dev/null and b/src/kdf/test.txt differ diff --git a/src/tomb b/src/tomb index b045ca5..ef16ebc 100755 --- a/src/tomb +++ b/src/tomb @@ -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