Check for commonly used or compromised passwords

Published: May 03, 2018  •  java, javascript

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 memorized passwords as lengthy (at least 8 characters) as they want using 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 will 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 common 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 will treat 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 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 about all the result properties.

This example is only interested in the result.score property, which contains a number between 0 and 4:

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 = "";
    }
  });

main.js


The zxcvbn JavaScript library was ported to different programming languages. Check the project page to see a list of all available ports. For Java the two libraries nbvcxz and zxcvbn4j exist.


hibp

hibp is a JavaScript client library for the Have I been pwned? service.

In a npm managed project you can 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;
    }
  }

main.js

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 and only 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 that 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.6.1</version>
    </dependency>

pom.xml

Usage is very simply. You create a new instance of the PasswordPolicy class and specify as first parameter the blacklist the library should check. Second and third parameter specify the required minimum and maximum length of the password.

passpol supports two blacklists. A check against the Have I been pwned? database. This will send 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

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);
  }

}

PasspolController.java

and then call it from JavaScript with the Fetch API and display 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;
      }
    }
  }

main.js


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 can write a RestController and then call the endpoint from a JavaScript application

@RestController
@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;
  }

}

SelfHostedHibp.java

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

main.js


You find all the examples on GitHub: https://github.com/ralscha/blog/tree/master/passwordcheck