24

Imagine the following situation. We're making a web application which should be really safe

Now the accounts/users are not directly added by us but they receive a letter with a logincode. We get a file every now and then containing an unsalted SHA-1 hash of this logincode, and some other really basic info.

Considering SHA-1 is now a days seen as a liability, and the software generating these hashes is not under our control (it's also probably old software not capable of better hashes)

Would it be a solution to hash the received hash using Bcrypt. This should fix the problem of it being unsafe/too fast if i'm correct? Should I also add a pepper?

Are there any other problems i might have missed? Or should we push back the problem and have them fix their software for better hashing functions?

Clarification:

Our client is an employee of a company and needs approval for this project.

The third party made a system for the company of our client that handles sensitive information about the customers of our clients company. They want to give some of their customers the ability to login to our application, this has to happen through their system because that's what the employees of our customer know and if they would have to add users manually in our application our client would not get approval.

Also if there is any leak or breach (even though technically we have 0 access to any sensitive information, even in our own application there is no name or any identifying info about the user) this could still attract unwanted negative attention that would hurt both the client and us.

Problem is, the system the third party made can only do unsalted SHA-1 Hashes. So my question is to try and see if we can work around this. Or if we are forced to tell my client to force the third party to implement a better hashing system, which they might just say answer with no we don't want to. Or it might cost a lot more money.

I hope i explained it a bit better while still being vague. :)

10
  • 3
    You could potentially give more information, as it is not clear what either the third party or your company are using the SHA1 for. If you don't want the SHA1 at all for instance, just ask to have it not sent. If you are adding it to your own DB and using it to check plaintext passcodes sent by end user then Anders' answer applies. If the end user or third party instead is sending the SHA1 to authenticate, then some of Peteris' answer could apply - although you could store a bcrypt of the SHA1 at your end (and destroy the letter) in that case. Commented Nov 28, 2016 at 14:27
  • 1
    @NeilSlater I understand your comment. I made it a bit vague on purpose as it's work related and don't feel it's professional to put the business idea of our client online. I'll try and clarify while trying not to do this.
    – Jester
    Commented Nov 28, 2016 at 15:03
  • @NeilSlater I added a clarification while still being as vague as possible.
    – Jester
    Commented Nov 28, 2016 at 15:19
  • Make sure to very strongly, very explicitly advise to your client that a SHA-1 hash in their system is dangerously weak and should be upgraded to modern password storage techniques. Also, this mechanism for delivering user data to your app is extremely ad hoc. It'd be better if storing user credentials wasn't even your app's responsibility (e.g., your app actually queries the other one to verify log in by obtaining a token or it implements OAuth or some other mechanism of getting the other app to perform the authentication). Then upgrading the other app doesn't have to be coordinated with you.
    – jpmc26
    Commented Nov 28, 2016 at 18:41
  • 1
    The problem isn't SHA-1 itself, which is actually still plenty strong for password hashing, but rather that the system does not appear to be using a salt. Without a salt, rainbow table attacks are possible even against perfect hashing function -- because users aren't choosing strong passwords. Commented Nov 28, 2016 at 21:01

6 Answers 6

20

Yes, hashing it again with bcrypt is a good idea. Do note, though, that it will not give your login code more entropy, it will just make it take longer time to crack. So if it is really low entropy to begin with you might need a very high cost factor to make it unfeasible to crack.

On a side note, in general it is better to just hash with one good hash function than playing around with many, as explained in answers to this question. But you don't have that option, so the arguments there are not really valid for your case.

As for using a pepper, I would recommend just encrypting the hashes instead. It gives the same kind of protection, but offers more flexibility. But it is no replacement for salting.

This and this might be interesting reading for you. Also, Roshan Bhumbra has an interesting suggestion about rehashing with just one function after the first login.

1
  • Comments are not for extended discussion; this conversation has been moved to chat.
    – Rory Alsop
    Commented Nov 29, 2016 at 13:22
5

Encryption in transit

In your current system it seems that de-facto password is the "SHA-1 hash of this logincode" since possession of a matching hash is sufficient to make a valid file, though it's not clear if it allows the attacker to exploit something directly. However, in this regard, it's no less secure than simply sending a plaintext password - which is appropriate as long as you "get a file" through a secure and encrypted channel, i.e. proper https / sftp / etc.

Encryption in storage

A risk with this system is that if an attacker can obtain a copy of that file, then they can obtain the full passwords of the users. IF you need to store these hashes, then you are right that it would be reasonable to hash them with bcrypt for storage, with the main issue being that you need to be sure that you immediately delete the received SHA-1 hash and no copies of it (e.g. temporary files, backups, caches, etc) remain in storage.

