1

I've been reading up on password storage and such, and have come to the conclusion that I need to be using bCrypt. I've got an implementation working correctly, but I'm wondering the best way to move forward with my application.

The previous password storage system was simply hashing and salting. It's actually easier for me to keep this system and layer bCrypt over the top than it is to rewrite it with bCrypt alone.

However, I keep reading that combining different hashing algorithms is a bad idea - so I wanted to get some clarification, as I can't think of any reason not to implement in this way:

  • Hash(password + salt + pepper) * 1000 iterations - SHA-256
  • Resulting hash is run through bCrypt with its own salting and a defined work factor.
  • Final hash stored in DB along with original salt
  • To check password, it's rehashed as above before being put through the bScrypt hash check.

Is there any reason I shouldn't be doing things this way and should abandon the original password + salt + pepper in favour of just giving the plain password to bCrypt?

Thanks

1
  • I presume I also need to convert the hash to base64... I read this somewhere also. Doesn't the character limit of bCrypt cause a problem?
    – Gary
    May 21, 2014 at 16:05

2 Answers 2

3

There aren't any obvious security weaknesses with your schemes. The original one (other than possibly not storing a version number with your hashes so you can upgrade your scheme to more rounds or a new pepper if necessary), or the new one layered with bcrypt. Bcrypt can take 72 bytes of input so there's no problem using sha256 as your hash.

I would be careful if you encode the output of your keystrengthened sha256hash before feeding it to bcrypt. In principle, its probably best to keep it in the binary form -- if you were using sha512, that would let all the bytes be used (as 72 bytes = 576 bits). Granted with sha-256, it doesn't matter as long as you do it correctly. A sha256 hash is 32 bytes long (in binary with non-printable letters), 44 bytes long after base64 encoding, 64 bytes long (in hexadecimal), but 88 bytes if you base64-encoded the hex-encoded hash (which would be partially truncated and slightly weaken security -- e.g., instead of 256-bits of inherent security you have 72/88*256 ~ 209 bits).

Note, if a user has an extremely high entropy password (e.g., 400-bits of entropy) that in principle would require ~2^400 time to crack, by using sha256 as an intermediate step it will only take time ~2^256 before its likely to find some password that works. Granted 2^256 is completely secure these days (as is anything significantly over ~2^80, so this difference has no practical relevance).

It does get more complicated and potentially more annoying to maintain (granted you could argue this is a good thing -- if your database was ever leaked it would be harder for an attacker to get a GPU to brute force it). If you ever upgrade your system and things don't work perfectly there are significantly more points where things could be going wrong. If someone new developer comes along and see hashes are in the form like bcrypt, they may assume they are just bcrypt and change something that breaks the system.

An alternative would be to keep both systems in place and gracefully upgrade users to bcrypt on their next successful login.

def login(username, password):
    user = get_user(username)
    if user.uses_old_hash():
        calculated_old_hash = sha256_1000_round(password, salt=user.salt)
        if constant_time_string_compare(user.hash, calculated_old_hash):
            calculated_new_hash = bcrypt.hashpw(password, salt=bcrypt.gensalt(12))
            user.hash = calculated_new_hash
            print "Login Successful"
            return True
        else:
            print "Login Failed"
            return False
    else:
        hash = user.hash
        calculated_new_hash = bcrypt.hashpw(password, salt=hash)
        if constant_time_string_compare(hash, calculated_new_hash):
            print "Login Successful"
            return True
        else:
            print "Login Failed"
            return False
5
  • Just remember (B)crypt expects a null terminated string when appending the binary form, which can contain null bytes, to the salt.
    – Darsstar
    May 21, 2014 at 20:48
  • Thanks for the detailed response. I'm a little confused on a few points here: First, you seem to be suggesting I can use SHA-512 - which is what I'd prefer. I was under the impression that the result wouldn't fit into bCrypt's character limit? Second I read that I needed to base encode the hash to base64 before passing it to bCrypt because of some problem with certain characters that may come out of the SHA hash, but again you seem to suggest otherwise? Third, in your code example, you're passing the password directly to bCrypt rather than the hash result - this is my original question! ;)
    – Gary
    May 22, 2014 at 9:16
  • Ok, I think I understand your points regarding the encoding... I don't seem able to keep the hash in binary format and still pass it to the bCrypt hashpw function - this could be a limitation of the library or language I'm using, I'm not sure. As best I can tell the hash function I'm using outputs a printable hexidecimal string, which I was then base64 encoding. If I understand you correctly, I should refrain from base64 encoding this string. If I change to SHA-512, this string becomes 128 hexidecimal characters - I assume I cannot put this through bCrypt.
    – Gary
    May 22, 2014 at 10:02
  • With regard to password entropy - one of my reasons for wanting to hash the password before putting through bCrypt, was the character limit. If someone wants to create a passphrase of twenty words, wouldn't bCrypt reduce the entropy of this passphrase due to its length limitations? Whereas the hash of the password is always the same length. Please correct me if I'm wrong, I'm new to the cryptography discussion - I'd thought salting and hashing was secure on its own! ;)
    – Gary
    May 22, 2014 at 10:04
  • @Darsstar I'm afraid that's gone over my head a little - My hashing function seems to output a hexidecimal string and while I can convert that to binary, the bCrypt hashpw function crashes out if I pass the result into it.
    – Gary
    May 22, 2014 at 11:06
-1

Recently, with the hacking of a lot of databases, there has been a rash of emails going around that claim to have hacked you and been watching you over your webcam and will release embarrassing data if you do not pay them. They will then have some gibberish and a message about knowing your password. Usually, in the gibberish, you will see your password and most people will ignore the non-password bits but feel certain this person has the embarrassing data since they will see their password in the mix.

I just want to point out that the gibberish and password that shows up in the email is a result of backwards hashing and retrieving several possible results. If the database table that is stolen and the reverse hash contains the salt, then the person backwards hashing could easily locate the real password by finding the result that contains the salt. It is true that the salt makes the hash longer and harder to reverse but the salt also gives values that can be searched in the reverse hash and verified.

1
  • Salts do not make hashes longer and the salt is not in the hash text. You don't add the salt to the hash. You add the salt to the password then hash the result. Hashes always end up the same length whether you salt or not. And one cannot easily locate the real password by finding the result that contains the salt. I think you have some misunderstanding about how passwords are hashed and how salts work.
    – schroeder
    Jul 18, 2020 at 19:12

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .