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 dependant.

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-----

I've attached a copy of Derek's patches for the FreeBSD 9-STABLE versions of 
pam_unix and the login.conf man page.  These may have to be adjusted for HEAD.

I really like Derek's solution.  It's working perfectly for bcrypt on my own 
network, and I'm planning to distribute it to the hundreds of FreeBSD servers 
that I am responsible for maintaining.  In my opinion, committing Derek's 
patches will allow kern/75934 to be closed.

--
A.J. Kehoe IV (Nanoman)     |  /"\  ASCII Ribbon Campaign
Nanoman's Company           |  \ /   - No HTML/RTF in E-mail
E-mail: [email protected]  |   X    - No proprietary attachments
WWW: http://www.nanoman.ca/ |  / \   - Respect for open standards
--- /usr/src/lib/libpam/modules/pam_unix/pam_unix.c.orig	2013-07-07 10:20:46.000000000 -0400
+++ /usr/src/lib/libpam/modules/pam_unix/pam_unix.c	2013-07-07 12:53:28.000000000 -0400
@@ -68,8 +68,9 @@
 #include <security/pam_mod_misc.h>
 
 #define PASSWORD_HASH		"md5"
+#define NOMODULAR		"disabled"
 #define DEFAULT_WARN		(2L * 7L * 86400L)  /* Two weeks */
-#define	SALTSIZE		32
+#define	SALTSIZE		64
 
 #define	LOCKED_PREFIX		"*LOCKED*"
 #define	LOCKED_PREFIX_LEN	(sizeof(LOCKED_PREFIX) - 1)
@@ -77,6 +78,7 @@
 static void makesalt(char []);
 
 static char password_hash[] =		PASSWORD_HASH;
+static char password_nomodular[] =	NOMODULAR;
 
 #define PAM_OPT_LOCAL_PASS	"local_pass"
 #define PAM_OPT_NIS_PASS	"nis_pass"
@@ -272,7 +274,7 @@
 	char salt[SALTSIZE + 1];
 	login_cap_t *lc;
 	struct passwd *pwd, *old_pwd;
-	const char *user, *old_pass, *new_pass;
+	const char *user, *old_pass, *new_pass, *modular_salt;
 	char *encrypted;
 	time_t passwordtime;
 	int pfd, tfd, retval;
@@ -378,16 +380,23 @@
 			return (PAM_BUF_ERR);
 
 		lc = login_getclass(pwd->pw_class);
-		if (login_setcryptfmt(lc, password_hash, NULL) == NULL)
-			openpam_log(PAM_LOG_ERROR,
-			    "can't set password cipher, relying on default");
-		
+
+		memset(salt, 0, sizeof(salt));
+		modular_salt = login_getcapstr(lc, "passwd_modular", password_nomodular, NULL);
+		if (strcmp(modular_salt, password_nomodular) == 0) {
+			if (login_setcryptfmt(lc, password_hash, NULL) == NULL)
+				openpam_log(PAM_LOG_ERROR,
+				    "can't set password cipher, relying on default");
+		} else {
+			strncpy(salt, modular_salt, sizeof(salt) - 1);
+		}
+
 		/* set password expiry date */
 		pwd->pw_change = 0;
 		passwordtime = login_getcaptime(lc, "passwordtime", 0, 0);
 		if (passwordtime > 0)
 			pwd->pw_change = time(NULL) + passwordtime;
-		
+
 		login_close(lc);
 		makesalt(salt);
 		pwd->pw_passwd = crypt(new_pass, salt);
@@ -464,13 +473,25 @@
 makesalt(char salt[SALTSIZE])
 {
 	int i;
+	int remainder;
+
+	/* If a salt magic has already been set, skip to the free area */
+	for (i = 0; i < SALTSIZE; i++) {
+		if (salt[i] == '\0') {
+			break;
+		}
+	}
 
 	/* These are not really random numbers, they are just
 	 * numbers that change to thwart construction of a
 	 * dictionary. This is exposed to the public.
 	 */
-	for (i = 0; i < SALTSIZE; i += 4)
-		to64(&salt[i], arc4random(), 4);
+	while (i < SALTSIZE) {
+		remainder = SALTSIZE - i;
+		to64(&salt[i], arc4random(), (remainder < 4 ? remainder : 4) );
+		i += 4;
+	}
+
 	salt[SALTSIZE] = '\0';
 }
 
--- /usr/src/lib/libutil/login.conf.5.orig	2013-07-04 10:16:42.000000000 -0400
+++ /usr/src/lib/libutil/login.conf.5	2013-07-04 10:31:59.000000000 -0400
@@ -275,6 +275,14 @@
 NIS clients using a
 .No non- Ns Fx
 NIS server should probably use "des".
+.It "passwd_modular	string	$02$08$	The encryption format that new or"
+changed passwords will use, based on the 
+.Xr crypt 3 
+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
+.Xr crypt 3
+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
+.Xr crypt 3
+for proper syntax and useage.
 .It "passwd_prompt	string		The password prompt presented by
 .Xr login 1
 .It "times.allow 	list		List of time periods during which

Attachment: smime.p7s
Description: S/MIME cryptographic signature

Reply via email to