misc/182518: Better Password Hashes

A.J. Kehoe IV (Nanoman) n6gXhrf6 at nanoman.ca
Mon Sep 30 17:50:01 UTC 2013

>Number:         182518
>Category:       misc
>Synopsis:       Better Password Hashes
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    freebsd-bugs
>State:          open
>Class:          change-request
>Submitter-Id:   current-users
>Arrival-Date:   Mon Sep 30 17:50:00 UTC 2013
>Originator:     A.J. Kehoe IV (Nanoman)
>Release:        10.0-CURRENT
Nanoman's Company
FreeBSD machine 10.0-CURRENT FreeBSD 10.0-CURRENT #0 r255473: Wed Sep 11 16:27:22 EDT 2013     root at machine:/usr/obj/usr/src/sys/GENERIC-PROD  amd64
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:

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:





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:


Now you can also set the rounds for sha512:


To disable passwd_modular and revert to passwd_format:


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


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.

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.
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 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:\
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 #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@@ -77,6 +78,7 @@
X static void makesalt(char []);
X static char password_hash[] =		PASSWORD_HASH;
X+static char password_nomodular[] =	NOMODULAR;
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 		lc = login_getclass(pwd->pw_class);
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 		/* 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+	/* 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 	/* 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 	salt[SALTSIZE] = '\0';
X }


More information about the freebsd-bugs mailing list