PBKDF2
PBKDF2¶
- Variable Key derivation function
- Higher iteration count makes the hash slower but more resilient to password cracking attacks
- Lower iteration count makes the hash faster but less resilient to password cracking attacks
Implementation¶
Actual Usage:
import hashlib
dk = hashlib.pbkdf2_hmac('sha256', b'password', b'salt', 100000)
print(dk.hex())
#0394a2ede332c9a13eb82e9b24631604c31df978b4e2f0fbd2c549944f9d79a5
How it works:
import hashlib
def fixedlen_xor(input1, input2):
assert(len(input1) == len(input2))
return bytes([input1[i] ^ input2[i] for i in range(len(input1))])
def int_to_bytes_length(i_data, length, be=True):
if be:
return (i_data).to_bytes(length, byteorder='big')
else:
return (i_data).to_bytes(length, byteorder='little')
def hmac(key, message, hash_function):
#Get hash_function block_size
block_size = getattr(hash_function(), 'block_size')
# Check if key is longer than block size.
if len(key) > block_size:
# IF it is then hash the key. This makes the keysize the same as the output of the hashfunction
key = hash_function(key).digest()
# IF key is shorter
if len(key) < block_size:
# Pad the key to blocksize
key = key + b"\x00" * (block_size - len(key))
#print(key, len(key), block_size)
# Create Keys
o_key = fixedlen_xor(key, b"\x5c" * block_size)
i_key = fixedlen_xor(key, b"\x36" * block_size)
#Hash i_key and message
tmp = hash_function(i_key + message)
#Hash the o_key and the hashed output of above
return hash_function(o_key + tmp.digest()).digest()
def pbkdf2(password, salt, itterations=1000, keylength=24, hashobj=hashlib.sha1):
#Use the password as the HMAC key
#mac = hmac(password, b"", hashobj)
#digest_size = getattr(hashobj(), 'block_size')
#print(digest_size)
key = b""
block_num = 1
while len(key) < keylength:
new_hash = hmac(password, (salt + int_to_bytes_length(block_num, 4)), hashobj)
xor_data = new_hash
#-1 for itterations since we already did one
for idx in range(itterations-1):
#Generate new hash
new_hash = hmac(password, new_hash, hashobj)
#Xor hash with running total
xor_data = fixedlen_xor(xor_data, new_hash)
#Do XOR on array and add to key
key += xor_data
#Update block number
block_num += 1
return key[:keylength]
if __name__ == '__main__':
#test = hmac(b'key', b'some msg', hashlib.sha256)
#print(test)
#32885b49c8a1009e6d66662f8462e7dd5df769a7b725d1d546574e6d5d6e76ad
#print(pbkdf1(b'password', b'salt', 1, 20, hashlib.sha1).hex())
#47e97e39e2b32b15eb9278e53f7bfca57f8e6b2c
print(pbkdf2(b'password', b'salt', 1, 20, hashlib.sha1).hex())
#0c60c80f961f0e71f3a9b524af6012062fe037a6
print(pbkdf2(b'password', b'salt', 10000, 20, hashlib.sha1).hex())
#a2c2646186828474b754591a547c18f132d88d74
Attacks¶
HMAC Prehash with Long password¶
Here is an interesting edgecase. The PBKDF2 function uses the HMAC function as shown above.
The HMAC function will hash the input_key if it is larger than the hash functions block size. This makes both of the HMAC keys the same value since the bytes_of_hex_password
variable will be hashed to be used as the HMAC key
PoC:
import os, hashlib
bytes_of_hex_password = bytearray(os.urandom(128).hex(), 'utf8')
salt = os.urandom(32)
hashedpassword = hashlib.sha256(bytes_of_hex_password).digest()
print("Bytes of Hex of password: {}".format(hashedpassword.hex()))
print("Hash of password: {}".format(bytes_of_hex_password.hex()))
print("PBKDF2 Hashed: {}".format(hashlib.pbkdf2_hmac('sha256', hashedpassword, salt, 100000).hex()))
print("PBKDF2 Origional: {}".format(hashlib.pbkdf2_hmac('sha256', bytes_of_hex_password, salt, 100000).hex()))
#Bytes of Hex of password: 32d9e62a13cd784ab4611c9ace495b11b542841900e32d29b4e9b9462ab004c0
#Hash of password: 62306362383734333430653833353961336463616330386465663733613966663464386535393133653665396335653038376631626438633838356164353231333961373534353333313339643632666138613835353238313535656134646131626633376533343035376330373232336634386633623866306565646430336532363265663034383432326266656664633038653065336432386534663631386465626536613362643331373936333064313034323432656539396166393630646537313966306336653837386631313839386538336339313135316532313333363661353766323234376263353839313232616439623837366564663837
#PBKDF2 Hashed: b8764a9b83e8d144ec3c910997947aa0d9a9e343f4eeffad
#PBKDF2 Origional: b8764a9b83e8d144ec3c910997947aa0d9a9e343f4eeffad
Unexploitable Versions:
- If the HMAC hash does not match the PBKDF2 Hash
- If the password is prehashed before sending to the PBKDF2 function
- HMAC using a Constant key
- Truncate the Key instead of hashing it
Password Limit for Hashes:
- 64-character passwords would be the upper limit for MD5, SHA-1, SHA-224, and SHA-256.
- 128-character passwords would be the upper limit for SHA-384 and SHA-512.