Announcing password-2.0

2020-05-18

I recently helped with the release of the password-2.0 library.

This 2.0 release corresponds to a big rewrite of the password library, spearheaded entirely by Felix Paulusma.

The biggest improvement in 2.0 is the addition of a few new options for hashing algorithms, including Argon2, Bcrypt, and PBKDF2.

What is the password library?

The password library was originally intended to be used by people implementing username/password authentication in web applications, but it is now easily usable by anyone dealing with passwords and password hashing in any type of application.

There are four main advantages of using password:

  • Canonical Password and PasswordHash types that can be shared throughout your code. These are meant to be simple types with reasonable typeclass instances. You should be able to take password as a dependency and rely on these types from other libraries.

  • Easy-to-use functions for hashing a Password, and checking a PasswordHash against a Password.

  • An optional password-instances package that provides additional typeclass instances for Password and PasswordHash, like FromJSON (from aeson), FromHttpApiData (from http-api-data), and PersistField (from persistent). These instances are frequently used when doing actual web development.

  • Great documentation. Anything confusing in the Haddocks is considered a "high-priority" bug.

What changed in password-2.0?

password-2.0 is a complete rewrite of the password library by Felix Paulusma1. The architecture of the library has been completely redone to support multiple hashing algorithms.

The following algorithms are now supported:

password-2.0 also adds a bunch of much-needed documentation, as well as many tests.

How to use password?

This section will present a short tutorial for using password. The following commands are run in a GHCi session. You can follow along if you start GHCi with a command like stack --resolver nightly-2020-05-16 ghci --package password-2.0.1.1.

Enable the OverloadedStrings extension to make the following code a little easier:

> :set -XOverloadedStrings

The first step in using password is to pick which hashing algorithm you want to use. If you don't know which to pick, we recommend Bcrypt. The trade-offs of the different algorithms are discussed in more detail in the Haddocks.

Once you've picked the hashing algorithm, you can import its module. We use Data.Password.Bcrypt as an example:

> import Data.Password.Bcrypt

Next, we need a Password to work with. Let's create one with mkPassword2:

> let pass = mkPassword "foobar"
> pass :: Password
**PASSWORD**

Note that the Show instance for Password doesn't actually show the password. This is a security measure. You don't have to worry about passwords leaking into your web application logs.

Now that you have a password, we can hash it with the hashPassword function3. This produces a PasswordHash:

> hash <- hashPassword pass
> hash :: PasswordHash Bcrypt
PasswordHash {unPasswordHash = "$2b$10$IbnlktUyGjeunSlE4bQtfu89LY6veyDW0CGIoR5Kj8qrQa916txMS"}

This PasswordHash can be stored in a database4.

The checkPassword function can be used to check a Password against a PasswordHash:

> let result = checkPassword pass hash
> result :: PasswordCheck
PasswordCheckSuccess

Future Work

There are a few issues in the password library that we'd like help with from anyone interested.

  • Split off the Data.Password module into a separate package called password-types.

    All of the hashing algorithms provided in password come from cryptonite.

    However, we would ideally like people to be able to use the Password and PasswordHash data types as canonical data types in different libraries on Hackage. We don't want all these packages to have to pull in a transitive dependency on cryptonite.

    Ideally, we would have a password-types package that just exports everything from the current Data.Password module (which doesn't require a dependency on cryptonite).

    https://github.com/cdepillabout/password/issues/20

  • Split the password-instances package into separate packages.

    Currently, password-instances has instances for Password and PasswordHash for type classes from aeson, http-api-data, and persistent.

    Ideally, there would be a more fine-grained way to pick only the instances you want.

    For instance, if you're not using persistent to access a database, you shouldn't have to transitively depend on persistent just to get instances from aeson.

    https://github.com/cdepillabout/password/issues/1

  • Add more helpful instances to password-instances.

    password-instances has helpful instances from a few widely used libraries, but it is missing many more.

    If you use password, please consider contributing missing instances!

  • Support different hash formats.

    password has its own way of encoding hashes. In the above example, you can see that the Bcrypt algorithm outputs hashes that look like this: $2b$10$IbnlktUyGjeunSlE4bQtfu89LY6veyDW0CGIoR5Kj8qrQa916txMS.

    This works well if you're writing your application from the beginning with password, but if you're trying to interoperate with password hashes produced by other languages and frameworks, you'll have to do some manual conversion.

    It would be nice to extend the checkPassword and hashPassword functions to be able to read/write password hashes in other formats.

    https://github.com/cdepillabout/password/issues/11

  • Add functionality for validating password complexity requirements.

    password currently doesn't provide any functionality for validating the complexity of passwords. It would be nice if it provided an API for checking whether passwords are at least a given length, have a certain number of uppercase, lowercase, and special characters, etc.

    This might be of interest to anyone who likes designing easy-to-use APIs for complicated use-cases.

    https://github.com/cdepillabout/password/issues/9

  • Perform a security audit.

    We'd love to get an in-depth review from any security-minded Haskellers. password mostly just wraps around functionality provided by cryptonite, but it would still be great to get a review for the code we do have.

    If you find any big issues, feel free to email me directly if you don't feel comfortable creating a public issue on GitHub.

Please feel free to send pull requests or create issues for any of the above enhancements.

Conclusion and Thanks

I need to give a big thanks to Felix Paulusma for password-2.0. He is solely responsible for almost all the work that went into this 2.0 release. Felix is now a full maintainer of password.

There were a few other people who contributed as well:

Thanks to everyone, and I hope we get more contributions in the future!

Footnotes


  1. The bulk of this work happened in this PR.↩︎

  2. In reality, you probably wouldn't create a password by hand like this, but instead get one in a JSON request. The FromJSON instance for Password provided by password-instances makes this easy.↩︎

  3. Note that PasswordHash, hashPassword, and checkPassword all have a phantom parameter. This is used to store the algorithm that was used to hash the password. You are not able to call checkPassword on a hash produced from a different algorithm's hashPassword.↩︎

  4. This is made easier with the PersistField instance for PasswordHash from password-instances.↩︎

tags: haskell