Use PBKDF2 with HMAC-SHA1 for master passphrase in clawsrc.

The 64 bytes long key derivation is stored in 'master_passphrase'
pref, together with number of rounds used in its computation.

Introducing also two new common prefs:
master_passphrase_salt - holds a randomly generated 64 bytes
  for use as salt with PBKDF2. Base64-encoded.
master_passphrase_pbkdf2_rounds - number of rounds (or
  iterations) for next passphrase key derivation

The latter can be tweaked by user in case they want to use more
or less rounds, e.g. if they're running on weaker hardware and
KD with default number of rounds takes too long.
This commit is contained in:
Andrej Kacian 2016-04-07 14:56:48 +02:00
parent e63f8db4c7
commit ffd418aaa7
4 changed files with 157 additions and 40 deletions

View file

@ -16,7 +16,6 @@ IV for the cipher is filled with random bytes.
Encryption
----------
We prepare a buffer 128+blocksize bytes long, with one block of random
data at the beginning, followed by the password we want to encrypt,
rest is padded with zero bytes.
@ -40,8 +39,23 @@ with our password.
Why the random block at the beginning?
--------------------------------------
We are taking advantage of property of CBC mode where decryption with
a wrong IV results in only first block being garbled. Therefore we
prepend a random block to our plaintext before encryption, and discard
first block from plaintext after decryption.
Master passphrase
-----------------
This can be any string user chooses. We store its 64 bytes long key
derivation (KD), using PBKDF2 with HMAC-SHA1, and later check correctness
of user-entered passphrase by making same KD from it and comparing it
to the stored one. Only if the two KDs match, the passphrase is accepted
and remembered for session, thus giving access to account or plugin
passwords.
Salt used for PBKDF2 is stored in 'master_passphrase_salt', encoded
as base64. It consists of 64 randomly generated bytes.
Number of rounds for PBKDF2 is stored in hidden preference
'master_passphrase_pbkdf2_rounds'.

View file

