What this does
It makes the password file that Apache and nginx use for basic
authentication: one user:hash line per user. You type a username and a
password, or generate a strong one, pick a format, and add users one at a
time; then you download the file or copy it. It also checks an existing
line against a password, which is the quickest way to find out why a login
is failing.
The hashing happens in your browser, so the passwords never leave this page. The file holds only hashes.
How to use it
- Enter a username, and a password or press “Generate”. Generated passwords are 24 letters and digits with look-alike characters left out, which is about 140 bits of randomness and safe to put in a URL or a command line.
- Pick a format. bcrypt is the right choice unless your server is old; see below.
- Press “Add to file”, and repeat for each user. Adding a name that is already there replaces its line.
- Copy or download the file, and keep a note of each password somewhere safe. The file cannot give them back.
Using the file with Apache
Save the file outside the web root, for example as /etc/apache2/.htpasswd,
readable by the user Apache runs as. Then protect a directory, either in its
<Directory> block or in an .htaccess file inside it:
AuthType Basic
AuthName "Restricted"
AuthUserFile /etc/apache2/.htpasswd
Require valid-user
An .htaccess file only takes effect if the server’s configuration allows
it for that directory with AllowOverride AuthConfig (or All).
Using the file with nginx
Save the file as, for example, /etc/nginx/.htpasswd, readable by nginx’s
worker user, and add two lines to the location (or server) you want to
protect:
location /admin/ {
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
}
Then check the configuration and reload: sudo nginx -t && sudo systemctl reload nginx.
Which format to choose
| Format | Apache 2.4 | nginx | Strength |
|---|---|---|---|
bcrypt ($2y$) |
Any system | Where the system crypt() has bcrypt: current Linux, Alpine | Strong |
SHA-512 crypt ($6$) |
Linux and other Unix, not Windows | Any Linux | Strong |
Apache MD5 ($apr1$) |
Any system | Any system | Weak if the file leaks |
Apache implements bcrypt and Apache MD5 itself, so those work wherever
Apache runs, while SHA-512 comes from the operating system’s crypt(), which
Windows lacks. nginx implements Apache MD5 and the {SHA}, {SSHA} and
{PLAIN} formats itself, and hands crypt-style hashes to the system’s
crypt(), so what it accepts depends on the system: bcrypt needs a crypt()
that includes it, and SHA-512 is in every Linux one.
Two older formats both servers still read are deliberately not offered: traditional DES crypt, which uses only the first eight characters of a password, and unsalted SHA-1, which falls to precomputed tables.
Why the default cost is 8
bcrypt is slow on purpose, and its cost setting doubles the work with each step. For a login form that runs once per sign-in, a high cost is cheap. Basic auth is different: the browser sends the password with every request, and the server checks it every time, for the page and for each image, stylesheet and script on it. These are the times per check, measured through libxcrypt’s crypt(), the function nginx calls, on an Intel Core i7-10510U laptop processor:
| Format | Time per request |
|---|---|
| bcrypt cost 5 (Apache’s own default) | 1.4 ms |
| bcrypt cost 8 (this tool’s default) | 10.6 ms |
| bcrypt cost 10 | 42 ms |
| bcrypt cost 12 | 170 ms |
| bcrypt cost 14 | 706 ms |
| SHA-512 crypt, 5,000 rounds | 2.9 ms |
| MD5 crypt | 0.2 ms |
At cost 12, a page with thirty images spends five seconds of server time on passwords alone. Cost 8 is eight times Apache’s default and still about a hundredth of a second a request. A server’s own figures scale with its processor, but every step of cost still doubles them.
The cost protects passwords if the file is stolen, by slowing down guessing. A long generated password is beyond guessing at any cost, which is why the tool makes generating one a single click.
Checking a line
Paste a line from a file, or just the hash, with the password you expect,
and press “Check”. It recognises every format Apache and nginx read, says
whether the password matches, and says what the format means for each
server. It checks bcrypt, SHA-512 and SHA-256 crypt, Apache MD5, MD5 crypt,
{SHA}, {SSHA} and {PLAIN}. It names, but does not check, DES crypt and
yescrypt ($y$, the format many Linux systems now use for their own login
passwords).
Every format is tested against independent implementations: the specification’s own test vectors for SHA-crypt, OpenSSL for Apache MD5, MD5 crypt and SHA-crypt, and Python and PHP’s bcrypt and the system crypt() library for bcrypt, including passwords with accented letters and emoji.
Limitations
bcrypt reads only the first 72 bytes of a password. Most servers ignore the rest without saying so; this tool refuses a longer password for bcrypt instead, and suggests SHA-512.
Characters beyond plain ASCII are hashed as UTF-8. A client that sends another encoding will not get in, so letters and digits are the safe choice, and they are what “Generate” uses.
It makes the file; it does not install it. Putting the file on the server and pointing the configuration at it is up to you, as above.