In 2017 the National Institute of Standards and Technology (NIST) released a new set of recommendations for handling passwords in software applications (SP 800-63 Digital Identity Guidelines).
There are some significant changes from the previous guidelines from 2003. The NIST now recommends that an application no longer enforces periodic password changes and should no longer enforce complexity requirements like "password must contain at least one upper case character, at least one number and at least one special character".
Instead, the guidelines recommend that applications should encourage users to create memorizable passwords as long (at least 8 characters) as they want with any characters. One other recommendation is that applications should check the passwords against a list of passwords known to be commonly used, expected, or compromised and should prevent the users from using such passwords.
In this blog post, we're going to look at a few examples that show how you could implement this recommendation in a web application.
zxcvbn ¶
zxcvbn is a JavaScript library developed and maintained by Dropbox.
zxcvbn is a password strength estimator. The reason why I present this library here is that the library contains a list of 30'000 commonly used passwords. According to the GitHub project page, this list contains common names and surnames according to US census data, popular English words from Wikipedia, and US television and movies.
You add the library in npm
managed projects with
npm install zxcvbn
and then import it with
import zxcvbn from 'zxcvbn';
You can then check a password
const result = zxcvbn(password);
The function expects one mandatory parameter the password in plain text. The function supports a second optional parameter an array of strings that
zxcvbn
treats as extra blacklist dictionary.
const result = zxcvbn(password, ['foo', 'bar']);
The result object contains several properties about the guessability of the password. For instance
result.guesses
returns the estimated guesses needed to crack the password. Other properties give an estimation about how long in seconds it takes to crack
this password. Visit the project page to find a description of all the result properties.
This example application is only interested in the result.score
property, which contains a number between 0 and 4:
- 0: too guessable: risky password
- 1: very guessable: protection from throttled online attacks
- 2: somewhat guessable: protection from unthrottled online attacks
- 3: safely unguessable: moderate protection from offline slow-hash scenario
- 4: very unguessable: strong protection from offline slow-hash scenario
This number is used for implementing a password strength meter underneath the text field.
The JavaScript and CSS code you see in this example is from the "Password Strength meter
" post from the css-tricks blog. The blog post describes how to use a <meter>
tag as a password strength meter.
const password = document.getElementById('password');
const meter = document.getElementById('password-strength-meter');
const text = document.getElementById('password-strength-text');
password.addEventListener('input', () => {
const val = password.value;
const result = zxcvbn(val);
meter.value = result.score;
if (val !== "") {
text.innerHTML = "Strength: " + "<strong>" + strength[result.score] + "</strong>" + "<span class='feedback'>" + result.feedback.warning + " " + result.feedback.suggestions + "</span";
}
else {
text.innerHTML = "";
}
});
The zxcvbn JavaScript library was ported to different programming languages. Check the project page to see a list of all available ports. For Java two libraries nbvcxz and zxcvbn4j are available.
hibp ¶
hibp is a JavaScript client library for the Have I been pwned? service.
In an npm
managed project you add the library with
npm install hibp
The library supports all available Have I been pwned? APIs: https://haveibeenpwned.com/API/v2
In this example I'm only interested in the pwnedPassword
method.
import { pwnedPassword } from 'hibp';
This method calls the 'search password by range' service and returns how many times a password has been exposed in a breach.
The method expects as argument the password in plain text, it runs asynchronous and returns a Promise.
async function checkHibp() {
try {
const numPwns = await pwnedPassword(password_hibp.value);
if (numPwns > 0) {
output.innerHTML = `Password found ${numPwns} of times in the haveibeenpwned.com database`;
} else {
output.innerHTML = `Password not found in the haveibeenpwned.com database`;
}
} catch (err) {
output.innerHTML = err;
}
}
The method does not send the password in plain text to the Have I been pwned? server. Instead, it first calculates the SHA-1 hash of the plain text password locally and then sends the first 5 characters of the hash to the service. Have I been pwned? returns a list of all the hashes that start with these 5 characters. The pwnedPassword
function then checks the list if it contains our password.
The method returns either 0 if the password was not found in the Have I been pwned? database or a number greater than 0. This number represents the number of times this password was exposed in a breach.
passpol ¶
passpol is a Java library that checks the minimum and maximum length and if the password is listed in a blacklist.
In a Maven manged project, you add the library with this dependency.
<dependency>
<groupId>com.codahale</groupId>
<artifactId>passpol</artifactId>
<version>0.7.0</version>
</dependency>
Usage is very simple. You create a new instance of the PasswordPolicy
class and specify as first parameter the blacklist the library should check.
The second and third parameter specifies the required minimum and maximum length of the password.
passpol supports two blacklists. A check against the Have I been pwned? database. This sends a request to the 'search password by range' service. Like hibp, this library only sends the first 5 characters of the SHA-1 hash to the Have I been pwned? server.
PasswordPolicy policy = new PasswordPolicy(BreachDatabase.haveIBeenPwned(5), 8, 64);
The second option is to use the built-in list of 100,000 passwords from Carey Li's NBP project. This text file is part of the library and works offline without sending any information to another server.
PasswordPolicy policy = new PasswordPolicy(BreachDatabase.top100K(), 8, 64);
After creating the PasswordPolicy
instance, you can check a password with the check
method.
Status result = policy.check(password);
The method expects as parameter the password in plain text and returns a Status
enumeration
- OK: The password is acceptable
- TOO_SHORT: The password is too short
- TOO_LONG: The password is too long
- BREACHED: The password has previously appeared in a data breach
For this example, I created a simple Spring Boot RestController
@RestController
@CrossOrigin
public class PasspolController {
private final PasswordPolicy policy;
public PasspolController() {
this.policy = new PasswordPolicy(BreachDatabase.haveIBeenPwned(), 8, 64);
}
@PostMapping("/passpolCheck")
public Status check(@RequestBody String password) {
return this.policy.check(password);
}
}
And wrote a JavaScript example that calls the service with the Fetch API and displays the response.
async function checkPasspol() {
if (password_passpol.value !== '') {
try {
const response = await fetch('http://localhost:8080/passpolCheck', {
body: password_passpol.value,
method: 'POST'
});
const status = await response.json();
output_passpol.innerHTML = status;
} catch (err) {
output_passpol.innerHTML = err;
}
}
}
Self-hosted Have I been pwned? database ¶
hibp
and passpol
use the Have I been pwned? database to check the password. If this is problematic
that your application sends information to an external third-party service, it is fortunately not that complicated to host
the Have I been pwned? database on your own server.
In a previous blog post I described the process how to download and import the Have I been pwned? password database into an embedded Xodus database.
If you followed the steps from this blog post, you have a local Xodus database and can write a RestController that accesses this database and then call the endpoint from a JavaScript application
@CrossOrigin
public class SelfHostedHibp {
private final MessageDigest md;
private final Environment env;
public SelfHostedHibp() throws NoSuchAlgorithmException {
this.md = MessageDigest.getInstance("SHA-1");
this.env = Environments.newInstance("e:/temp/pwnd");
}
@PreDestroy
public void destroy() {
if (this.env != null) {
this.env.close();
}
}
private Integer haveIBeenPwned(String password) {
return this.env.computeInReadonlyTransaction(txn -> {
Store store = this.env.openStore("passwords", StoreConfig.WITHOUT_DUPLICATES, txn);
byte[] passwordBytes = this.md.digest(password.getBytes());
ByteIterable key = new ArrayByteIterable(passwordBytes);
ByteIterable bi = store.get(txn, key);
if (bi != null) {
return IntegerBinding.compressedEntryToInt(bi);
}
return null;
});
}
@PostMapping("/selfHostedHibpCheck")
public int selfHostedHibpCheck(@RequestBody String password) {
Integer count = haveIBeenPwned(password);
if (count != null) {
return count.intValue();
}
return 0;
}
}
In JavaScript, we call the /selfHostedHibpCheck
endpoint with the Fetch API. The service returns
how many times a password has been exposed in a breach. If the number is 0, the password in question is not found in the database.
async function checkSelfHostedHibp() {
if (password_shhibp.value !== '') {
try {
const response = await fetch('http://localhost:8080/selfHostedHibpCheck', {
body: password_shhibp.value,
method: 'POST'
});
const status = await response.json();
if (status === 0) {
output_shhibp.innerHTML = `This password wasn't found in any of the Pwned Passwords loaded into Have I Been Pwned`;
} else {
output_shhibp.innerHTML = `This password has been seen ${status} times before<br>
This password has previously appeared in a data breach and should never be used. If you've ever used it anywhere before, change it!`;
}
} catch (err) {
output_shhibp.innerHTML = err;
}
}
}
You find all the examples on GitHub: https://github.com/ralscha/blog/tree/master/passwordcheck