Cryptographic details about bitwarden

Hi community,

I’m currently writting an ansible native plugin implementation for bitwarden secret-manager at work where we’re using bitwarden secret-manager.

But I’m having issues with missing informations about used cryptographics that I can’t really found even on clients source code or extract from the security whitepaper.

Here are my assumptions from the white paper and clients source code:

Master password to derivated master key:

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

Derivated master key to stretched derivated master key:

Function: HKDF-HMAC-SHA256
Payload: master key
Salt: none
key length: 64
hash protocol and function: hmac-sha256

Client side master password hash:

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

Server side master password hash:

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

Let’s take the previous parameters and convert them into raw python:

Input parameters:

email = [email protected]
password = secret

Derivated master key:

master_key = PBKDF2(password, bytes(email, ‘utf-8’), 32, count=100000, hmac_hash_module=SHA256).hex()

hash result: da70641e147501a960065d4c047f696e77cc4046df18b3d821e53269db02717d

Stretched derivated master key:

hkdf_salt = ‘’
stretched_master_key = HKDF(bytes(master_key, ‘utf-8’), 64, bytes(hkdf_salt, ‘utf-8’), SHA256).hex()

hash result: 8882fcaee2cfee94755bd52008ce7704c8d82b4fb40b2f73ba2dcbd2b7faaba565ed9c625f0fd90c38ea875646e5e99048897669684aa7c544a5c305cc8500c6

Client side master password hash:

password_hash = PBKDF2(master_key, bytes(password, ‘utf-8’), 32, count=1, hmac_hash_module=SHA256).hex()

hash result: 3889c084593d145fe020669cc838b733c26e47d6d3dff0d32392def084ac226a

Ok, here is where things gets weird, if I try to validate the Server side password hash using the DB stored salt I can’t get the same password hash as the one stored on DB for comparison.

Server side master password hash:

salt = D5AF4483678B7806E4B310AB76DBE47970E597F582F9F6B74E4CCA8462F058C4
server_password_hash = PBKDF2(password_hash, bytes(salt, ‘utf-8’), 32, count=100000, hmac_hash_module=SHA256).hex()

hash result: 22c4de6ccadc4572e68d23e251484877801a6200e6d02305bf14b8f8616f8c0e
extracted server hash: A1B5A53993D2CD253662375201B5DF61B14D7B630445ED328E7B245CF4C4C6FF73A5DB5DF37B2026717F9106F171D8FB588B4721710803F256144F95001B05A0

First we can see that the server side password hash seems to be a 64 bytes long SHA512
So let’s try the same hash function with revised parameters:

Revised server side master password hash:

salt = D5AF4483678B7806E4B310AB76DBE47970E597F582F9F6B74E4CCA8462F058C4
server_password_hash = PBKDF2(password_hash, bytes(salt, ‘utf-8’), 64, count=100000, hmac_hash_module=SHA512).hex()

hash result: 4d53983a38d0059c3b4e9587697f9afad06e2b83169234469cacbfe6318827d309d5667174e7a2e9ce5751adecc714b194533543a5c7b07ead7c083955e962a0
extracted server hash: A1B5A53993D2CD253662375201B5DF61B14D7B630445ED328E7B245CF4C4C6FF73A5DB5DF37B2026717F9106F171D8FB588B4721710803F256144F95001B05A0

Looks closer, but yet not identical.

I betted on the 64 bytes SHA512 hash as in client source code, the crypto service function is actually able to use both SHA256 and SHA512 hash function: crypto_function

If anyone want to reproduce the test, here is the python gist that I wrote (I know it’s poorly written python but that’s not what I’m focusing on here): python_bitwarden_validator

I’ve done the same process using openssl and end up with the same results so at least the functions themselves are similarly implemented.

So, if anyone is having a clue of what’s going on, I’m all ears out!

@Fl1nt Welcome to the forum!

If you are asking about the Bitwarden’s Secrets Manager (currently available only in beta), as opposed to the Password Manager, please confirm so that I can move this thread into the Secrets Manager Forum.

You may find Bitwarden’s Interactive Cryptography Tool to be helpful (you can also view the source code of that page, which may provide some additional insight). I believe that all cryptographic keys used by Bitwarden are always 32 bytes in length (256 bits), but they are sometimes concatenated with a 32-byte MAC key for validation purposes — which produces a total length of 64 bytes (512 bits). This is why the Stretched Master Key has a length of 64 bytes.

1 Like

Hi @grb Thanks!

No, I’m really talking about the Password Manager.

Thanks for the Interactive Tool! First thing that I remark is that all keys exposed on the ICT are base64 encoded but the whitepaper and the clients source code do not.

For now, I’m just focusing on the master password, master key and master password hash from client and server side as it help me validate everything written on bitwarden documentation.

The master key for instance isn’t supposed to be stretched using any MAC Key from the symmetric key, it’s supposed to be using a HKDF function without salt.

So I’m a bit confused in here ^^
I’ll check if my raw hash are matching the b64 based one from the IC Tool.

Thanks!

Alright, I’ve check out the ICT, I’ll rework my python to generate mac and enc symmetric key to be used as salt info for the HKDF function as it seems it’s what the ICT is doing.

TBH: I’m kinda astonished that ICT and client tool use symmetric keys as a way to iterate over block on the HKDF expand function as HKDF specify that the info (salt) should be a non-secret, reusable value.

I’ll let this topic open and come back to you with a working python implementation for anyone willing to get a way to validate its code and assumptions.