tAdd KDF support #82 - tomb - the crypto undertaker
 (HTM) git clone git://parazyd.org/tomb.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 2e6a3df756fbc08e462cd6db64f2979795cc1f83
 (DIR) parent 6bb655df0bb1bb3e4f19a27de88a79bdac1ab5b7
 (HTM) Author: boyska <piuttosto@logorroici.org>
       Date:   Sat,  4 Aug 2012 18:34:10 +0200
       
       Add KDF support #82
       
       Include pbkdf2 tools inside tomb
       It also supports parameters (itertime).
       
       Diffstat:
         A KEY_SPECIFICATIONS.txt              |      36 +++++++++++++++++++++++++++++++
         M Makefile.am                         |       2 +-
         M configure.ac                        |       2 ++
         M doc/tomb.1                          |      27 ++++++++++++++++++++++++++-
         A src/kdf/.gitignore                  |       4 ++++
         A src/kdf/Makefile.am                 |      13 +++++++++++++
         A src/kdf/README                      |      20 ++++++++++++++++++++
         A src/kdf/hexencode.c                 |      49 +++++++++++++++++++++++++++++++
         A src/kdf/pbkdf2/benchmark.c          |      59 +++++++++++++++++++++++++++++++
         A src/kdf/pbkdf2/gen_salt.c           |      39 +++++++++++++++++++++++++++++++
         A src/kdf/pbkdf2/pbkdf2.c             |     144 +++++++++++++++++++++++++++++++
         A src/kdf/test.sh                     |      22 ++++++++++++++++++++++
         A src/kdf/test.txt                    |       0 
         M src/tomb                            |     125 ++++++++++++++++++++++---------
       
       14 files changed, 504 insertions(+), 38 deletions(-)
       ---
 (DIR) diff --git a/KEY_SPECIFICATIONS.txt b/KEY_SPECIFICATIONS.txt
       t@@ -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).
 (DIR) diff --git a/Makefile.am b/Makefile.am
       t@@ -1 +1 @@
       -SUBDIRS = src share doc
       +SUBDIRS = src src/kdf share doc
 (DIR) diff --git a/configure.ac b/configure.ac
       t@@ -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])
        
       t@@ -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
        ])
 (DIR) diff --git a/doc/tomb.1 b/doc/tomb.1
       t@@ -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
       t@@ -124,6 +124,31 @@ tomb create -s 100 tombname -k /media/usb/tombname
        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.
        See the \fIHOOKS\fR section in this manual for more information.
 (DIR) diff --git a/src/kdf/.gitignore b/src/kdf/.gitignore
       t@@ -0,0 +1,4 @@
       +tomb-kdf-pbkdf2
       +tomb-kdf-pbkdf2-gensalt
       +tomb-kdf-pbkdf2-getiter
       +tomb-utils-hexencode
 (DIR) diff --git a/src/kdf/Makefile.am b/src/kdf/Makefile.am
       t@@ -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
       +
 (DIR) diff --git a/src/kdf/README b/src/kdf/README
       t@@ -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)
       +
 (DIR) diff --git a/src/kdf/hexencode.c b/src/kdf/hexencode.c
       t@@ -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;
       +        }
       +}
 (DIR) diff --git a/src/kdf/pbkdf2/benchmark.c b/src/kdf/pbkdf2/benchmark.c
       t@@ -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;
       +
       +}
 (DIR) diff --git a/src/kdf/pbkdf2/gen_salt.c b/src/kdf/pbkdf2/gen_salt.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/kdf/pbkdf2/pbkdf2.c b/src/kdf/pbkdf2/pbkdf2.c
       t@@ -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: */
 (DIR) diff --git a/src/kdf/test.sh b/src/kdf/test.sh
       t@@ -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
 (DIR) diff --git a/src/kdf/test.txt b/src/kdf/test.txt
       Binary files differ.
 (DIR) diff --git a/src/tomb b/src/tomb
       t@@ -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
       +
        }
        
        # }}}
       t@@ -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
       t@@ -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}
       t@@ -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
       t@@ -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]=""
       t@@ -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