Hacker News new | ask | show | jobs
by SloopJon 1637 days ago
The article suggests that hashing (PBKDF2) is done client-side only, and that LastPass stores this hash directly. If true, this is very bad. However, LastPass claims that PBKDF2 is also used server side:

> We then take that value, and use a salt (a random string per user) and do another 100,000 rounds of hashing, and compare that to what is in our database.

https://blog.lastpass.com/2015/06/lastpass-security-notice/

While it's true that the client-side hashing means that LastPass never sees your plaintext password, the first hash effectively becomes the password. Then it's on LastPass to treat it as such, which they claim to do by hashing it again.

Edit: another link describing the use of PBKDF2:

https://support.logmeininc.com/lastpass/help/about-password-...

3 comments

When I was doing some research into building an app that encrypted data similar to these cloud password managers, I encountered OPAQUE[1] which seems to be the ideal way to perform authentication and securing a master encryption key. It is an asymmetric PAKE that also has a step for providing a salt. This removes the need to do what LastPass does with treating the first hash as a password. There is a great article from Cloudflare on how it works[2], and a working implementation of the spec in rust[3].

[1]: https://github.com/cfrg/draft-irtf-cfrg-opaque

[2]: https://blog.cloudflare.com/opaque-oblivious-passwords/

[3]: https://github.com/novifinancial/opaque-ke

I wish there was a good way to implement this sort of double hashing in web apps. Doing the extra salted hash client side ensures that the value the server sees is globally unique, even when the user is reusing passwords across sites.

Unfortunately the only way I know how to implement that is to have the server send JS down to the browser that instructs it to perform the hashing. For certain types of compromises server side, the attacker would just modify the JS to get the unhashed password. I'd also need to fall back to pure JS hashing for old browsers (5% users?), so there's a UX concern if I perform lots of rounds.

I kind of wish there was a different password HTML field that could run the client side hashing without JS, so the browser would manage that. Ideally using different UX so the user understands they are using a "safe" password field. The end result would be to deny access to the raw password, which is likely reused on multiple sites.

There's little security advantage to doing this other than some obscurity, because a well informed attacker can still implement all the same attacks:

* An attacker with access to the database will know they can reduce the "hashing algorithm" to two sequential hashing algorithms and still bruteforce a series of plaintext passwords to check to see if the hash matches what is in the database.

* An attacker with access to the plaintext network communications or app server can just store and replay the second hash to login

* An attacker with access to the client machine can grab the plaintext password still

Lastpass does this is for end-to-end encryption reasons, where it is useful, but for standard apps I don't think it would be.

Your analysis seems to overlook an important detail in that first bullet point - dictionary attacks are only feasible when the KDF is fast. Authentication servers tend to require the KDF to be fast so they aren't constantly performing a denial of service attack on themselves. What people are looking for is a way to make the combined KDF slow by pushing most of the work to the client side. If this succeeds, you have made dictionary attacks very difficult without making your authentication process a denial of service vector.

The web browser ecosystem makes this a pretty hard task, unfortunately. You need fallbacks that weaken the process to the point where it is basically useless in a lot of cases. But the goal of a client/server split in the KDF is actually quite sensible. The client side does the large amount of work to protect users from dictionary attacks. The server side does a relatively much smaller amount of work to protect itself from being easily exploited if its hashes are exfiltrated when the hashes aren't themselves vulnerable to dictionary attacks.

This is an interesting point. I'd be inclined to ensure that the server side hash is still at least independently expensive enough as would be desirable for plain text, but then using the client side hash to go above and beyond computationally seems reasonable to me.

I would wonder though -- there's obviously a practical limit on user experience for waiting for computation, and is there really fast enough implementations available to the browser within that limit that would foil what a dedicated attacker could compute in proper hardware for a dictionary attack? You'd also have to consider if it's really that expensive to just throw more hardware at a server side hash. I guess it'd depend on the browser, whether the attack is targeted, and how big the attacker's budget is that you're considering.

Either way, if this was the goal and the team considered those factors then still felt it was worth the effort of doing so I would think it seems reasonable, though it's probably not something I'd make standard practice in my own apps.

> is there really fast enough implementations available to the browser

Browsers have pretty good support for surfacing native code SHA family hash functions which you can use to speed up PBKDF2. It's called the Web Crypto API and it's available even in Internet Explorer 11. [1]

If you're willing to drop support for IE11 and 10+ year old phones like the iPhone 4S, then you get access to WebAssembly. With WASM you can get a bunch of custom algorithms to be quite fast. The Argon2 browser WASM library claims to be only about 10x slower than optimized native code. [2]

It's not perfect, but it isn't as bad as it used to be with just pure JavaScript.

--

[1] https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_...

[2] https://github.com/antelle/argon2-browser

Yeah, I can't really justify using this in browsers because their capabilities vary so widely. Logging in shouldn't take 10 minutes on a 5-year-old feature phone.

But I can see using a split KDF for some high-security system where the clients all have a known minimum spec.

> that the server side hash is still at least independently expensive enough

That is useless if a hash of the passphrase is sent by the client. The input space is evenly distributed over all hash values, so a dictionary attack is no better than sending all possible hashes directly (brute force).

A single round of server side hash suffices here.

It does prevent inadvertent logging of passwords, though: no piece of software on the server side will have the user's password in memory at any point. Which does mean the user's actual password (if they're reusing passwords) stays more secure (by "more secure" I mean "has a lower probability of leaking to a malicious actor", not necessarily "has some additional security properties").
Ah yes that's a good point. It can still leak the "password" that's used for authentication so it doesn't protect their account on that service but logs wouldn't leak the original password that might be reused elsewhere so it could protect their account on other services marginally more.
If we add client side hashing, it does nothing to prevent hash replay attacks. Agreed.

However, it prevents the attacker from immediately trying the same raw password on other sites (i.e. credential stuffing). They would need to perform additional offline attack of the first hash. This adds cost to something that would have previously been trivial.

Given that about 65% of users reuse passwords and 76% don't even use a password manager [1], I think that slowing down credential stuffing attacks is important.

Protecting against some attacks is valuable even if you don't protect against all attacks. Layered security.

1. https://services.google.com/fh/files/blogs/google_security_i...

Everything you explain points at no need to do client side hashing: what exact attack vector would be stopped by having it? (The only thing you bring up is reuse of passwords, but then you explain how that would be easily exploited if server was compromised, and it's even easier if client is)

I would imagine most developers unfamiliar with encryption would assume that client hashing is sufficient and not bother with server side hashing which is the only one that ensures privacy in case of compromise on the server side (nothing really stops client side compromise).

I assumed the point would be to store all of the salt values ever used server side and never allow reuse of a salt. That way if the hash is captured but has already been used then it is useless.
WASM will do what you want.

You can certainly have client side JS produce a hash using some other credential as the salt and then encrypt the information server side. What you really lose is the ability to do low cost comparison, because most password hashing is done deterministically. Instead you would need to retrieve the record, decrypt with the server side key, and finally compare.

Use client TLS certificates and let the browser handle credential management, including at-rest encryption. Or use FIDO2 hardware. There are many options available but for probably familiarity reasons the vast majority of users and browsers didn't go down that path, and neither did website owners.
I've only seen client certs used in contexts where an IT department assigns them to employees. Has anyone had success with these on public facing websites?

Extra hardware seem cool, but I've also rarely seen people using them. I'm guessing the added cost is a deterrent.

before the current wave of password managers most people didnt use more than one password. they made it easy enough for people to use. now theyre everywhere. the things you list above are the new oddball ui issues, smooth them out a but amd people will use them too.
I see lots of folks in the tech industry using password managers and using the password manager to create and fill passwords. I've also seen people use password managers to remember the ~1 password they self created and consistently reuse.

I'd like to see more password manager use, but changing user behavior is hard. Google suggests <25% of users do so.

https://services.google.com/fh/files/blogs/google_security_i...

But then malware on the user’s machine could catch that password.

There is no 100% solution to this.

Different problems need different solutions. I don't think I've ever seen a solution that solves 100% of all problems. This doesn't stop me from trying to improve security where I see opportunity.
Malware on the user's machine could catch the password in either case.
Which to me begs the question: then why hash client-side at all? What are the threats it protects against?
This sort of scheme is common so that you do not have to share the encryption key with the provider. You derive two keys from your plaintext password: one used for authentication and one used for encrypting / decrypting the blob. This way, Lastpass can authenticate you without having to see the key to decrypt your data.

Not sure the specifics of how lastpass implements this but this is a really common approach for end-to-end encrypted apps.

I wouldn't say it's so you don't have to share the encryption key with your provider (you achieve that with a separate encryption key), but rather so you can use a single memorable secret for both login to the provider and local encryption.

As in, the idea is that it is used to save you from having two secrets which might be more or less easy/hard to remember.

It's a UX improvement (which might be a security imorovement on average too).

Oh I see, so the master password is like a seed for multiple things: the password hash, but also e2e encrypting the passwords.

That makes complete sense, thank you for the answer.

No problem! If anyone is curious for more on this pattern, Firefox Sync had a great blog post breaking down their implementation: https://hacks.mozilla.org/2018/11/firefox-sync-privacy/
The client-side hashing is actually the part that keeps your master password a secret, from everyone including LastPass yourself. This makes sure that LastPass cannot decrypt your passwords despite them being stored there. The server-side part on the other hand is severely misimplemented and not much good to anyone. This is one of the issues I’ve written about here: https://palant.info/2018/07/09/is-your-lastpass-data-really-...
From what I understand: you still need the master password locally to decrypt the individual passwords. A hash won't provide that. All you're doing here is logging into LastPass.
Hashing client side has a non-cryptographic benefit of making all passwords a fixed length. This, in turn, helps avoid accidents with bcrypt misuse.