12

I'm building a web app that needs to support user-switching offline and am wondering if it is safe to expose bcrypt hashes to authenticate users.

The basic flow:

  1. Primary users logs in while web app is online, hitting the server
  2. Server returns a list of other users within the same company, including their bcrypt hashes (hash cost of 10)
  3. Other user is allowed to create a record while the web app is offline by entering their own password, compared to those returned earlier using a JS bcrypt library

The application itself is fairly low-risk and there aren't many scenarios where a user would have incentive to impersonate a co-worker, but I would like to get this right.

Thanks in advance for any insight.

(A follow up question is: when syncing with the server, how to prove to the server that the other user actually did the offline authentication before creating their record, but that might be better as its own post.)

Alex Dunae
  • 221
  • 2
  • 6
  • 7
    Ah yes, a common error of assuming nobody else makes a client for your custom server. – Joshua Nov 26 '16 at 17:51
  • 2
    playing the devil's advocate: If publishing the bcrypted password is a security problem, then it won't matter if one stores passwords in clear or bcrypted on a server ... – Hagen von Eitzen Nov 26 '16 at 21:48

2 Answers2

33

No, it's not ok, but that's not a matter of the bcrypt hashes, but because you moved authentication from the server you trust (because it's your server) to the user's device (which you can't trust). It now takes a simple debugger to change the result of the "is this password valid?" check from "no" to "yes".

Also, it gives an attacker a persistent access to a list of hashes. So, the attacker can brute-force that list for as long as they want on any computer that they want to use.

Simply put, that is a very bad idea.

The right thing to do would be to cache the actions of the user who claims to be a coworker of your authenticated user, and let that coworker properly authenticate with your server as soon as they are back online to actually commit whatever they did to the server. One way to do that is simply to store the password the user entered and then send it to the server, in whatever shape you use during a "normal" log in. If that works, fine, the user has authenticated itself, and the stored actions can be carried out. If not, well, tell the user that he entered a wrong password and his actions cannot be committed unless he enters the right one.

Marcus Müller
  • 5,863
  • 2
  • 17
  • 28
  • That's definitely a concern. Given the low security environment, I'm thinking of sending the user a summary of records signed in their name after syncing to review / approve. The brute-forcing issue seems like the real show stopper though. Is there any other method of securely authenticating a user offline that you've encountered? – Alex Dunae Nov 25 '16 at 20:52
  • I'd really would be careful with that – it'll be very easy to forge a transaction that looks fine at a quick glance, but actually does something harmful. (e.g. do *you* always check the transaction summary in every single digit of the account number you're sending money to when doing online banking? And that's for a transaction you did seconds ago, not something you did yesterday evening on your way home from work...). IMHO, there can only be "successfully authenticated" and "nonexistent", not "hanging somewhere in between" actions,unless your users are very strictly trained to deal with such! – Marcus Müller Nov 25 '16 at 20:55
  • 12
    Imagine someone started spamming your system with thousands of not-really-authenticated pending records. Now, not only would that be a stupidly easy denial of service attack, someone, somewhere will approve one of these. That's the way most email worms propagate. It sadly works. – Marcus Müller Nov 25 '16 at 20:58
  • Really good points. The spamming shouldn't be an issue since we've already required a server auth step at the beginning and require it to post any records to the server. The user-review apathy issue is definitely a problem, though. Hard to get someone to review a dozen records carefully a day after they made them. Thanks for the good thoughts. – Alex Dunae Nov 25 '16 at 21:08
  • 3
    Storing the user's password in plaintext on the client is even worse than the original proposal. – kasperd Nov 26 '16 at 17:59
  • @kasperd no, it's not. The user needs to trust the user's device. That's normal. If the user can't trust his/her device, it makes no difference whether passwords are stored in plaintext, or hashed, or properly encrypted, or not at all: They are entered on an untrusted device, and thus, have to be considered tainted.(There's little sense in the user trying to figure out his/her own password by inspecting their own device.If I enter my password on the device of a colleague,I have to trust that colleague – there's simply no way around that. Doesn't matter in which shape the passwords are stored.) – Marcus Müller Nov 26 '16 at 18:01
  • 1
    @MarcusMüller The device needs to be trusted at the time where the password is entered. But it will not necessarily remain trusted for the entire duration the password is stored in plaintext on the device. For example if the device is stolen with the password stored in plaintext I would no longer consider the device to be stored. I would much rather have a hash of my password in circulation on a thousand untrusted devices than having the password stored in plaintext on a single trusted device. – kasperd Nov 26 '16 at 18:33
  • The whole point of the question is to get coworkers to share a device. In that scenario, it is unwise to store the plain-text password in the device :/ – mgarciaisaia Nov 26 '16 at 19:10
  • 1
    You should neither store password nor it's hash. After authentication, server should send *token* to client. Then, even if device is stolen you only have to remove that token from list of logged users instead of changing password. http://stackoverflow.com/questions/1592534/what-is-token-based-authentication – rav_kr Nov 26 '16 at 20:36
  • 2
    The moment you can use the hash of your password to authenticate, the hash has the same security "value" as the plaintext password. I fail to see why under that authentication scheme, where knowing the hash of your own password is sufficient for auth, "having the hash on thousands of devices" is remotely acceptable. – Marcus Müller Nov 26 '16 at 22:59
3

By pushing password hashes to the client you'd be exposing them to offline brute force attacks. That can be entirely acceptable if the passwords are sufficiently strong. But that is a dangerous assumption to make.

You could ask each user to confirm that they want their own hash to be published (after explaining the risk to them). But there is a lot of users who would happily say they understand the risk and have chosen a sufficiently strong password even though what they actually choose was password12345!!!

At this point you will have to evaluate how important this feature is compared to the risk from allowing the users to shoot themselves in the foot.

There is however another drawback in your design which is that of authenticating the updates made offline. Relying on the client to validate the user is not good enough.

Storing the user's password together with the update and sending both to the server once online would address the problem of performing only client side validation. But you would introduce three new problems:

  1. It would not catch mistyped credentials when entering updates. And later when those credentials are verified by the server, the user who typed them in the first place may not be around to fix that issue.
  2. The update would be malleable. It would be trivial to modify the update while stored on the client device to make it perform a different update once it arrives on the server.
  3. You would be storing passwords in plaintext on the device which is a big no-no securitywise.

I don't see any way to prevent offline brute force of passwords while still allowing the password to be validated at the time where an update is entered, and that validation is necessary for usability reasons.

The approach I would thus take is to create a public key pair based on the users password and a salt, and use the corresponding private key to sign the update. That means the update is no longer malleable, and the password need not be stored and transmitted, rather you just store and transmit the signature.

Of course the client would still need to know salt and username to generate a properly signed transaction. And it would need to know either the public key or a hash to verify it in the first place, which allows for offline brute force attacks.

You can truncate the hash verified client side to only a single byte, which makes offline brute force attacks less feasible and still has 99% chance of detecting incorrect passwords on the client side.

On the server side you should still be verifying the transmitted public key using a salted hash to ensure you only receive valid transactions.

Obviously any design for the application you are designing leaves lots of room for mistakes, so having final design and code reviewed by multiple security professionals is a necessity to ensure that the final result is secure.

kasperd
  • 5,442
  • 1
  • 19
  • 38
  • Thanks for the detailed reply. Can you say more about "...truncate the hash verified client side to only a single byte"? I've been puzzling over that for a few days and can't figure out what you're referring to. – Alex Dunae Dec 01 '16 at 19:34
  • @AlexDunae You need server side validation for security. But if you only do server side validation and no client side validation, a user can accidentally type an incorrect password, and you won't know as long as you are offline. The incorrect password can only be reported much later when the device is online again. With enough data on the client to fully validate the password, offline brute force becomes possible. A single byte of hash (but many bytes of salt) can provide a partial password validation. There is 1/256 chance that an incorrect password is accepted. – kasperd Dec 01 '16 at 23:57
  • @AlexDunae Of course 1/256 chance of an attacker bypassing password validation is not acceptable security, but the one byte hash is not there for security reasons. The attacker still has to bypass the full server side check which means the attacker need a correct password. But for the legitimate user accidentally mistyping their password there is more than 99% chance the client will notice and let the user know they mistyped their password. – kasperd Dec 02 '16 at 00:00
  • @AlexDunae If an attacker tries to perform an offline brute force attack, the one byte hash can eliminate many guesses for a password. But an attacker trying thousands of passwords per second will still find multiple passwords per second which would pass the single byte hash but still be correct. So even after an offline brute force attack, the attacker is still left with many possibilities for what the password might have been. – kasperd Dec 02 '16 at 00:02