import hmac
import hashlib
from struct import Struct
from operator import xor
from itertools import izip, starmap

_pack_int = Struct('>I').pack


def pbkdf2_hex(data, salt, iterations=1000, keylen=24, hashfunc=None):
    """
        Like :func:`pbkdf2_bin` but returns a hex encoded string.
    """
    return pbkdf2_bin(data, salt, iterations, keylen, hashfunc).encode('hex')


def pbkdf2_bin(data, salt, iterations=1000, keylen=24, hashfunc=None):
    """
        Returns a binary digest for the PBKDF2 hash algorithm of `data`
        with the given `salt`.  It iterates `iterations` time and produces a
        key of `keylen` bytes.  By default SHA-1 is used as hash function,
        a different hashlib `hashfunc` can be provided.
    """
    hashfunc = hashfunc or hashlib.sha1
    mac = hmac.new(data, None, hashfunc)
    def _pseudorandom(x, mac=mac):
        h = mac.copy()
        h.update(x)
        return map(ord, h.digest())

    buf = []

    for block in xrange(1, -(-keylen // mac.digest_size) + 1):
        rv = u = _pseudorandom(salt + _pack_int(block))
        for i in xrange(iterations - 1):
            u = _pseudorandom(''.join(map(chr, u)))
            rv = starmap(xor, izip(rv, u))
        buf.extend(rv)

    return ''.join(map(chr, buf))[:keylen]