@ -40,6 +40,7 @@
#include "common/passcrypt.h"
#include "common/plugin.h"
#include "common/pkcs5_pbkdf2.h"
#include "common/utils.h"
#include "account.h"
#include "alertpanel.h"
@ -51,6 +52,87 @@
#ifndef PASSWORD_CRYPTO_OLD
static gchar *_master_passphrase = NULL;
/* Length of stored key derivation, before base64. */
#define KD_LENGTH 64
/* Length of randomly generated and saved salt, used for key derivation.
* Also before base64. */
#define KD_SALT_LENGTH 64
static void _generate_salt()
{
#if defined G_OS_UNIX
int rnd;
#elif defined G_OS_WIN32
HCRYPTPROV rnd;
#endif
gint ret;
guchar salt[KD_SALT_LENGTH];
if (prefs_common_get_prefs()->master_passphrase_salt != NULL) {
g_free(prefs_common_get_prefs()->master_passphrase_salt);
}
/* Prepare our source of random data. */
#if defined G_OS_UNIX
rnd = open("/dev/urandom", O_RDONLY);
if (rnd == -1) {
perror("fopen on /dev/urandom");
#elif defined G_OS_WIN32
if (!CryptAcquireContext(&rnd, NULL, NULL, PROV_RSA_FULL, 0) &&
!CryptAcquireContext(&rnd, NULL, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET)) {
debug_print("Could not acquire a CSP handle.\n");
#endif
return;
}
#if defined G_OS_UNIX
ret = read(rnd, salt, KD_SALT_LENGTH);
if (ret != KD_SALT_LENGTH) {
perror("read into salt");
close(rnd);
#elif defined G_OS_WIN32
if (!CryptGenRandom(rnd, KD_SALT_LENGTH, salt)) {
debug_print("Could not read random data for salt\n");
CryptReleaseContext(rnd, 0);
#endif
return;
}
prefs_common_get_prefs()->master_passphrase_salt =
g_base64_encode(salt, KD_SALT_LENGTH);
}
#undef KD_SALT_LENGTH
static guchar *_make_key_deriv(const gchar *passphrase, guint rounds)
{
guchar *kd, *salt;
gchar *saltpref = prefs_common_get_prefs()->master_passphrase_salt;
gsize saltlen;
gint ret;
/* Grab our salt, generating and saving a new random one if needed. */
if (saltpref == NULL || strlen(saltpref) == 0) {
_generate_salt();
saltpref = prefs_common_get_prefs()->master_passphrase_salt;
}
salt = g_base64_decode(saltpref, &saltlen);
kd = g_malloc0(KD_LENGTH);
ret = pkcs5_pbkdf2(passphrase, strlen(passphrase), salt, saltlen,
kd, KD_LENGTH, rounds);
g_free(salt);
if (ret == 0) {
return kd;
}
g_free(kd);
return NULL;
}
static const gchar *master_passphrase()
{
gchar *input;
@ -88,8 +170,8 @@ static const gchar *master_passphrase()
const gboolean master_passphrase_is_set()
{
if (prefs_common_get_prefs()->master_passphrase_hash == NULL
|| strlen(prefs_common_get_prefs()->master_passphrase_hash) == 0)
if (prefs_common_get_prefs()->master_passphrase == NULL
|| strlen(prefs_common_get_prefs()->master_passphrase) == 0)
return FALSE;
return TRUE;
@ -97,40 +179,52 @@ const gboolean master_passphrase_is_set()
const gboolean master_passphrase_is_correct(const gchar *input)
{
gchar *hash;
guchar *kd, *input_kd;
gchar **tokens;
gchar *stored_hash = prefs_common_get_prefs()->master_passphrase_hash;
const GChecksumType hashtype = G_CHECKSUM_SHA256;
const gssize hashlen = g_checksum_type_get_length(hashtype);
gssize stored_len;
gchar *stored_kd = prefs_common_get_prefs()->master_passphrase;
gsize kd_len;
guint rounds = 0;
gint ret;
g_return_val_if_fail(stored_kd != NULL && strlen(stored_kd) > 0, FALSE);
g_return_val_if_fail(input != NULL, FALSE);
if (stored_hash == NULL)
if (stored_kd == NULL)
return FALSE;
tokens = g_strsplit_set(stored_hash, "{}", 3);
if (strlen(tokens[0]) != 0 ||
strcmp(tokens[1], "SHA-256") ||
strlen(tokens[2]) == 0) {
debug_print("Mangled master_passphrase_hash in config, can not use it.\n");
tokens = g_strsplit_set(stored_kd, "{}", 3);
if (tokens[0] == NULL ||
strlen(tokens[0]) != 0 || /* nothing before { */
tokens[1] == NULL ||
strncmp(tokens[1], "PBKDF2-HMAC-SHA1,", 17) || /* correct tag */
strlen(tokens[1]) <= 17 || /* something after , */
(rounds = atoi(tokens[1] + 17)) <= 0 || /* valid rounds # */
tokens[2] == NULL ||
strlen(tokens[2]) == 0) { /* string continues after } */
debug_print("Mangled master_passphrase format in config, can not use it.\n");
g_strfreev(tokens);
return FALSE;
}
stored_hash = tokens[2];
stored_len = strlen(stored_hash);
g_return_val_if_fail(stored_len == 2*hashlen, FALSE);
hash = g_compute_checksum_for_string(hashtype, input, -1);
if (!strncasecmp(hash, stored_hash, stored_len)) {
g_free(hash);
stored_kd = tokens[2];
kd = g_base64_decode(stored_kd, &kd_len); /* should be 64 */
g_strfreev(tokens);
if (kd_len != KD_LENGTH) {
debug_print("master_passphrase is %ld bytes long, should be %d.\n",
kd_len, KD_LENGTH);
g_free(kd);
return FALSE;
}
input_kd = _make_key_deriv(input, rounds);
ret = memcmp(kd, input_kd, kd_len);
g_free(input_kd);
g_free(kd);
if (ret == 0)
return TRUE;
}
g_strfreev(tokens);
g_free(hash);
return FALSE;
}
@ -153,8 +247,11 @@ void master_passphrase_forget()
void master_passphrase_change(const gchar *oldp, const gchar *newp)
{
const GChecksumType hashtype = G_CHECKSUM_SHA256;
gchar *hash;
guchar *kd;
gchar *base64_kd;
guint rounds = prefs_common_get_prefs()->master_passphrase_pbkdf2_rounds;
g_return_if_fail(rounds > 0);
if (oldp == NULL) {
/* If oldp is NULL, make sure the user has to enter the
@ -165,18 +262,20 @@ void master_passphrase_change(const gchar *oldp, const gchar *newp)
g_return_if_fail(oldp != NULL);
/* Update master passphrase hash in prefs */
if (prefs_common_get_prefs()->master_passphrase_hash != NULL)
g_free(prefs_common_get_prefs()->master_passphrase_hash);
if (prefs_common_get_prefs()->master_passphrase != NULL)
g_free(prefs_common_get_prefs()->master_passphrase);
if (newp != NULL) {
debug_print("Storing hash of new master passphrase\n");
hash = g_compute_checksum_for_string(hashtype, newp, -1);
prefs_common_get_prefs()->master_passphrase_hash =
g_strconcat("{SHA-256}", hash, NULL);
g_free(hash);
debug_print("Storing key derivation of new master passphrase\n");
kd = _make_key_deriv(newp, rounds);
base64_kd = g_base64_encode(kd, 64);
prefs_common_get_prefs()->master_passphrase =
g_strdup_printf("{PBKDF2-HMAC-SHA1,%d}%s", rounds, base64_kd);
g_free(kd);
g_free(base64_kd);
} else {
debug_print("Setting master_passphrase_hash to NULL\n");
prefs_common_get_prefs()->master_passphrase_hash = NULL;
debug_print("Setting master_passphrase to NULL\n");
prefs_common_get_prefs()->master_passphrase = NULL;
}
/* Now go over all accounts, reencrypting their passwords using

View file

@ -1191,7 +1191,9 @@ static PrefParam param[] = {
{"enable_avatars", "3", &prefs_common.enable_avatars, P_INT, NULL, NULL, NULL},
#ifndef PASSWORD_CRYPTO_OLD
{"use_master_passphrase", FALSE, &prefs_common.use_master_passphrase, P_BOOL, NULL, NULL, NULL },
{"master_passphrase_hash", "", &prefs_common.master_passphrase_hash, P_STRING, NULL, NULL, NULL },
{"master_passphrase", "", &prefs_common.master_passphrase, P_STRING, NULL, NULL, NULL },
{"master_passphrase_salt", "", &prefs_common.master_passphrase_salt, P_STRING, NULL, NULL, NULL },
{"master_passphrase_pbkdf2_rounds", "50000", &prefs_common.master_passphrase_pbkdf2_rounds, P_INT, NULL, NULL, NULL},
#endif
{NULL, NULL, NULL, P_OTHER, NULL, NULL, NULL}

View file

@ -541,7 +541,9 @@ struct _PrefsCommon
#ifndef PASSWORD_CRYPTO_OLD
gboolean use_master_passphrase;
gchar *master_passphrase_hash;
gchar *master_passphrase;
gchar *master_passphrase_salt;
guint master_passphrase_pbkdf2_rounds;
#endif
};