Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 30 Sep 2013 17:44:33 GMT
From:      "A.J. Kehoe IV (Nanoman)" <n6gXhrf6@nanoman.ca>
To:        freebsd-gnats-submit@FreeBSD.org
Subject:   misc/182518: Better Password Hashes
Message-ID:  <201309301744.r8UHiXns081442@oldred.freebsd.org>
Resent-Message-ID: <201309301750.r8UHo0np048714@freefall.freebsd.org>

next in thread | raw e-mail | index | archive | help

>Number:         182518
>Category:       misc
>Synopsis:       Better Password Hashes
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          change-request
>Submitter-Id:   current-users
>Arrival-Date:   Mon Sep 30 17:50:00 UTC 2013
>Closed-Date:
>Last-Modified:
>Originator:     A.J. Kehoe IV (Nanoman)
>Release:        10.0-CURRENT
>Organization:
Nanoman's Company
>Environment:
FreeBSD machine 10.0-CURRENT FreeBSD 10.0-CURRENT #0 r255473: Wed Sep 11 16:27:22 EDT 2013     root@machine:/usr/obj/usr/src/sys/GENERIC-PROD  amd64
>Description:
My friend and fellow FreeBSD enthusiast Derek Marcotte recently pointed out that FreeBSD has no easy way to set the logarithmic rounds for bcrypt password hashes.  Doing so is trivial in OpenBSD, and considering the capabilities of current GPU attacks, I want this functionality.

This issue was raised over eight years ago in kern/75934 by Steven Alexander Jr., who included a patch to add this feature.  Unfortunately, this seems to have been completely overlooked, and there were no public responses to this PR.

I commissioned Derek to come up with a solution by either updating Steven's patch or by devising a new method.  To paraphrase Derek's comments:

-----BEGIN PARAPHRASIS-----
I did some research into what other *BSDs are doing.  OpenBSD and NetBSD use the algorithm name, a comma, and then the number of rounds:

http://www.openbsd.org/cgi-bin/man.cgi?query=login.conf&sektion=5

localcipher=blowfish,6

http://netbsd.gw.com/cgi-bin/man-cgi?passwd.conf+5+NetBSD-current

localcipher=blowfish,6

To me, this isn't a good way to do it because we'd need special rules to parse this extra field out of the previously unstructured data.  This parsing would be algorithm dependent.

Everyone knows about modular crypt, so why not feed the modular crypt salt string as the parameter directly?  Instead of messing with different names, give the power to the system admin to control this directly, so when crypt is updated, pam_unix can take advantage.  Each implementation of crypt algorithms already includes parsing of the salt magic.

I found that patching pam_unix was the least invasive way to handle configurable hashes for login.  I've added a passwd_modular parameter that will supersede passwd_format when defined.  passwd_modular will feed directly into crypt, so any options that are passed to crypt via the salt are immediately available for use in the master.passwd file.  For example:

:passwd_modular=$2a$11$:\

Now you can also set the rounds for sha512:

:passwd_modular=$6$rounds=1000000$:\

To disable passwd_modular and revert to passwd_format:

:passwd_modular=disabled:\

This also lets admins shoot themselves in the foot by supplying invalid or bad salts.  For example:

:passwd_modular=$1$constantsalt:\

I had considered setting a second variable like ":passwd_param=8:\", but then you really have to mess with crypt to make it work.  I think it would be a much more invasive change, and unnecessary, providing the documentation for login.conf is brought up to date.

FreeBSD 8.* doesn't have access to the SHA family of hashes.  If this is merged back into 8, it will give much stronger password security when using $2a$08$ (or higher) than is currently available.

bcrypt is preferable to sha512 because of its resilience to current GPU attacks.  This is expected to change.  Hopefully, my patch will lay some groundwork to incorporate scrypt.
-----END PARAPHRASIS-----
>How-To-Repeat:

>Fix:
I've attached a copy of Derek's patches for FreeBSD 10.0-CURRENT.  I also have his patches for 9-STABLE, but have not included them here.

I really like Derek's solution.  In my opinion, committing Derek's patches will allow kern/75934 to be closed.

Patch attached with submission follows:

