ChatGPT解决这个技术问题 Extra ChatGPT

How can bcrypt have built-in salts?

Coda Hale's article "How To Safely Store a Password" claims that:

bcrypt has salts built-in to prevent rainbow table attacks.

He cites this paper, which says that in OpenBSD's implementation of bcrypt:

OpenBSD generates the 128-bit bcrypt salt from an arcfour (arc4random(3)) key stream, seeded with random data the kernel collects from device timings.

I don't understand how this can work. In my conception of a salt:

It needs to be different for each stored password, so that a separate rainbow table would have to be generated for each

It needs to be stored somewhere so that it's repeatable: when a user tries to log in, we take their password attempt, repeat the same salt-and-hash procedure we did when we originally stored their password, and compare

When I'm using Devise (a Rails login manager) with bcrypt, there is no salt column in the database, so I'm confused. If the salt is random and not stored anywhere, how can we reliably repeat the hashing process?

In short, how can bcrypt have built-in salts?


e
erickson

This is bcrypt:

Generate a random salt. A "cost" factor has been pre-configured. Collect a password.

Derive an encryption key from the password using the salt and cost factor. Use it to encrypt a well-known string. Store the cost, salt, and cipher text. Because these three elements have a known length, it's easy to concatenate them and store them in a single field, yet be able to split them apart later.

When someone tries to authenticate, retrieve the stored cost and salt. Derive a key from the input password, cost and salt. Encrypt the same well-known string. If the generated cipher text matches the stored cipher text, the password is a match.

Bcrypt operates in a very similar manner to more traditional schemes based on algorithms like PBKDF2. The main difference is its use of a derived key to encrypt known plain text; other schemes (reasonably) assume the key derivation function is irreversible, and store the derived key directly.

Stored in the database, a bcrypt "hash" might look something like this:

$2a$10$vI8aWBnW3fID.ZQ4/zo1G.q1lRps.9cGLcZEiGDMVr5yUP1KUOYTa

This is actually three fields, delimited by "$":

2a identifies the bcrypt algorithm version that was used.

10 is the cost factor; 210 iterations of the key derivation function are used (which is not enough, by the way. I'd recommend a cost of 12 or more.)

vI8aWBnW3fID.ZQ4/zo1G.q1lRps.9cGLcZEiGDMVr5yUP1KUOYTa is the salt and the cipher text, concatenated and encoded in a modified Base-64. The first 22 characters decode to a 16-byte value for the salt. The remaining characters are cipher text to be compared for authentication.

This example is taken from the documentation for Coda Hale's ruby implementation.


Would you have more details as to why cost factor of 10 would not be enough? In Grails, I noticed that 10 is the default value for cost factor/log rounds for bcrypt so it might be worth updating given your suggestion.
The cost factor for bcrypt is exponential, or rather, a cost factor of 10 means 2^10 rounds (1024), a cost factor of 16 would mean 2^16 rounds (65536). It's natural then that it would take 5-10 seconds. It should take about 64 times as long as a cost factor of 10 does. To clear up other misinformation, PHP's crypt function uses the unix crypt library which is implemented in c.
@TJChambers That's right; if you can set the password on the account, you will be able to authenticate. Password hashing isn't intended to prevent that attack. It's meant to prevent an attacker with read-only access to the password table from authenticating. For example, you get a backup tape with the table on it.
Isn't storing the salt with the chiper bad security? If someone gets their hand on the hash, with enough computing it can be cracked. If he doesn't know the salt it's virtually impossible.
@LobsterMan No, not really. If you could keep a secret, you wouldn't use this approach, you'd just store the password. Password authentication schemes are based on the assumption that the attacker has discovered everything that you know. The salt is there to require each password to be attacked individually. The computational effort required for testing passwords is governed by the iterations. If users pick good passwords, they will be secure, even if when the salt is revealed. Hiding the salt could help someone with a bad password in some cases, but I'd work on password quality first.
A
Adam Paynter

I believe that phrase should have been worded as follows:

bcrypt has salts built into the generated hashes to prevent rainbow table attacks.

The bcrypt utility itself does not appear to maintain a list of salts. Rather, salts are generated randomly and appended to the output of the function so that they are remembered later on (according to the Java implementation of bcrypt). Put another way, the "hash" generated by bcrypt is not just the hash. Rather, it is the hash and the salt concatenated.


OK, so I sign up for a site and choose a the password "foo". Bcrypt adds a random salt of "akd2!*", resulting in "fooakd2!*", which is hashed and stored. Later, I try to sign in with password "bar". To see if I'm correct, it needs to hash "barakd2!*". If the salt was generated randomly to start with, how does it know how to add it back to "bar" before hashing and comparing?
@Nathan: bcrypt knows how to extract the salt back out of the generated output (which is stored in the database). When it comes time to authenticate, bcrypt separates the original output into its hash and salt components. The salt component is applied to the incoming password typed by the user.
To answer Nathan Long's comment, a good way of thinking of this is that salts are not meant to be secret. This is why the salt is included in the output from the bcrypt function as one of the answers pointed out above. The salt is there to prevent rainbow tables, which are lists of common passwords, or just brute force, etc... of different passwords but hashed. Without salt, the hash for a password in database A would be the same as a hash for a password in database B. Salt merely changes up the hash values making it harder for someone who stole the database to decrypt (unhash) passwords.
@Nathan but can an attacker just remove the known salts in all passwords and then create a table with them?
This is how I understand it: The idea is that every password has an unique salt. The salt in incorporated in the password hash so a hacker would have to create a rainbow table for every password. This would take an enormous amount of time for a moderate database. It's all about slowing an attacker down and thus making brute-forcing pointless.
j
jony89

To make things even more clearer,

Registeration/Login direction ->

The password + salt is encrypted with a key generated from the: cost, salt and the password. we call that encrypted value the cipher text. then we attach the salt to this value and encoding it using base64. attaching the cost to it and this is the produced string from bcrypt:

$2a$COST$BASE64

This value is stored eventually.

What the attacker would need to do in order to find the password ? (other direction <- )

In case the attacker got control over the DB, the attacker will decode easily the base64 value, and then he will be able to see the salt. the salt is not secret. though it is random. Then he will need to decrypt the cipher text.

What is more important : There is no hashing in this process, rather CPU expensive encryption - decryption. thus rainbow tables are less relevant here.


M
Manomite

This is a simple terms...

Bcrypt does not have a database it stores the salt...

The salt is added to the hash in base64 format....

The question is how does bcrypt verifies the password when it has no database...?

What bcrypt does is that it extract the salt from the password hash... Use the salt extracted to encrypt the plain password and compares the new hash with the old hash to see if they are the same...


K
Khalifa

Lets imagine a table that has 1 hashed password. If hacker gets access he would know the salt but he will have to calculate a big list for all the common passwords and compare after each calculation. This will take time and he would have only cracked 1 password.

Imagine a second hashed password in the same table. The salt is visible but the same above calculation needs to happen again to crack this one too because the salts are different.

If no random salts were used, it would have been much easier, why? If we use simple hashing we can just generate hashes for common passwords 1 single time (rainbow table) and just do a simple table search, or simple file search between the db table hashes and our pre-calculated hashes to find the plain passwords.