In the aftermath of the LastPass breach it became increasingly clear that LastPass didn’t protect their users as well as they should have. When people started looking for alternatives, two favorites emerged: 1Password and Bitwarden. But do these do a better job at protecting sensitive data?
For 1Password, this question could be answered fairly easily. The secret key functionality decreases usability, requiring the secret key to be moved to each new device used with the account. But the fact that this random value is required to decrypt the data means that the encrypted data on 1Password servers is almost useless to potential attackers. It cannot be decrypted even for weak master passwords.
As to Bitwarden, the media mostly repeated their claim that the data is protected with 200,001 PBKDF2 iterations: 100,001 iterations on the client side and another 100,000 on the server. This being twice the default protection offered by LastPass, it doesn’t sound too bad. Except: as it turns out, the server-side iterations are designed in such a way that they don’t offer any security benefit. What remains are 100,000 iterations performed on the client side, essentially the same protection level as for LastPass.
Mind you, LastPass isn’t only being criticized for using a default iterations count that is three time lower than the current OWASP recommendation. LastPass also failed to encrypt all data, a flaw that Bitwarden doesn’t seem to share. LastPass also kept the iterations count for older accounts dangerously low, something that Bitwarden hopefully didn’t do either (Edit: yes, they did this, some accounts have considerably lower iteration count). LastPass also chose to downplay the breach instead of suggesting meaningful mitigation steps, something that Bitwarden hopefully wouldn’t do in this situation. Still, the protection offered by Bitwarden isn’t exactly optimal either.
Edit (2023-01-23): Bitwarden increased the default client-side iterations to 350,000 a few days ago. So far this change only applies to new accounts, and it is unclear whether they plan to upgrade existing accounts automatically. And today OWASP changed their recommendation to 600,000 iterations, it has been adjusted to current hardware.
Edit (2023-01-24): I realized that some of my concerns were already voiced in Bitwarden’s 2018 Security Assessment. Linked to it in the respective sections.
How Bitwarden protects users’ data
Like most password managers, Bitwarden uses a single master password to protect users’ data. The Bitwarden server isn’t supposed to know this password. So two different values are being derived from it: a master password hash, used to verify that the user is allowed to log in, and a key used to encrypt/decrypt the data.
If we look at how Bitwarden describes the process in their security whitepaper, there is an obvious flaw: the 100,000 PBKDF2 iterations on the server side are only applied to the master password hash, not to the encryption key. This is pretty much the same flaw that I discovered in LastPass in 2018.
What this means for decrypting the data
So what happens if some malicious actor happens to get a copy of the data, like it happened with LastPass? They will need to decrypt it. And for that, they will have to guess the master password. PBKDF2 is meant to slow down verifying whether a guess is correct.
Testing the guesses against the master password hash would be fairly slow: 200,001 PBKDF2 iterations here. But the attackers wouldn’t waste time doing that of course. Instead, for each guess they would derive an encryption key (100,000 PBKDF2 iterations) and check whether this one can decrypt the data.
This simple tweak removes all the protection granted by the server-side iterations and speeds up master password guessing considerably. Only the client-side iterations really matter as protection.
What this means for you
The default protection level of LastPass and Bitwarden is identical. This means that you need a strong master password. And the only real way to get there is generating your password randomly. For example, you could generate a random passphrase using the diceware approach.
Using a dictionary for 5 dice (7776 dictionary words) and picking out four random words, you get a password with slightly over 50 bits of entropy. I’ve done the calculations for guessing such passwords: approximately 200 years on a single graphics card or $1,500,000.
This should be a security level sufficient for most regular users. If you are guarding valuable secrets or are someone of interest for state-level actors, you might want to consider a stronger password. Adding one more word to your passphrase increases the cost of guessing your password by factor 7776. So a passphrase with five words is already almost unrealistic to guess even for state-level actors.
All of this assumes that your KDF iterations setting is set to the default 100,000. Bitwarden will allow you to set this value as low as 5,000 without even warning you. This was mentioned as BWN-01-009 in Bitwarden’s 2018 Security Assessment, yet there we are five years later. Should your setting be too low, I recommend fixing it immediately. Reminder: current OWASP recommendation is 310,000.
Is Bitwarden as bad as LastPass?
So as it turns out, with the default settings Bitwarden provides exactly the same protection level as LastPass. This is only part of the story however.
One question is how many accounts have a protection level below the default configured. It seems that before 2018 Bitwarden’s default used to be 5,000 iterations. Then the developers increased it to 100,000 in multiple successive steps. When LastPass did that, they failed upgrading existing accounts. I wonder whether Bitwarden also has older accounts stuck on suboptimal security settings.
The other aspect here is that Dmitry Chestnykh wrote about Bitwarden’s server-side iterations being useless in 2020 already, and Bitwarden should have been aware of it even if they didn’t realize how my research applies to them as well. On the other hand, using PBKDF2 with only 100,000 iterations isn’t a great default today. Still, Bitwarden failed to increase it in the past years, apparently copying LastPass as “gold standard” – and they didn’t adjust their PR claims either:
Users have been complaining and asking for better key derivation functions since at least 2018. It was even mentioned as BWN-01-007 in Bitwarden’s 2018 Security Assessment. This change wasn’t considered a priority however. Only after the LastPass breach things started moving, and it wasn’t Bitwarden’s core developers driving the change. Someone contributed the changes required for scrypt support and Argon2 support. The former was rejected in favor of the latter, and Argon2 will hopefully become the default (only?) choice at some point in future.
Adding a secret key like 1Password would have been another option to address this issue. This suggestion has also been around since at least 2018 and accumulated a considerable amount of votes, but so far it hasn’t been implemented either.
On the bright side, Bitwarden clearly states that they encrypt all your vault data, including website addresses. So unlike with LastPass, any data lifted from Bitwarden servers will in fact be useless until the attackers manage to decrypt it.
How server-side iterations could have been designed
In case you are wondering whether it is even possible to implement server-side iterations mechanism correctly: yes, it is. One example is the onepw protocol Mozilla introduced for Firefox Sync in 2014. While the description is fairly complicated, the important part is: the password hash received by the server is not used for anything before it passes through additional scrypt hashing.
Firefox Sync has a different flaw: its client-side password hashing uses merely 1,000 PBKDF2 iterations, a ridiculously low setting. So if someone compromises the production servers rather than merely the stored data, they will be able to intercept password hashes that are barely protected. The corresponding bug report has been open for the past six years and is still unresolved.
The same attack scenario is an issue for Bitwarden as well. Even if you configure your account with 1,000,000 iterations, a compromised Bitwarden server can always tell the client to apply merely 5,000 PBKDF2 iterations to the master password before sending it to the server. The client has to rely on the server to tell it the correct value, and as long as low settings like 5,000 iterations are supported this issue will remain.
Thanks for this article. Did you inform Bitwarden of these finding and did you get a response?
Yes, Bitwarden replied on Mastodon: https://fosstodon.org/@bitwarden/109738655508698076
They are also discussing it in their community forum, less friendly reply here: https://community.bitwarden.com/t/increasing-the-default-number-of-pbkdf2-for-existing-accounts/49550
This seems to be the case. I've started using the account early 2020, but don't remember whether it's older. The iterations were set at 5000. Setting them to 2000000 didn't cause a noticeable slowdown.
Wouldn't 2fa feature with bitwarden be the solution?
Not in the scenario that happened to LastPass. As I said, you rather need a strong master password.
seems like that the server side need to do so to allow both new and old accounts working properly
I guess a self-hosted BitWarden or Vaultwarden have slightly different properties and configurability.
Thanks for sharing!
Well, what you say is not essentially correct as well.
100,000 iterations on client side and 100,000 iterations on server side still means, 200,000 iterations are needed. If attacker gets the data and tries to guess the password using bitwarden servers, 100,000 on server side will slow him down as well. And there will be ratelimit to prevent DDoS attack. If attacker has the data, he can brute force combinations, he will still have to use 200,000 iterations for that.
Please do some research before posting such material.
No affiliated with bitwarden in anyway.
Really, why not read the blog post before posting such comments? The server-side iterations do not apply to the encryption key (“stretched master key” in their diagram). So testing guesses can be done against the protected symmetric key, only 100,000 iterations here.
Would you happen to know if Vaultwarden suffers from the same issue, or did they go for a much higher iterations value?
Given that Vaultwarden is merely an alternative server implementation which has to be compatible with official Bitwarden clients – yes, most certainly the same issue. Note that the number of client-side iterations is being configured by the client (e.g. when the account is created), the server has no say here.
Where does it say 310000 iterations? The linked OWASP page says 210000 for 512 and 600000 for 256 which Bitwarden uses?
As it says in the update, OWASP changed their recommendation to 600,000 iterations a few hours after this article was published.
The whole usage of PBKDF2 is questionable, and I have voiced that opinion consistently. I don't mind 100k PBKDF2 itself, because PBKDF2 is a joke, given what we know nowadays about bitcoin mining. We have extremely efficient ASICs that can hammer SHA256 very easily and produce PBKDF2 with brute-force in no time. I have no doubts that there are specialized chips and hardware out there to brute-force PBKDF2. It's not hard at all to design your own circuits around available ASICs chips.
Why don't these companies use Argon2 or similar hash functions is beyond me. There are implementations for Argon2 on all platforms nowadays.
Recently I found an issue in the way they compute the strength of passwords entered by users, but they seem to have ignored my report and changed it into a "feature request". https://community.bitwarden.com/t/password-strength-testing-tool-with-non-english-dictionaries/47260
Yes, zxcvbn has significant shortcomings when it comes to assessing password strength. While it’s a good start, Bitwarden’s 2018 Security Assessment recommended helping users choose passphrases. Sadly, this suggestion has been ignored.
Just a note that Dashlane has been using Argon2 for a few years. We also migrated users from PBKDF2 over to Argon2.
I work at Dashlane.
This is possible to do as an end user. Not seen any instructions above. Just updated mine to 650,000 in online Web Vault under Account Settings | Security | Keys, set new value then press "Change KDF". Requires the master password to be re-entered and to log back in to all Bitwarden copies (make sure you have your master password as finger print login etc will need to be re-enabled). Should be good for a few more years until OWASP change the recommendation of PBKDF2-HMAC-SHA256: 600,000 iterations. There's a little more delay when logging in but not that noticeable. (Bitwarden recommend 100,000 and increasing in 50K increments on slowest device). I used Diceware 8 word password and YubiKey for 2FA.
Ideally, Bitwarden will switch to Argon2 soon. Because increasing PBKDF2 iterations isn’t a race you can win.
Wladimir, what password manager and settings you would recommend for personal use? Self hosted?
Thank you for your blog post.
I’m not good at recommendations. However, if you really want my advise, see https://infosec.exchange/@WPalant/109739825281157041.
Why don't we go for enpass password manager instead of 1Password and Bitwarden? Have you got time to review enpass as an alternative to both password managers mentioned above?
There are literally hundreds of password managers, and most I’ve never looked into. Sadly, enpass is one of them.
Is an 18 character pwd that uses all special characters less secure than a diceware one?
I'd surely prefer to go the diceware way, given that it's much easier to memorize words.
A randomly generated 18 character password: no. A human-chosen 18 character password: yes, most likely. Humans are incredibly bad at choosing secure passwords.
Is there any calculator anywhere to compare randomly generated pwd with special characters to diceware pwd's? If I can maintain the same level of security I may switch to diceware. Getting old, I may forget the randomly generated 18 chars pwd...
There is https://lowe.github.io/tryzxcvbn/. Every 3.9 of the
guesses_log10value correspond to one diceware word. So for example
guesses_log10being 15.6 means that you have the equivalent of four diceware words.
Mind you, zxcvbn tends to overestimate the complexity of passwords. There are many password patterns that it won’t detect, particularly when it comes to non-English passwords.
vaultwarden (an unofficial Bitwarden compatible server written in Rust) has been updated two hours ago via https://github.com/dani-garcia/vaultwarden/commit/2d8c8e18f74726303f7789af9731e8a80ce05610 –
Change default Password Hash KDF Storage from 100_000 to 600_000 iterations
Update Password Hash when the default iteration value is different
Validate client-side KDF to prevent it from being set lower than 100_000
Glad I found this! My iterating were at 5000 as well. Had to bump those numbers up!
What about Microsoft, Apple and Google password managers? I think the majority uses these.
You mean the ones built into browsers? I looked into some of these a while ago: https://palant.info/2018/03/13/can-chrome-sync-or-firefox-sync-be-trusted-with-sensitive-data/
Mine set to 100000, created in 2021. You could easily change that in web interface. Set it to 300000 for now, maybe bump this higher or they somehow migrate this to now default 600000
Given all that info, what are the current password managers with safe default settings? i.e. something that can be recommended to multiple users
Sorry, I’m really the wrong person to ask for password manager recommendations.
1Password is only using 100,000 iterations.
Read their defense of this here: https://1password.community/discussion/136293/1p-pbkdf2-iterations-are-less-than-recommended-by-owasp-please-do-better/p1
Yes, they are correct. The secret key changes the calculation considerably. Having this high-entropy value as part of the encryption scheme makes cracking the encryption unrealistic regardless of the key derivation.
Was there an announcement about the recent changes to iterations from 100,000 to 350,000? Or the pull request being merged?
Here is the pull request: https://github.com/bitwarden/mobile/pull/2305
Hi, let's say your Bitwarden vault is protected by a strong master password and a physical security key like YubiKey. Would that YubiKey as a second factor protect you from an attack like the one that happened to LastPass?
I don’t think that YubiKey matters here. My understanding is that it is only used as a second authentication factor for login, but vault encryption still depends entirely on the master password. So attackers who bypass the login by copying the vault data directly from the servers don’t care about your YubiKey.
It still helps in case your Bitwarden credentials leak by some other means. Without it, the attackers won’t be able to log into your account, meaning that they won’t be able to download the vault data.
I signed up for Bitwarden about 0400 UTC on 1/26/2023. My account used only 100,000 iterations so them saying it was fixed on Monday is incorrect. When changing to 600,000 iterations should I also rotate the symmetric encryption key so vault data will be re-encrypted? Do both steps need to happen to make the iterations more secure?
Are you using the Bitwarden app? This change probably didn’t make it there yet.
No, rotating the encryption key isn’t necessary. This step is only necessary if your account was compromised at some point – merely changing the password isn’t sufficient to lock out the perpetrators then.
I signed up on the website. After playing with the website for a few hours I downloaded the Android app and started using it. I logged out of all instances, changed the iterations, then relogged in on my devices/browsers. I have no reason to believe my password was compromised. Thanks for confirming I'm safe
hi, thanks for posting this very relevant info. I have recently spent hours and hours repairing the damage caused by the LastPass debacle. Ain't taking no chances with BW (might move on to 1Password though because of their secret key) and set my iterations to the max level of a whopping 2.000.000 combined with a ridiculous password of 921 bits.
You should update your article: Bitwarden has announced on Mastodon that it is increasing iterations to 600,000, exploring backward compatibility, is working on Argon2 support, and is double-encrypting data at rest on their servers (encrypting vault w/ your key; encrypting hash of master password with their key).
There is nothing new here. They already changed the default iterations to 350,000, they are merely moving to match OWASP recommendation. The important question is what they will do with existing accounts, something they are unable to tell.
The article already says that they are working on Argon2 support, this effort started a few weeks ago already. Whether Argon2 will become the default at some point and whether existing accounts will be upgraded are the important questions here as well, as of yet unanswered.
“Double-encryping data” is meaningless until we see the actual approach used.
LastPass also copied production data to test environments, which also made sure that any alleged security control (like access control, separation of duties, etc.) they may have had in production, was nullified. All of this tear down on password managers will bear its fruits. I am absolutely glad of blog posts like this one. We will finally unravel that all of this claims of “encryption” need to be scrutinised much better and not taken at face value.
I'm developing an app with similar encryption and authentication schemes as Bitwarden. Do you think Dmitry's solution is suitable given a good number of iterations? This was my authentication process up until now: https://imgur.com/a/eNeHpBf
This scheme has the same issue as Bitwarden, the login hash enjoys much better protection than the encryption key. On the client side, it really isn’t necessary to do a complicated derivation from the master key to login hash, HKDF will do. Instead, you should look into improving client-side Argon2 parameters for master key derivation. 8 MB of memory in particular seems rather low. Memory is cheap even on low-end devices and you can probably afford spending at least 32 MB.
On the server side, Jeremi Gosney argues that bcrypt is superior to Argon2: https://infosec.exchange/@epixoip/109598238452730957. So you might want to consider switching here.
But – yes, I think that Dmitry’s solution will work to make additional server side protection actually useful. But one probably shouldn’t take this too literally and do the key derivation effort twice. Instead, one would derive the serverEncKey and then hash it once more (e.g. via HKDF) to get the verifier.
Following up on my pwd equivalency, I've just checked the tool that you've recommended and it ended up with 18 guesses, so an equivalency of 4.5 dice words. So, using 5 dice words would end up being a better option. Hmm, gotta think this through, but I may switch my Bitwarden to 5 dice words.
Did I get my assessment right? Not sure if the 18 chars if my pwd ending up as the guesses was just a coincidence, or if I've done something wrong.
Presumably, you mean
guesses_log10value. Yes, that value being 18 translates into 4.6 diceware words (5 dice used, meaning 7776 words in the dictionary). A five words password would definitely be better, already because zxcvbn cannot detect all the patterns in your password, so 10^18 guesses should be considered the upper limit of your password’s complexity. A five word diceware password on the other hand will definitely require more than 10^19 guesses.
also set iteration value in bitwarden to the max of 2.000.000 . No noticable delay in opening and/or using the vault as yet (2 iPhones and an iPad - nothing fancy).
Regards and kudos for your work
"Even if you configure your account with 1,000,000 iterations, a compromised Bitwarden server can always tell the client to apply merely 5,000 PBKDF2 iterations to the master password before sending it to the server."
I'm confused, so what's the advantage of increasing the KDF iterations from our account settings?
Or is this in case of a compromised Bitwarden server that is modified to always tell the client to apply merely 5,000 PBKDF2 iterations to the master password before sending it to the server?
It’s a different scenario. Increasing iterations helps if merely the data stored on the server is stolen (e.g. from a backup like with LastPass). But a server made to behave in a different way can still subvert this protection, it’s a separate issue.
I've been using Bitwarden since 2019, after seeing this article today, I logged onto the web vault and I saw that my KDF iterations were set to a horrendous 100k. Thank you for illuminating this problem and the solution. I've set it to 600k now. My master password is fine I think, although I will take a closer look soon.
Are you familiar with Enpass? Do you know if they do better than Bitwarden regarding security?
Sorry, I have no idea.
Great write up. You didn't mention anything about the fact that the master password is also the account password, thus it travels through the Internet protected only by SSL (even though it's hashed). Is that a concern?
It’s hashed, so it is protected – even though the server-side iterations don’t apply in that scenario. It’s only an issue if the Bitwarden server is compromised. And in that scenario the bigger concern is that the server can downgrade the protection to 5,000 PBKDF2 iterations (see last paragraph of the article).
Bitwarden latest update Feb 2023, included Argon2Id support as follows : "By default, Bitwarden is set to allocate 64 MiB of memory, iterate over it 3 times, and do so across 4 threads. These defaults are above current OWASP recommendations"
OWASP recommendation : "Use Argon2id with a minimum configuration of 19 MiB of memory, an iteration count of 2, and 1 degree of parallelism."
Do you think, default Bitwarden Argon2id would be sufficient with strong 5 letter random passphrase (Even random that of Diceware / EFF list) ?
Reference https://bitwarden.com/help/kdf-algorithms/ https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#introduction
I’ve written about it here: https://palant.info/2023/01/30/password-strength-explained/
BitWarden are now encrypting the data as it is saved into the database, decrypted as it read from. I think key is symmetric and stored in Azure HSM.
Does this solve the issue of server side iterations?
I don’t know, hard to tell. But I know that Bitwarden improved client-side protection: Argon2 is now available, and the parameters are proper. This definitely solves the issue. Question is whether they manage to migrate existing accounts automatically or whether they will once again rely on people taking action themselves.