Skip to content

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.