Can't reproduce expected cryptography with vaultwarden

Hi everyone!

I’m currently working on a ansible collection module to manage vaultwarden “natively” with ansible without having to use the bitwarden “bscli” client.

However, I’m having hard time to reproduce the cryptography required for that purpose.

I’m creating the cryptography using the following sources:
Bitwarden/Vaultwarden infos (opendev.org)

However, it doesn’t work as expected as I’m unable to generate a master passwor hash that match the one on vaultwarden DB.

First of all I have few questions regarding how vaultwarden do create the master_password_hash saved on DB:

  1. Because vaultwarden store the salt as a blob on SQLite DB I do extract it assuming it’s a hexadecimal string using:
    sqlite> select hex(salt) from users where email='user@acme.com';
    Am I correct?

  2. Same question for the master_password_hash

  3. It looks like the official bitwarden client use a b64 encoded master_key string to generate the master_password_hash:
    https://github.com/bitwarden/clients/blob/master/libs/common/src/platform/services/crypto.service.ts#L256
    Did I correctly understood the return of this hashMasterKey function?

  4. Same question for the master_password_hash function.

Here is my current workflow, did I missed something or am I on the right path?

  1. Create a master_key derivated from master_password and email using:

Function: PBKDF2-HMAC-SHA256
Payload: master_password
Salt: email
iteration: 100 000
key length: 32
hash protocol and function: hmac-sha256

  1. Create a master_password_hash from master_key and master_password as salt using:

Function: PBKDF2-HMAC-SHA256
Payload: master key (not stretched, string, no B64)
Salt: password
iteration: 1
key length: 32
hash protocol and function: hmac-sha256

  1. Trying to create a master_password_hash salted as on DB using DB information using:

Function: PBKDF2-HMAC-SHA256
Payload: master password hash
Salt: random bytes → using string stored in DB transform as bytes array.
iteration: 100 000
key length: 32
hash protocol and function: hmac-sha256

Now my issue is, when I do compare my locally computed master_password_hash it doesn’t match the one from DB.

I tried to use a b64 encoded master_password_hash as input instead of the string master_password_hash, it doesn’t match neither.

Here is a simple python snippet that I use to recreate/test this workflow for anyone interested:
vaultwarden_sample (opendev.org)

Any help on this topic would be greatly appreciated, especially @nozza87 if you ever have your password recovery/brute force snippet to share.

Anyone having any ideas?

I can’t get my head around why by respecting what’s on the client source code AND the server part of vaultwarden.

I don’t get a matching master_password_hash between what my code is computing and what the server is.

I’ve tested many options such as to use b64encoded master key and/or master password hash and it doesn’t work neither :-/

Not really familiar specifically with this but here are a few resources that may be of help if you didn’t already have them.

Quite a few more resources and write ups too from the official Bitwarden team regarding the encryption, hashing, salt, etc. Used to derive all this

Hope it helps :slightly_smiling_face:

Hi @cksapp thanks for your reply!

If you look at the links that I’ve put on my first message, those are part of it and indeed what is disturbing as none of those models works if you try to implement them.

If you look at bitwarden clients source code for instance they do introduced a base64 return for the master key derivation that isn’t noticed anywhere on those papers unfortunately :-/

Vaultwarden uses byte representation instead of the plain text received of the master password hash received from the clients and uses PBKDF2 with 600_000 iterations on it.

If you would search this forum, there is some who tried the same for brute forcing a forgotten password.

Yes, I’ve seen that, it’s not specifically vaultwarden that use the byte representation, python’s PBKDF2 from cryptodome is also using that and I’m also used it as it to compute it.

However, I think there is something odd at the client side of bitwarden as if you simply code your workflow steps using what’s explain there, then it doesn’t work.

Their own source code seems to explain that they b64 encode the hash and return it, which then in turn vaultwarden do byte translate and iterate over 600k or 100k times + salt depending on your setting and vaultwarden server version. Mine is using 100k so far as stated on DB for few accounts that were created before the upgrade to 600k.

I’ve carefully read @nozza87 thread you’re mentioning too but unfortunately he didn’t responded yet.

I would really appreciate anyone looking at the client sources that have linked on my first link as I’m a bit lost on bitwarden monorepo and extensive TypeScript usage.

From my investigation it seems that:
1°/- They always return b64 encoded hashes from the derivation function, would it be for master key creation AND password hash.

2°/- Unfortunately when I try to do it that way too and then calculate the expected master_password_hash using DB salt and bytes representation I can’t get the same master_password_hash that the one stored on vaultwarden.

3°/- I’ll probably have to compile my desktop client from upstream sources with debugging modification in order to find out what’s the client is really sending to the server as the payload.

Thanks a lot at least for your kind answer and care about this topic that I really can’t get any satisfying answers anywhere :-/ Even Bitwarden communication channels doesn’t work, I really feel like if I missed something somewhere that everybody else out there already clicked xD

Have you also checked GitHub - corpusops/bitwardentools: bitwarden python api client and additional tools like for migrating from vaultier to bitwarden (bitwarden_rs) ?

Also first step would be to see if you can generate the exact same hash as the clients return of course, so use the browser developer tools and see what it sends Vaultwarden.

Or what there example page generates for example.
https://bitwarden.com/crypto.html

Oooh didn’t knew about that tool, I’ll have a look thanks!

Yes the first step know that I validated that I can’t compute a matching hash will be to catch a client made master_password_hash from a compiled client with debug activated.

I’m kinda astonished that bitwarden even on its most recent responses keep telling people the workflow is ABC when indeed it seems to be A A’ B B’ C

Yes I did had a look at this page too but even from that specific page using my testing user the page can’t reproduce the expected hash, I think they didn’t updated it since a long time.

It works perfectly for me. At least when using PBKDF2. It will fail when using Argon2, since that is not (yet) implemented into that page. Also, check what the response is of the /identity/accounts/prelogin call, this will contain the values needed to generate the correct key, like which hashing algo and how many iterations.

I’ll try the whole thing again in few weeks/months for now I’m unfortunately stuck with couples of upgrades on our infrastructure components he he he.

But many thanks for your kind answers and hints, I’m it will help.