0

I have a database table with accounts. I'm using PBKDF2 to create hashes from passwords. The passwords are of the correcthorsebatterystaple type, so we assume they are secure and unique. There are no usernames, so I need to be able to find the matching account from an entered password. To make the hashes indexable, I'm using an empty string as PBKDF2 salt.

From what I read, PBKDF2 does not protect from rainbow table attacks. Is there anything I can do to make this more secure?

I was thinking, what if I use the entered password as the salt? On the other hand that feels like it would just add one more iteration to the key-stretching, but I'm a bit out of my depth here.

AndreKR
  • 498
  • 4
  • 9
  • You need to migrate the user's password. In the end, they send you the clear passwords. Once you get it, migrate it into Bcrypt, PBKDF2, or Argon2i without interaction from the user. – kelalaka Sep 23 '19 at 10:02
  • No key stretching or cryptographic hashing algorithm will ever be resistant to rainbow tables without (a suitably random) salt. (Also side note: Correct Horse Battery Staple passwords are better than what most humans use, but don't hold a candle to password-manager stored truly random passwords that are never reused; because the weak part is, invariably, the limits of human memory and our tendency to reuse passwords.) – Ghedipunk Sep 24 '19 at 18:11
  • That said, you probably don't have to worry about rainbow tables for a reasonably tuned PBKDF2 difficulty, as few people would bother creating a rainbow table for every possible difficulty setting with a blank or predictable salt. – Ghedipunk Sep 24 '19 at 18:13

2 Answers2

1

Honestly, I don't see an issue with adding a field to store the salt. Or you could store the salt in the database field similar to how bcrypt does it: $version$salt$hash.

This^ is the easiest way to prevent creating a security issue. This should be the answer to the question, and if this is possible, this is by far the most recommended way.

But since you asked...

Rainbow tables trade memory for time. By creating a large lookup table, you have to generate the table only once and apply it many times (this saves time) but it requires more storage. If the attacker needs to generate a custom rainbow table for your database, there is no real benefit to a rainbow table.

A salt also protects from another 'attack': you can see which users have the same password. That is, users within your user database table, but also globally: if anyone ever used pbkdf2 with the same settings, their value for the password correct horse battery staple will match your record if anyone uses that password. If you have no unique field (other than the password) in your database, then the passwords must be unique within your database and you only need to protect from matching passwords in other databases.

The best way to do this is by using a pepper value. This is a randomly generated secret that you mix in with the password and ensures that your hashes are unique to your database. It also means that an attacker needs both the hashes and the pepper in order to crack any of the passwords. Without the pepper, assuming the pepper is strong enough (e.g. 128 bits of entropy), they will not even be able to crack the password "123456". A salt is unique per user and not secret, but a pepper is the same for all users and secret. It is usually best to apply both a salt and a pepper to stored passwords (NIST recommends that since 2017).

One safe way to mix in a pepper with a password is by using an HMAC:
password = hmac(password, pepper, algo="sha256")

To generate your pepper, you could use a regular password generator and generate a very long password. Make sure it uses a cryptographically secure random engine. The most popular and open source password managers do this.

If possible, the pepper should be stored separately from the passwords. If the passwords are stolen, the pepper might not be, and the passwords would remain safe. Since passwords are commonly in a database (like MariaDB), the pepper is commonly stored in a config file:
password = hmac(password, read_file("/etc/example/pepper"), algo="sha256")
password = pbkdf2(password, ...parameters)
if password == mariadb_query('select password from users where...') then ...

But this may be different in your situation, since you don't have usernames (or any other unique field) which is rather odd.

See also: Why do we authenticate by prompting a user to enter both username and password? Does prompting the password only suffice?

As for your proposal to use the password as salt, that only helps insofar as the password is unique. If you can guarantee the password is globally unique (and therefore also very strong, otherwise you cannot guarantee that), there is no point doing PBKDF2 anyway (any safe hash function will do, even MD5 if I'm not mistaken (of course, never ever use MD5 unless you travel back in time to before SHA2)). But if you cannot absolutely guarantee that the passwords are unique and strong enough to be uncrackable with a single pass of a fast hash function, then it's also not a good idea to use the password as salt. The whole point of a salt is to make your hashed value unique, and this defeats that purpose. Anyone who uses PBKDF2 with the password as salt, will have the same value for the same password. If using the password as salt was a good thing to do, we'd already be making our lives easy.

Luc
  • 31,973
  • 8
  • 71
  • 135
  • Sorry, I [slightly edited](https://security.stackexchange.com/posts/218507/revisions) my question. I *think* now it asks what I really wanted to ask. :) – AndreKR Sep 24 '19 at 18:06
  • @AndreKR Is the "password as salt" thing what you are hoping to have answered? If yes, see my edit (addition at the bottom). I don't quite understand the password as index remark in your edited question. If that's what you want to ask about, I think you should clarify it. – Luc Sep 24 '19 at 21:07
0

PBKDF2 is salted. What sources seem to be indicating otherwise?

Royce Williams
  • 9,128
  • 1
  • 31
  • 55