The blowfish crypt(3) mechanism supports the use of a "cost value" for password encryption. The cost value is encoded into the encrypted password that is stored in master.passwd. On OpenBSD, this cost value can be set in login.conf. FreeBSD does not currently support the cost value. The cost value is the base-2 logarithm of the number of rounds of encryption to use so rounds=1<<cost; This functionality can be supported through modifications to /usr/bin/passwd (which actually means a change to PAM) or through modifications to libcrypt.
In order to patch passwd, it must be modified to provide a specially formatted salt value for the encryption of new passwords. Specifically, $2a$COST$ must be prepended to the generated salt value. "2a" is the major and minor version for blowfish/bcrypt.
Since passwd should not have to keep up with any formatting requirements for any libcrypt mechanism, I modified libcrypt instead.
The diff is pasted below strictly for viewing, the uuencoded version is included. In libcrypt, I use getpwuid_r(getuid(), ...) to get a pwd structure for the current user. Then, I use login_getpwclass() to return a login_cap_t structure and use login_getcapnum(...,"ln_rounds",...) to grab the value for ln_rounds in login.conf. The only drawback to this approach is that it grabs the entry for the current user rather than the user whose password is being changed. Normally, root will have a higher cost value than normal users. If root changes a user's password, the password will be encrypted with a higher cost than if the user changed it themselves. This doesn't seem to be all that bad.
To support this patch, /etc/login.conf must include an entry of the form ":ln_rounds=10:" and cap_mkdb must be run on /etc/login.conf to apply the change. This is slightly different than the way this feature is turned on in OpenBSD.
diff -c ./secure/lib/libcrypt-new/crypt-blowfish.c ./secure/lib/libcrypt/crypt-blowfish.c
*** ./secure/lib/libcrypt-new/crypt-blowfish.c Fri Jan 7 19:43:31 2005
--- ./secure/lib/libcrypt/crypt-blowfish.c Mon Jun 2 12:17:24 2003
*** 55,63 ****
- #include <libutil.h>
- #include <login_cap.h>
--- 55,60 ----
*** 147,157 ****
static const char *magic = "$2a$04$";
- struct passwd pw, *pwd;
- char pwbuf;
- login_cap_t *lc;
/* Defaults */
minr = 'a';
--- 144,149 ----
*** 201,238 ****
/* Discard num rounds + "$" identifier */
salt += 3;
- /* We're crypting a new password. We want to get the
- ln_rounds value that is stored in login.conf
- and use it to initialize the rounds value.
- ln_rounds is the base r2 logarithm of the
- desired rounds value. */
- if(getpwuid_r(getuid(), &pw, pwbuf, sizeof(pwbuf), &pwd) == 0)
- if( (lc = login_getpwclass(pwd)) != NULL)
- logr = (int)login_getcapnum(lc, "ln_rounds", logr, logr);
- rounds = 1 << logr;
- if(rounds < BCRYPT_MINROUNDS)
- printf("ln_rounds in login.conf is too small\n");
- return error;
- printf("could not look up capability\n");
- return error;
- printf("Could not look up current user %d\n", getuid());
- return error;
--- 193,198 ----
How-To-Repeat: Observe all blowfish encrypted passwords begin with $2a$04$. Blowfish can be turned on by setting ":passwd_format=blf:" in login.conf and running "cap_mkdb /etc/login.conf".