Link to this headingPBKDF2

  • 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

Link to this headingImplementation

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

Link to this headingAttacks

Link to this headingHMAC 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.