5
  • Can you explain your first sentence: "In your current system it seems that de-facto password is the "SHA-1 hash of this logincode" since possession of a matching hash is sufficient to make a valid file" I don't fully understand what you mean, i think the logincode is the de-facto password? Or do you mean because one could use a rainbow table to get the original login if one were to intercept the SHA-1 hash? Also if the channel through which this is communicated is not secure than there is nothing i could do to fix this right?
    – Jester
    Commented Nov 28, 2016 at 12:37
  • @Jester I mean that in the absence of any other components in addition to those that you've described, the attacker doesn't need to know the original logincode if they have the hash. Since you're only receiving the hashed value, they might be able to impersonate the user by simply sending a captured hash instead of actually hashing something. And yes, if the channel is not secure then the SHA-1 hashes are exposed and pretty much mean that the user passwords are freely available to anyone who can snoop this traffic.
    – Peteris
    Commented Nov 28, 2016 at 12:43
  • I don't understand how they "might be able to impersonate the user by simply sending a captured hash instead of actually hashing something." If i have a form which on submit hashes the input SHA-1 and then bcrypt (all server side of course). entering the SHA-1 hash instead of the original logincode would result into a different hash and thus not match? I can see how this might happen if you hash client side and then send it to the server to check, but i never heard of anyone doing that. Is that what you meant?
    – Jester
    Commented Nov 28, 2016 at 12:50
  • @Jester as I said, "any other components in addition to those that you've described" - the original post mentions only receiving a file with a SHA-1 hash for authentication, not a separate channel with the full password; it can be implied that maybe the file is just used to create accounts and the actual authentication is done elsewhere according to common best practice, but it wasn't stated currently. But anyway, if the channel over which SHA-1 hashes are sent isn't secure then that seems an obvious serious weak point.
    – Peteris
    Commented Nov 28, 2016 at 13:01
  • Aaah okay, i think i did mention them receiving a letter with the logincode and us receiving the hash of said logincode. But i understand! :) Thanks for the tips though! I'll make sure the channel is secure and that the files get deleted after being processed.
    – Jester
    Commented Nov 28, 2016 at 13:05
3

I see another option which doesn't seem to have been mentioned, although this would work better if the system which generates the hash could use a Bcrypt.

You just need to update the hash when the user logs in. Although this means you'll have two different hashing algorithms in use at the same time, it gets around the 'issue' mentioned by @Anders regarding using two algorithms on one value.

You can store a value next to the hash to tell the application which type it is, and if it's the SHA-1 hash then you can hash the password they just entered (provided it was correct) and store that.

2
  • This could be implemented to get rid of the double hashing after first login. but the double hashing still has to be done so it's more reliable until then. If the system could generate a bcrypt hash i wouldn't be here ;')
    – Jester
    Commented Nov 28, 2016 at 21:20
  • you can rip the existing passwords to avoid a login wait...
    – dandavis
    Commented Nov 29, 2016 at 19:34
2

If the original hashes are not salted, then you can simply apply any salted hash algorithm to the original unsalted hash that you have received.

It is however important that it is clearly marked in your database that these hashes are using the combined hash algorithm. It is not entirely unlikely that at a later date you will have different users in your database secured with different hash algorithms. At that time it is important that you know the correct function to use for each user in your database.

It is possible to upgrade stored hashes in your database the next time the user logs in. Though it may not be important because bcrypt is only marginally more secure than SHA1 followed by bcrypt.

If the original hashes are salted it becomes more tricky. In that case you can still apply two layers of hashing, but in that case it is very important to store all the salts and only the last hash value.

2

There is an additional danger when wrapping unsalted hashes - the "hash shucking" problem (disclosure: my answer)

Briefly, if an unsalted hash also appears in any other corpus, even those not yet cracked, that hash can be tried directly against a wrapped bcrypt corpus (as if the hash was the password). Once an inner hash is discovered, that raw hash can then be attacked separately at much higher rates than would be possible if the password was directly hashed with bcrypt.

(This is understandably not intuitive, so readers are encouraged to experiment with a practical example, as described in my answer.)

A common mitigation for this is to add a 'pepper' (a static, complex shared salt) to the hashes, and store the pepper separately from the main corpus (ideally, in an HSM, or on a separate hardened authentication server that adds the pepper). The goal is to make the pepper unavailable to the attacker even if they steal the bcrypt corpus, rendering hash shucking (and many other attacks) impossible.

In an upgrade scenario (migrating from a raw hash to bcrypt, using wrapping the old hashes in bcrypt as an interim step), the mitigation is to intercept the plaintext upon the next successful login, and hash the plain directly as bcrypt, eliminating the inner hash. The need for such upgrades is common, and can be tricky. See Michal Spacek's guide to upgrading password hashing.

0

A lot of good advice here already, just to add a bit more context about "hash shucking".

For a nasty breach, one can assume the attackers know user IDs bound to password-hashes bound to salts, etc.