# This is a shell archive.  Save it in a file, remove anything before
# this line, and then unpack it by entering "sh file".  Note, it may
# create directories; files and directories will be owned by you and
# have default permissions.
#
# This archive contains:
#
#	login.conf.5.patch
#	login.conf.patch
#	pam_unix.c.patch
#
echo x - login.conf.5.patch
sed 's/^X//' >login.conf.5.patch << 'd004b1f1103394d3b7210e3d0b27e4cf'
X--- /usr/src/lib/libutil/login.conf.5.orig	2013-09-30 10:15:58.000000000 -0400
X+++ /usr/src/lib/libutil/login.conf.5	2013-09-30 10:16:20.000000000 -0400
X@@ -275,7 +275,15 @@
X NIS clients using a
X .No non- Ns Fx
X NIS server should probably use "des".
X-.It "passwd_prompt	string		The password prompt presented by"
X+.It "passwd_modular	string	$02$08$	The encryption format that new or"
X+changed passwords will use, based on the 
X+.Xr crypt 3 
X+magic constants.  Overrides passwd_format when set. Valid values include "disabled" to fall back to passwd_format, $02$08$ would be blf with work factor 8, or $6$rounds=5000$ would be sha512 with 5000 rounds, will accept any of the magic salt values from
X+.Xr crypt 3
X+Be aware that setting this to an invalid crypt magic will likely fall back to des. Appending text to after the salt magic, (e.g. $02$08$dontdothis) will weaken the salt.  Please refer to
X+.Xr crypt 3
X+for proper syntax and useage.
X+.It "passwd_prompt	string		The password prompt presented by
X .Xr login 1
X .It "times.allow 	list		List of time periods during which"
X logins are allowed.
d004b1f1103394d3b7210e3d0b27e4cf
echo x - login.conf.patch
sed 's/^X//' >login.conf.patch << '91dbd97499532c598f33fd04820120bd'
X--- /etc/login.conf.orig	2013-09-30 10:18:16.000000000 -0400
X+++ /etc/login.conf	2013-09-30 10:18:38.000000000 -0400
X@@ -24,6 +24,7 @@
X 
X default:\
X 	:passwd_format=sha512:\
X+	:passwd_modular=$2a$08$:\
X 	:copyright=/etc/COPYRIGHT:\
X 	:welcome=/etc/motd:\
X 	:setenv=MAIL=/var/mail/$,BLOCKSIZE=K:\
91dbd97499532c598f33fd04820120bd
echo x - pam_unix.c.patch
sed 's/^X//' >pam_unix.c.patch << '9833db6189445b1dfe20e210ed2256ff'
X--- /usr/src/lib/libpam/modules/pam_unix/pam_unix.c.orig	2013-09-30 10:16:06.000000000 -0400
X+++ /usr/src/lib/libpam/modules/pam_unix/pam_unix.c	2013-09-30 10:16:35.000000000 -0400
X@@ -68,8 +68,9 @@
X #include <security/pam_mod_misc.h>
X 
X #define PASSWORD_HASH		"md5"
X+#define NOMODULAR		"disabled"
X #define DEFAULT_WARN		(2L * 7L * 86400L)  /* Two weeks */
X-#define	SALTSIZE		32
X+#define	SALTSIZE		64
X 
X #define	LOCKED_PREFIX		"*LOCKED*"
X #define	LOCKED_PREFIX_LEN	(sizeof(LOCKED_PREFIX) - 1)
X@@ -77,6 +78,7 @@
X static void makesalt(char []);
X 
X static char password_hash[] =		PASSWORD_HASH;
X+static char password_nomodular[] =	NOMODULAR;
X 
X #define PAM_OPT_LOCAL_PASS	"local_pass"
X #define PAM_OPT_NIS_PASS	"nis_pass"
X@@ -272,7 +274,7 @@
X 	char salt[SALTSIZE + 1];
X 	login_cap_t *lc;
X 	struct passwd *pwd, *old_pwd;
X-	const char *user, *old_pass, *new_pass;
X+	const char *user, *old_pass, *new_pass, *modular_salt;
X 	char *encrypted;
X 	time_t passwordtime;
X 	int pfd, tfd, retval;
X@@ -378,9 +380,16 @@
X 			return (PAM_BUF_ERR);
X 
X 		lc = login_getclass(pwd->pw_class);
X+
X+		memset(salt, 0, sizeof(salt));
X+		modular_salt = login_getcapstr(lc, "passwd_modular", password_nomodular, NULL);
X+		if (strcmp(modular_salt, password_nomodular) == 0) {
X 		if (login_setcryptfmt(lc, password_hash, NULL) == NULL)
X 			openpam_log(PAM_LOG_ERROR,
X 			    "can't set password cipher, relying on default");
X+		} else {
X+			strncpy(salt, modular_salt, sizeof(salt) - 1);
X+		}
X 		
X 		/* set password expiry date */
X 		pwd->pw_change = 0;
X@@ -464,13 +473,25 @@
X makesalt(char salt[SALTSIZE + 1])
X {
X 	int i;
X+	int remainder;
X+
X+	/* If a salt magic has already been set, skip to the free area */
X+	for (i = 0; i < SALTSIZE; i++) {
X+		if (salt[i] == '\0') {
X+			break;
X+		}
X+	}
X 
X 	/* These are not really random numbers, they are just
X 	 * numbers that change to thwart construction of a
X 	 * dictionary.
X 	 */
X-	for (i = 0; i < SALTSIZE; i += 4)
X-		to64(&salt[i], arc4random(), 4);
X+	while (i < SALTSIZE) {
X+		remainder = SALTSIZE - i;
X+		to64(&salt[i], arc4random(), (remainder < 4 ? remainder : 4) );
X+		i += 4;
X+	}
X+
X 	salt[SALTSIZE] = '\0';
X }
X 
9833db6189445b1dfe20e210ed2256ff
exit



>Release-Note:
>Audit-Trail:
>Unformatted:



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201309301744.r8UHiXns081442>