# -*- coding: utf-8 -*-
"""
    pbkdf2_ctypes
    ~~~~~~

    Fast pbkdf2.

    This module implements pbkdf2 for Python using crypto lib from
    openssl or commoncrypto.

    Note: This module is intended as a plugin replacement of pbkdf2.py
    by Armin Ronacher.

    Git repository:
    $ git clone https://github.com/michele-comitini/pbkdf2_ctypes.git

    :copyright: Copyright (c) 2013: Michele Comitini <mcm@glisco.it>
    :license: LGPLv3

"""

import ctypes
import ctypes.util
import hashlib
import platform
import os.path
import binascii
import sys

__all__ = ['pkcs5_pbkdf2_hmac', 'pbkdf2_bin', 'pbkdf2_hex']
__version__ = '0.99.3'

def _commoncrypto_hashlib_to_crypto_map_get(hashfunc):
    hashlib_to_crypto_map = {hashlib.sha1: 1,
                             hashlib.sha224: 2,
                             hashlib.sha256: 3,
                             hashlib.sha384: 4,
                             hashlib.sha512: 5}
    crypto_hashfunc = hashlib_to_crypto_map.get(hashfunc)
    if crypto_hashfunc is None:
        raise ValueError('Unkwnown digest %s' % hashfunc)
    return crypto_hashfunc

def _commoncrypto_pbkdf2(data, salt, iterations, digest, keylen):
    """Common Crypto compatibile wrapper
    """
    c_hashfunc = ctypes.c_uint32(_commoncrypto_hashlib_to_crypto_map_get(digest))
    c_pass = ctypes.c_char_p(data)
    c_passlen = ctypes.c_size_t(len(data))
    c_salt = ctypes.c_char_p(salt)
    c_saltlen = ctypes.c_size_t(len(salt))
    c_iter = ctypes.c_uint(iterations)
    c_keylen = ctypes.c_size_t(keylen)
    c_buff = ctypes.create_string_buffer(keylen)

    crypto.CCKeyDerivationPBKDF.restype = ctypes.c_int
    crypto.CCKeyDerivationPBKDF.argtypes = [ctypes.c_uint32,
                                            ctypes.c_char_p,
                                            ctypes.c_size_t,
                                            ctypes.c_char_p,
                                            ctypes.c_size_t,
                                            ctypes.c_uint32,
                                            ctypes.c_uint,
                                            ctypes.c_char_p,
                                            ctypes.c_size_t]
    ret = crypto.CCKeyDerivationPBKDF(2, # hardcoded 2-> PBKDF2
                                           c_pass, c_passlen,
                                           c_salt, c_saltlen,
                                           c_hashfunc,
                                           c_iter,
                                           c_buff,
                                           c_keylen)

    return (1 - ret, c_buff)

def _openssl_hashlib_to_crypto_map_get(hashfunc):
    hashlib_to_crypto_map = {hashlib.md5: crypto.EVP_md5,
                                 hashlib.sha1: crypto.EVP_sha1,
                                 hashlib.sha256: crypto.EVP_sha256,
                                 hashlib.sha224: crypto.EVP_sha224,
                                 hashlib.sha384: crypto.EVP_sha384,
                                 hashlib.sha512: crypto.EVP_sha512}
    crypto_hashfunc = hashlib_to_crypto_map.get(hashfunc)
    if crypto_hashfunc is None:
        raise ValueError('Unkwnown digest %s' % hashfunc)
    crypto_hashfunc.restype = ctypes.c_void_p
    return crypto_hashfunc()