We can generously make one more assumption, that they even know the hashes were generated from the function bcrypt(md5($pass), salt, ...) specifically. Thus, for a given user, these people know everything needed to generate the same hash -- except for the critical/secret password ($pass), the whole point of this exercise.

So it becomes more obvious that most, if not all, hashing functions are only as secure as their arguments remain secret -- they have to be so, to be useful. Their intrinsic strength (whether bcrypt, MD5, etc.) becomes irrelevant once said arguments are known by attackers. For instance, 2 scenarios:

  • If the function is known (bcrypt), likely secret arguments are known (md5($pass)) from a previous breach, then of course it becomes trivial to use the result of bcrypt(md5($pass)) to find matches in a new breach.

  • Again, the same reasoning applies when the function is known (bcrypt), likely secret arguments are known ($pass this time) from a previous breach, then you can use the result of bcrypt($pass) to find matches in a new breach.

In each case, the attackers still haven't learned anything new about the original passwords -- all they've gained is the knowledge that this user used the same password in both breaches/websites (and possibly more unbreached sites).

If the 2nd/new breach is from a high-value target, this knowledge might then motivate those rogues to crack those secret arguments to get back at the original password. The potential gain now matches or surpasses the cost of the effort involved.

Thus, this "shucking" isn't really "stripping" anything, implying that bcrypt was bypassed due to some special inherent weakness in the rehashing. Instead, it's simply what happens when you know everything that went into generating a hash -- in this case, the hash bcrypt(x, y, ...).

And when all those arguments are known, then of course the underlying password is now only protected by how well the arguments hide it -- which isn't very much in both cases above. But remember, beyond basic length requirements, these arguments were never required to be strong or even unique in the first place, they merely had to be secret. Whether in the user's head or in other (Responsible) people's databases.

The real differences between the 2 scenarios above are:

  • Secret-cracking effort: To get at the original password, it is, um, significantly easier to "crack" the secrets in the 2nd scenario than in the first. But ultimately, they are both fairly easy to crack, if there's sufficient incentive.
  • Likelihood of knowing arguments: There are (apparently) far more MD5, SHA-1, etc. leaked and uncracked hashes in the wild, than there are real leaked passwords. This means that it is considerably easier to find likely secrets for the first scenario, than it is for the 2nd.

And that last part is the true and perhaps only danger with rehashing this way, and why you should move on from it as soon as feasible -- because a lot of your secrets (md5($pass)) have a relatively high likelihood of being already known to attackers. And those secrets are easily cracked, if you're important enough and ever suffer a breach.

It wasn't the hashing scheme itself that failed you -- rather, it was others (like "you" >_>) who "allowed" your (shared) secrets, whether MD5 hashes or clear passwords, to get out into the world.

Because if there were no such breaches, then the attacker can sadly only resort to trying regular dictionary attacks, with generated password lists passed into bcrypt(md5($pass)) -- which would of course require more effort compared to bcrypt($pass).

5
  • Just to clarify -- are you claiming that, even against some user that never "reuses" passwords, an attack from a dictionary of MD5's hashes is still somehow "superior" to a plain, traditional raw-password-guessing one?
    – ManRow
    Commented Aug 5, 2023 at 10:06
  • @ManRow No, I'd say that if there's no reuse, then the superior attack would depend on how strong the original password is, how likely that it's been used by others or is in password lists. In other words, for a given user, most/all of the added danger from shucking disappears if their password is a strong randomly-generated one. Commented Aug 5, 2023 at 21:36
  • The overall point I was trying to make is that, this danger from rehashing (i.e. shucking) arises only because of the considerably greater proportion of leaked (MD5 etc.) hashes in the wild, compared to leaked passwords -- not because of some other intrinsic problem with rehashing. Commented Aug 5, 2023 at 22:38
  • Sure, but specifically just for a user who never reuses passwords (although still uses weak ones!), is a server using bcrypt(md5($pass)) still more dangerous (just to them) than one that uses the standard bcrypt($pass)? In other words -- against such a user (who never reuses passwords) on a server using bcrypt(md5($pass)), would a dictionary attack of MD5 hashes still be "superior" to a plain, raw-password-guessing one -- since, as you suggested, there are simply already many more MD5's "out in the wild" than just raw passwords anyway?
    – ManRow
    Commented Aug 5, 2023 at 23:18
  • 1
    It's not a binary thing really. At the weakest end of the password spectrum, your known/common password lists should yield results faster, perhaps much faster, than MD5 hashes, since you can order the list such that you attempt the most common passwords first, unlike opaque hashes. While at the other/strongest end, you'd be better off just using the MD5 hashes to keep your cracking focused on real passwords all used by real people, rather than generated word combinations that nobody may actually use. This is pure speculation though, I'm no great expert here. Commented Aug 6, 2023 at 1:16

You must log in to answer this question.

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