Default password hash

Gleb Kurtsou gleb.kurtsou at gmail.com
Mon Jun 11 17:57:01 UTC 2012


On (11/06/2012 12:43), Simon L. B. Nielsen wrote:
> On Sun, Jun 10, 2012 at 3:53 PM, Gleb Kurtsou <gleb.kurtsou at gmail.com> wrote:
[...]
> > Do you mean pkcs5v2_calculate from geli? It seems to have a drawback
> 
> Correct.
> 
> > that results produced depend on actual CPU load.
> 
> That's not the drawback, but the whole point (well, at least a point).
> While it can of course produce fewer rounds on different systems,
> especially on fast systems you will likely end up with a lot more
> rounds than whatever the default is.

I meant that pkcs5v2_calculate() should be constant or nearly constant
for a given system. It shouldn't depend on CPU load at the moment user
decides to change his/her password -- function will return fewer number
of rounds than it normally would. GELI use case is ok, normally one
won't shuffle encrypted hard disks between machines, which is often the
case with password database (the same password database accessed by all
machines in network/cluster, fast and dog slow).

> > % ./pkcs5v-test [*]
> > 541491
> > 539568
> > 542352
> > 540376
> > 388285 -- start several instances of pkcs5v-test in parallel
> > 303071
> > 284793
> > 281110
> >
> > It would be awesome to provide user with options to configure minimal
> > and maximal iteration count and randomly choose iteration count within
> > the range for each new password. Such trivial change should considerably
> > complicate mass password bruteforce cracking.
> 
> That's also an option. I'm not sure how well that would work in practice.
> 
> > Variable number of rounds for a password would also require changing
> > crypt() interface.
> 
> Exactly, so probably not actually a working solution for normal case,
> and possibly just not worth the trouble at all due to compatibility.

It turned out to be fairly easy to implement without nasty hacks. Please
find the patch attached. I believe patch to be correct. There is no
standard way to extract and pass salt from hashed password due to
differences in formats, so universal workaround for crypt() interface
limitations is the following:

check password:
strcmp(crypt(hashed_pass, realpw), realpw) == 0

set new password:
crypt_set_format("hash type")
hashed_new_pass = crypt(new_pass, random_salt);

Thanks,
Gleb.
-------------- next part --------------
commit d8b1a55a18365b805b3025496a91899df9def8f0
Author: Gleb Kurtsou <gleb.kurtsou at gmail.com>
Date:   Mon Jun 11 20:55:23 2012 +0300

    libcrypt: Use random number of rounds in crypt_sha* for new passwords
    
    By default randomly increment number of rounds by at most 10%.

diff --git a/lib/libcrypt/crypt-sha256.c b/lib/libcrypt/crypt-sha256.c
index cab7405..1e91648 100644
--- a/lib/libcrypt/crypt-sha256.c
+++ b/lib/libcrypt/crypt-sha256.c
@@ -58,6 +58,8 @@ static const char sha256_rounds_prefix[] = "rounds=";
 #define ROUNDS_MIN 1000
 /* Maximum number of rounds. */
 #define ROUNDS_MAX 999999999
+/* Random number of rounds increment, set 0 to disable. */
+#define ROUNDS_INCRANDOM (ROUNDS_DEFAULT / 10)
 
 static char *
 crypt_sha256_r(const char *key, const char *salt, char *buffer, int buflen)
@@ -69,7 +71,7 @@ crypt_sha256_r(const char *key, const char *salt, char *buffer, int buflen)
 	size_t salt_len, key_len, cnt, rounds;
 	char *cp, *copied_key, *copied_salt, *p_bytes, *s_bytes, *endp;
 	const char *num;
-	bool rounds_custom;
+	bool rounds_custom, has_salt_prefix;
 
 	copied_key = NULL;
 	copied_salt = NULL;
@@ -77,12 +79,16 @@ crypt_sha256_r(const char *key, const char *salt, char *buffer, int buflen)
 	/* Default number of rounds. */
 	rounds = ROUNDS_DEFAULT;
 	rounds_custom = false;
