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!