Hashing passwords with BCrypt in Kotlin
Why passwords should be stored as salted hashes rather than encrypted, and how choosing BCrypt removes the need for a separate salt column.
- Published
This article is also published elsewhere. https://iganin.hatenablog.com/entry/2021/09/09/232902
Originally written in Japanese. This is a translation of the same piece.
TL;DR
- Passwords are best stored hashed with a salt, not merely encrypted
- With BCrypt the salt is contained in the encrypted data, so you do not need a salt column
- jBCrypt/BCrypt.java at master · jeremyh/jBCrypt · GitHub is probably the safe bet in Java
Encrypting passwords
First, some background on storing passwords.
Storing them in plaintext is terrible — if someone gets into the network and can read the database, it is over. Is encryption enough? Not really. If there is a malicious administrator on the inside, they can reach the database and decrypt the passwords. A password is the system’s last line of defence, so ideally nobody but the user themselves can ever know it.
That is why hashing is usually the answer. Hashing gives you a one-way conversion from the entered password to a hashed password, and you can compare that against the hashed password stored in the database to decide whether they match. Because you cannot derive the original password from the hash, a malicious insider cannot obtain it, which makes the system considerably more robust. That said, a weak hash function apparently can be reversed, and too few rounds of hashing increases the exposure — both worth keeping in mind.
Even with all that, passwords can still be attacked with dictionary attacks or rainbow tables. Setting a salt per user and adding it to the password prevents those.
So when storing passwords, I think you need all of the following:
- Encryption
- Appropriate hashing
- A salt
BCrypt
It is, as I understand it, a library implementing Blowfish encryption. Plenty of articles recommend it, and a security-minded engineer at my workplace recommended Blowfish, so using BCrypt for password encryption is probably not far wrong.
Using it is very simple. These are the methods you need:
// Generates a salt. The argument apparently determines the number of hashing rounds:
// it runs 2 to the power of the argument. Careful — raising it grows the running time exponentially.
String gensalt(int log_rounds)
// Produces a hashed password from a plaintext password and a salt
String hashpw(String password, String salt)
// Compares a plaintext password against a hashed password and decides whether they match.
// Note that no salt is needed as an argument.
String checkpw(String plaintext, String hashed)
Closing
Writing about security always makes me nervous. If anything here is wrong, please let me know.