Working Around Lack of htpasswd
28 Jun 2016∞
Do you run an nginx-based web server? Do you use the http_auth_basic_module
module? This post might be for you!
I just set up a new FreeBSD server (blog post forthcoming) and decided to skip the install of httpd, preferring nginx. All the cool kids are using it, so I figured I should as well. One problem: no htpasswd
command.
There are several webapps I’ve written over the years for my own personal uses, and they all use HTTP Basic authentication to regulate access. nginx happily supports this, if you have an existing htpasswd-formatted file to use. Unfortunately, you cannot create one since it does not supply a tool to do so.
On FreeBSD I found a simple enough workaround:
$ echo YOUR_PASSWORD | \
pw useradd YOUR_USERNAME -h 0 -N | \
awk -F: '{print $1"$"$2}' | \
awk -F'$' '{print $1":$6$rounds=5000$"$4"$"$5;}'
Thankfully FreeBSD’s (and perhaps your distro’s) crypt(3)
supports strong password hashes, the default being SHA512 with 5,000 rounds. When you supply the -N
flag to the pw
command, it will not actually perform the action, but instead print out the result (incluiing the hashed password, which is what we’re after).
SHA512 with 5,000 rounds (and a random, 32-character salt) is probably more security than anyone actually needs. If we want more than that, though, we have options — other than installing httpd and gaining htpasswd
. I wrote a super-simple PHP script to generate a bcrypt hash with selectable cost (2^x rounds):
bcrypt_hash.php [user]
#!/usr/local/bin/php
<?php
// 10 is reasonable, but time the output of this script to test what is reasonable for you
$cost = 10;
// write prompt to stderr so we can redirect output with > and only include what we want
$stderr = fopen('php://stderr', 'w+');
fwrite($stderr, 'Enter password: ');
// disable echoing the password to terminal
system('stty -echo');
$passwd = stream_get_line(STDIN, 1024, PHP_EOL);
system('stty echo');
fwrite($stderr, "\n");
// at least on FreeBSD you need to replace PHP's bcrypt hash version 2y with OpenBSD's 2b
// Fixed in secure/lib/libcrypt/crypt-blowfish.c revision 284483
// https://svnweb.freebsd.org/base?view=revision&revision=284483
$hashed = str_replace('$2y$', '$2b$', password_hash($passwd, PASSWORD_BCRYPT, array('cost' => $cost)));
echo ( (isset($argv[1])) ? $argv[1] : "user" ) . ":$hashed\n";
?>
FreeBSD’s pw
will generate a bcrypt-based hash if you edit /etc/login.conf
and add
blf_users:\
:passwd_format=blf:\
:tc=default:
Then rebuild the mapping with cap_mkdb /etc/login.conf
and add -L blf_users
before the -h
flag in the aforementioned command. Sadly pw
will use the default cost value of 4, which seems pointless to me. Looking over /usr/src/usr.sbin/pw/pw_user.c
the pw_pwcrypt()
function does not support changing the random salt that is generated to a format specifying the cost value — better to use the above PHP script.