+	has_salt_prefix = false;
 
 	/* Find beginning of salt string. The prefix should normally always
 	 * be present. Just in case it is not. */
-	if (strncmp(sha256_salt_prefix, salt, sizeof(sha256_salt_prefix) - 1) == 0)
+	if (strncmp(sha256_salt_prefix, salt, sizeof(sha256_salt_prefix) - 1)
+	    == 0) {
 		/* Skip salt prefix. */
 		salt += sizeof(sha256_salt_prefix) - 1;
+		has_salt_prefix = true;
+	}
 
 	if (strncmp(salt, sha256_rounds_prefix, sizeof(sha256_rounds_prefix) - 1)
 	    == 0) {
@@ -94,6 +100,9 @@ crypt_sha256_r(const char *key, const char *salt, char *buffer, int buflen)
 			rounds = MAX(ROUNDS_MIN, MIN(srounds, ROUNDS_MAX));
 			rounds_custom = true;
 		}
+	} else if (!has_salt_prefix && ROUNDS_INCRANDOM != 0) {
+			rounds += arc4random() % ROUNDS_INCRANDOM;
+			rounds_custom = true;
 	}
 
 	salt_len = MIN(strcspn(salt, "$"), SALT_LEN_MAX);
diff --git a/lib/libcrypt/crypt-sha512.c b/lib/libcrypt/crypt-sha512.c
index 8e0054f..8cc710b 100644
--- a/lib/libcrypt/crypt-sha512.c
+++ b/lib/libcrypt/crypt-sha512.c
@@ -58,6 +58,8 @@ static const char sha512_rounds_prefix[] = "rounds=";
 #define ROUNDS_MIN 1000
 /* Maximum number of rounds. */
 #define ROUNDS_MAX 999999999
+/* Random number of rounds increment, set 0 to disable. */
+#define ROUNDS_INCRANDOM (ROUNDS_DEFAULT / 10)
 
 static char *
 crypt_sha512_r(const char *key, const char *salt, char *buffer, int buflen)
@@ -69,7 +71,7 @@ crypt_sha512_r(const char *key, const char *salt, char *buffer, int buflen)
 	size_t salt_len, key_len, cnt, rounds;
 	char *cp, *copied_key, *copied_salt, *p_bytes, *s_bytes, *endp;
 	const char *num;
-	bool rounds_custom;
+	bool rounds_custom, has_salt_prefix;
 
 	copied_key = NULL;
 	copied_salt = NULL;
@@ -80,9 +82,12 @@ crypt_sha512_r(const char *key, const char *salt, char *buffer, int buflen)
 
 	/* Find beginning of salt string. The prefix should normally always
 	 * be present. Just in case it is not. */
-	if (strncmp(sha512_salt_prefix, salt, sizeof(sha512_salt_prefix) - 1) == 0)
+	if (strncmp(sha512_salt_prefix, salt, sizeof(sha512_salt_prefix) - 1)
+	    == 0) {
 		/* Skip salt prefix. */
 		salt += sizeof(sha512_salt_prefix) - 1;
+		has_salt_prefix = true;
+	}
 
 	if (strncmp(salt, sha512_rounds_prefix, sizeof(sha512_rounds_prefix) - 1)
 	    == 0) {
@@ -94,6 +99,9 @@ crypt_sha512_r(const char *key, const char *salt, char *buffer, int buflen)
 			rounds = MAX(ROUNDS_MIN, MIN(srounds, ROUNDS_MAX));
 			rounds_custom = true;
 		}
+	} else if (!has_salt_prefix && ROUNDS_INCRANDOM != 0) {
+			rounds += arc4random() % ROUNDS_INCRANDOM;
+			rounds_custom = true;
 	}
 
 	salt_len = MIN(strcspn(salt, "$"), SALT_LEN_MAX);


More information about the freebsd-security mailing list