def _openssl_pbkdf2(data, salt, iterations, digest, keylen):
    """OpenSSL compatibile wrapper
    """
    c_hashfunc = ctypes.c_void_p(_openssl_hashlib_to_crypto_map_get(digest))

    c_pass = ctypes.c_char_p(data)
    c_passlen = ctypes.c_int(len(data))
    c_salt = ctypes.c_char_p(salt)
    c_saltlen = ctypes.c_int(len(salt))
    c_iter = ctypes.c_int(iterations)
    c_keylen = ctypes.c_int(keylen)
    c_buff = ctypes.create_string_buffer(keylen)

    # PKCS5_PBKDF2_HMAC(const char *pass, int passlen,
    #                            const unsigned char *salt, int saltlen, int iter,
    #                            const EVP_MD *digest,
    #                       int keylen, unsigned char *out);

    crypto.PKCS5_PBKDF2_HMAC.argtypes = [ctypes.c_char_p, ctypes.c_int,
                                         ctypes.c_char_p, ctypes.c_int,
                                         ctypes.c_int, ctypes.c_void_p,
                                         ctypes.c_int, ctypes.c_char_p]

    crypto.PKCS5_PBKDF2_HMAC.restype = ctypes.c_int
    err = crypto.PKCS5_PBKDF2_HMAC(c_pass, c_passlen,
                            c_salt, c_saltlen,
                            c_iter,
                            c_hashfunc,
                            c_keylen,
                            c_buff)
    return (err, c_buff)

try:  # check that we have proper OpenSSL or Common Crypto on the system.
    system = platform.system()
    if system == 'Windows':
        if platform.architecture()[0] == '64bit':
            libname = ctypes.util.find_library('libeay64')
            if not libname:
                raise OSError('Library not found')
            crypto = ctypes.CDLL(libname)
        else:
            libname = ctypes.util.find_library('libeay32')
            if not libname:
                raise OSError('Library libeay32 not found.')

            crypto = ctypes.CDLL(libname)
        _pbkdf2_hmac = _openssl_pbkdf2
        crypto.PKCS5_PBKDF2_HMAC # test compatibility
    elif system == 'Darwin': # think different(TM)! i.e. break things!
        if [int(x) for x in platform.mac_ver()[0].split('.')] < [10, 7, 0]:
            raise OSError('OS X Version too old %s < 10.7.0' % platform.mac_ver()[0])
        libname = ctypes.util.find_library('System')
        if not libname:
            raise OSError('Library not found')

        crypto = ctypes.CDLL(os.path.basename(libname))
        _pbkdf2_hmac = _commoncrypto_pbkdf2
    else:
        libname = ctypes.util.find_library('crypto')
        if not libname:
            raise OSError('Library crypto not found.')
        crypto = ctypes.CDLL(os.path.basename(libname))
        _pbkdf2_hmac = _openssl_pbkdf2
        crypto.PKCS5_PBKDF2_HMAC # test compatibility

except (OSError, AttributeError):
    _, e, _ = sys.exc_info()
    raise ImportError('Cannot find a compatible cryptographic library '
                      'on your system. %s' % e)


def pkcs5_pbkdf2_hmac(data, salt, iterations=1000, keylen=24, hashfunc=None):
    if hashfunc is None:
        hashfunc = hashlib.sha1
    err, c_buff = _pbkdf2_hmac(data, salt, iterations, hashfunc, keylen)

    if err == 0:
        raise ValueError('wrong parameters')
    return c_buff.raw[:keylen]


def pbkdf2_hex(data, salt, iterations=1000, keylen=24, hashfunc=None):
    return binascii.hexlify(pkcs5_pbkdf2_hmac(data, salt, iterations, keylen, hashfunc))


def pbkdf2_bin(data, salt, iterations=1000, keylen=24, hashfunc=None):
    return pkcs5_pbkdf2_hmac(data, salt, iterations, keylen, hashfunc)

if __name__ == '__main__':
    try:
        crypto.SSLeay_version.restype = ctypes.c_char_p
        print(crypto.SSLeay_version(0))
    except:
        pass

    import platform
    if platform.python_version_tuple() < ('3', '0', '0'):
        def bytes(*args):
            return str(args[0])

    for h in [hashlib.sha1, hashlib.sha224, hashlib.sha256,
              hashlib.sha384, hashlib.sha512]:
        print(binascii.hexlify(pkcs5_pbkdf2_hmac(bytes('secret', 'utf-8') * 11,
                                                 bytes('salt', 'utf-8'),
                                                 hashfunc=h)))

[((48, 13, 48, 34), 'ctypes.c_char_p', 'ctypes.c_char_p', ({(48, 29, 48, 33): 'data'}, {}), '(data)', False, 'import ctypes\n'), ((50, 13, 50, 34), 'ctypes.c_char_p', 'ctypes.c_char_p', ({(50, 29, 50, 33): 'salt'}, {}), '(salt)', False, 'import ctypes\n'), ((52, 13, 52, 38), 'ctypes.c_uint', 'ctypes.c_uint', ({(52, 27, 52, 37): 'iterations'}, {}), '(iterations)', False, 'import ctypes\n'), ((53, 15, 53, 38), 'ctypes.c_size_t', 'ctypes.c_size_t', ({(53, 31, 53, 37): 'keylen'}, {}), '(keylen)', False, 'import ctypes\n'), ((54, 13, 54, 48), 'ctypes.create_string_buffer', 'ctypes.create_string_buffer', ({(54, 41, 54, 47): 'keylen'}, {}), '(keylen)', False, 'import ctypes\n'), ((95, 13, 95, 34), 'ctypes.c_char_p', 'ctypes.c_char_p', ({(95, 29, 95, 33): 'data'}, {}), '(data)', False, 'import ctypes\n'), ((97, 13, 97, 34), 'ctypes.c_char_p', 'ctypes.c_char_p', ({(97, 29, 97, 33): 'salt'}, {}), '(salt)', False, 'import ctypes\n'), ((99, 13, 99, 37), 'ctypes.c_int', 'ctypes.c_int', ({(99, 26, 99, 36): 'iterations'}, {}), '(iterations)', False, 'import ctypes\n'), ((100, 15, 100, 35), 'ctypes.c_int', 'ctypes.c_int', ({(100, 28, 100, 34): 'keylen'}, {}), '(keylen)', False, 'import ctypes\n'), ((101, 13, 101, 48), 'ctypes.create_string_buffer', 'ctypes.create_string_buffer', ({(101, 41, 101, 47): 'keylen'}, {}), '(keylen)', False, 'import ctypes\n'), ((123, 13, 123, 30), 'platform.system', 'platform.system', ({}, {}), '()', False, 'import platform\n'), ((156, 14, 156, 28), 'sys.exc_info', 'sys.exc_info', ({}, {}), '()', False, 'import sys\n'), ((186, 7, 186, 38), 'platform.python_version_tuple', 'platform.python_version_tuple', ({}, {}), '()', False, 'import platform\n'), ((126, 22, 126, 58), 'ctypes.util.find_library', 'ctypes.util.find_library', ({(126, 47, 126, 57): '"""libeay64"""'}, {}), "('libeay64')", False, 'import ctypes\n'), ((129, 21, 129, 41), 'ctypes.CDLL', 'ctypes.CDLL', ({(129, 33, 129, 40): 'libname'}, {}), '(libname)', False, 'import ctypes\n'), ((131, 22, 131, 58), 'ctypes.util.find_library', 'ctypes.util.find_library', ({(131, 47, 131, 57): '"""libeay32"""'}, {}), "('libeay32')", False, 'import ctypes\n'), ((135, 21, 135, 41), 'ctypes.CDLL', 'ctypes.CDLL', ({(135, 33, 135, 40): 'libname'}, {}), '(libname)', False, 'import ctypes\n'), ((141, 18, 141, 52), 'ctypes.util.find_library', 'ctypes.util.find_library', ({(141, 43, 141, 51): '"""System"""'}, {}), "('System')", False, 'import ctypes\n'), ((148, 18, 148, 52), 'ctypes.util.find_library', 'ctypes.util.find_library', ({(148, 43, 148, 51): '"""crypto"""'}, {}), "('crypto')", False, 'import ctypes\n'), ((125, 11, 125, 34), 'platform.architecture', 'platform.architecture', ({}, {}), '()', False, 'import platform\n'), ((140, 63, 140, 81), 'platform.mac_ver', 'platform.mac_ver', ({}, {}), '()', False, 'import platform\n'), ((139, 28, 139, 46), 'platform.mac_ver', 'platform.mac_ver', ({}, {}), '()', False, 'import platform\n')]