# Impacket - Collection of Python classes for working with network protocols.
#
# Copyright Fortra, LLC and its affiliated companies 
#
# All rights reserved.
#
# This software is provided under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#
# Description:
#   DPAPI and Windows Vault parsing structures and manipulation
#
# Author:
#   Alberto Solino (@agsolino)
#
# References:
#   All of the work done by these guys. I just adapted their work to my needs.
#   - https://www.passcape.com/index.php?section=docsys&cmd=details&id=28
#   - https://github.com/jordanbtucker/dpapick
#   - https://github.com/gentilkiwi/mimikatz/wiki/howto-~-credential-manager-saved-credentials (and everything else Ben did)
#   - http://blog.digital-forensics.it/2016/01/windows-revaulting.html
#   - https://www.passcape.com/windows_password_recovery_vault_explorer
#   -  https://www.passcape.com/windows_password_recovery_dpapi_master_key
#

from __future__ import division
from __future__ import print_function
import sys

from struct import unpack
from datetime import datetime, timezone
from binascii import unhexlify, hexlify
from struct import pack
from hashlib import pbkdf2_hmac
from Crypto.Hash import HMAC, SHA512, SHA1, MD4
from Crypto.Cipher import AES, DES3
from Crypto.Util.Padding import unpad
from Crypto.PublicKey import RSA
from Crypto.Util.number import bytes_to_long
from six import PY3

from impacket.ese import getUnixTime
from impacket.structure import Structure, hexdump
from impacket.uuid import bin_to_string
from impacket.dcerpc.v5.enum import Enum
from impacket.dcerpc.v5.dtypes import RPC_SID

# Algorithm classes
ALG_CLASS_ANY                   = (0)
ALG_CLASS_SIGNATURE             = (1 << 13)
ALG_CLASS_MSG_ENCRYPT           = (2 << 13)
ALG_CLASS_DATA_ENCRYPT          = (3 << 13)
ALG_CLASS_HASH                  = (4 << 13)
ALG_CLASS_KEY_EXCHANGE          = (5 << 13)
ALG_CLASS_ALL                   = (7 << 13)

# Algorithm types
ALG_TYPE_ANY                    = (0)
ALG_TYPE_DSS                    = (1 << 9)
ALG_TYPE_RSA                    = (2 << 9)
ALG_TYPE_BLOCK                  = (3 << 9)
ALG_TYPE_STREAM                 = (4 << 9)
ALG_TYPE_DH                     = (5 << 9)
ALG_TYPE_SECURECHANNEL          = (6 << 9)
ALG_SID_ANY                     = (0)
ALG_SID_RSA_ANY                 = 0
ALG_SID_RSA_PKCS                = 1
ALG_SID_RSA_MSATWORK            = 2
ALG_SID_RSA_ENTRUST             = 3
ALG_SID_RSA_PGP                 = 4
ALG_SID_DSS_ANY                 = 0
ALG_SID_DSS_PKCS                = 1
ALG_SID_DSS_DMS                 = 2
ALG_SID_ECDSA                   = 3

# Block cipher sub ids
ALG_SID_DES                     = 1
ALG_SID_3DES                    = 3
ALG_SID_DESX                    = 4
ALG_SID_IDEA                    = 5
ALG_SID_CAST                    = 6
ALG_SID_SAFERSK64               = 7
ALG_SID_SAFERSK128              = 8
ALG_SID_3DES_112                = 9
ALG_SID_CYLINK_MEK              = 12
ALG_SID_RC5                     = 13
ALG_SID_AES_128                 = 14
ALG_SID_AES_192                 = 15
ALG_SID_AES_256                 = 16
ALG_SID_AES                     = 17
ALG_SID_SKIPJACK                = 10
ALG_SID_TEK                     = 11

CRYPT_MODE_CBCI                 = 6       # ANSI CBC Interleaved
CRYPT_MODE_CFBP                 = 7       # ANSI CFB Pipelined
CRYPT_MODE_OFBP                 = 8       # ANSI OFB Pipelined
CRYPT_MODE_CBCOFM               = 9       # ANSI CBC + OF Masking
CRYPT_MODE_CBCOFMI              = 10      # ANSI CBC + OFM Interleaved

ALG_SID_RC2                     = 2
ALG_SID_RC4                     = 1
ALG_SID_SEAL                    = 2

# Diffie - Hellman sub - ids
ALG_SID_DH_SANDF                = 1
ALG_SID_DH_EPHEM                = 2
ALG_SID_AGREED_KEY_ANY          = 3
ALG_SID_KEA                     = 4
ALG_SID_ECDH                    = 5

# Hash sub ids
ALG_SID_MD2                     = 1
ALG_SID_MD4                     = 2
ALG_SID_MD5                     = 3
ALG_SID_SHA                     = 4
ALG_SID_SHA1                    = 4
ALG_SID_MAC                     = 5
ALG_SID_RIPEMD                  = 6
ALG_SID_RIPEMD160               = 7
ALG_SID_SSL3SHAMD5              = 8
ALG_SID_HMAC                    = 9
ALG_SID_TLS1PRF                 = 10
ALG_SID_HASH_REPLACE_OWF        = 11
ALG_SID_SHA_256                 = 12
ALG_SID_SHA_384                 = 13
ALG_SID_SHA_512                 = 14

# secure channel sub ids
ALG_SID_SSL3_MASTER             = 1
ALG_SID_SCHANNEL_MASTER_HASH    = 2
ALG_SID_SCHANNEL_MAC_KEY        = 3
ALG_SID_PCT1_MASTER             = 4
ALG_SID_SSL2_MASTER             = 5
ALG_SID_TLS1_MASTER             = 6
ALG_SID_SCHANNEL_ENC_KEY        = 7
ALG_SID_ECMQV                   = 1

def getFlags(myenum, flags):
    return '|'.join([name for name, member in myenum.__members__.items() if member.value & flags])

class FLAGS(Enum):
    CRYPTPROTECT_UI_FORBIDDEN = 0x1
    CRYPTPROTECT_LOCAL_MACHINE = 0x4
    CRYPTPROTECT_CRED_SYNC = 0x8
    CRYPTPROTECT_AUDIT = 0x10
    CRYPTPROTECT_VERIFY_PROTECTION = 0x40
    CRYPTPROTECT_CRED_REGENERATE = 0x80
    CRYPTPROTECT_SYSTEM = 0x20000000

# algorithm identifier definitions
class ALGORITHMS(Enum):
    CALG_MD2                = (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_MD2)
    CALG_MD4                = (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_MD4)
    CALG_MD5                = (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_MD5)
    CALG_SHA                = (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA)
    CALG_SHA1               = (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA1)
    CALG_RSA_SIGN           = (ALG_CLASS_SIGNATURE | ALG_TYPE_RSA | ALG_SID_RSA_ANY)
    CALG_DSS_SIGN           = (ALG_CLASS_SIGNATURE | ALG_TYPE_DSS | ALG_SID_DSS_ANY)
    CALG_NO_SIGN            = (ALG_CLASS_SIGNATURE | ALG_TYPE_ANY | ALG_SID_ANY)
    CALG_RSA_KEYX           = (ALG_CLASS_KEY_EXCHANGE|ALG_TYPE_RSA|ALG_SID_RSA_ANY)
    CALG_DES                = (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_DES)
    CALG_3DES_112           = (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_3DES_112)
    CALG_3DES               = (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_3DES)
    CALG_DESX               = (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_DESX)
    CALG_RC2                = (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_RC2)
    CALG_RC4                = (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_STREAM|ALG_SID_RC4)
    CALG_SEAL               = (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_STREAM|ALG_SID_SEAL)
    CALG_DH_SF              = (ALG_CLASS_KEY_EXCHANGE|ALG_TYPE_DH|ALG_SID_DH_SANDF)
    CALG_DH_EPHEM           = (ALG_CLASS_KEY_EXCHANGE|ALG_TYPE_DH|ALG_SID_DH_EPHEM)
    CALG_AGREEDKEY_ANY      = (ALG_CLASS_KEY_EXCHANGE|ALG_TYPE_DH|ALG_SID_AGREED_KEY_ANY)
    CALG_KEA_KEYX           = (ALG_CLASS_KEY_EXCHANGE|ALG_TYPE_DH|ALG_SID_KEA)
    CALG_HUGHES_MD5         = (ALG_CLASS_KEY_EXCHANGE|ALG_TYPE_ANY|ALG_SID_MD5)
    CALG_SKIPJACK           = (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_SKIPJACK)
    CALG_TEK                = (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_TEK)
    CALG_SSL3_SHAMD5        = (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SSL3SHAMD5)
    CALG_SSL3_MASTER        = (ALG_CLASS_MSG_ENCRYPT|ALG_TYPE_SECURECHANNEL|ALG_SID_SSL3_MASTER)
    CALG_SCHANNEL_MASTER_HASH   = (ALG_CLASS_MSG_ENCRYPT|ALG_TYPE_SECURECHANNEL|ALG_SID_SCHANNEL_MASTER_HASH)
    CALG_SCHANNEL_MAC_KEY   = (ALG_CLASS_MSG_ENCRYPT|ALG_TYPE_SECURECHANNEL|ALG_SID_SCHANNEL_MAC_KEY)
    CALG_SCHANNEL_ENC_KEY   = (ALG_CLASS_MSG_ENCRYPT|ALG_TYPE_SECURECHANNEL|ALG_SID_SCHANNEL_ENC_KEY)
    CALG_PCT1_MASTER        = (ALG_CLASS_MSG_ENCRYPT|ALG_TYPE_SECURECHANNEL|ALG_SID_PCT1_MASTER)
    CALG_SSL2_MASTER        = (ALG_CLASS_MSG_ENCRYPT|ALG_TYPE_SECURECHANNEL|ALG_SID_SSL2_MASTER)
    CALG_TLS1_MASTER        = (ALG_CLASS_MSG_ENCRYPT|ALG_TYPE_SECURECHANNEL|ALG_SID_TLS1_MASTER)
    CALG_RC5                = (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_RC5)
    CALG_HMAC               = (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_HMAC)
    CALG_TLS1PRF            = (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_TLS1PRF)
    CALG_HASH_REPLACE_OWF   = (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_HASH_REPLACE_OWF)
    CALG_AES_128            = (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_AES_128)
    CALG_AES_192            = (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_AES_192)
    CALG_AES_256            = (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_AES_256)
    CALG_AES                = (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_AES)
    CALG_SHA_256            = (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_256)
    CALG_SHA_384            = (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_384)
    CALG_SHA_512            = (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_512)
    CALG_ECDH               = (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_DH | ALG_SID_ECDH)
    CALG_ECMQV              = (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_ANY | ALG_SID_ECMQV)
    CALG_ECDSA              = (ALG_CLASS_SIGNATURE | ALG_TYPE_DSS | ALG_SID_ECDSA)

class CREDENTIAL_FLAGS(Enum):
    CRED_FLAGS_PASSWORD_FOR_CERT = 0x1
    CRED_FLAGS_PROMPT_NOW = 0x2
    CRED_FLAGS_USERNAME_TARGET = 0x4
    CRED_FLAGS_OWF_CRED_BLOB = 0x8
    CRED_FLAGS_REQUIRE_CONFIRMATION = 0x10
    CRED_FLAGS_WILDCARD_MATCH = 0x20
    CRED_FLAGS_VSM_PROTECTED = 0x40
    CRED_FLAGS_NGC_CERT = 0x80

class CREDENTIAL_TYPE(Enum):
    CRED_TYPE_GENERIC = 0x1
    CRED_TYPE_DOMAIN_PASSWORD = 0x2
    CRED_TYPE_DOMAIN_CERTIFICATE = 0x3
    CRED_TYPE_DOMAIN_VISIBLE_PASSWORD = 0x4
    CRED_TYPE_GENERIC_CERTIFICATE = 0x5
    CRED_TYPE_DOMAIN_EXTENDED = 0x6
    CRED_TYPE_MAXIMUM = 0x7
    CRED_TYPE_MAXIMUM_EX = 0x8

class CREDENTIAL_PERSIST(Enum):
    CRED_PERSIST_NONE = 0x0
    CRED_PERSIST_SESSION = 0x1
    CRED_PERSIST_LOCAL_MACHINE = 0x2
    CRED_PERSIST_ENTERPRISE = 0x3

ALGORITHMS_DATA = {
    # Algorithm: key/SaltLen, CryptHashModule, Mode, IVLen, BlockSize
    ALGORITHMS.CALG_SHA.value: (160//8, SHA1, None, None, 512//8),
    ALGORITHMS.CALG_HMAC.value: (160//8, SHA512, None, None, 512//8),
    ALGORITHMS.CALG_3DES.value: (192//8, DES3, DES3.MODE_CBC, 64//8),
    ALGORITHMS.CALG_SHA_512.value: (128//8, SHA512, None, None, 1024//8),
    ALGORITHMS.CALG_AES_256.value: (256//8, AES, AES.MODE_CBC, 128//8),
}

class MasterKeyFile(Structure):
    structure = (
        ('Version', '<L=0'),
        ('unk1', '<L=0'),
        ('unk2', '<L=0'),
        ('Guid', "72s=b''"),
        ('Unkown', '<L=0'),
        ('Policy', '<L=0'),
        ('Flags', '<L=0'),
        ('MasterKeyLen', '<Q=0'),
        ('BackupKeyLen', '<Q=0'),
        ('CredHistLen', '<Q=0'),
        ('DomainKeyLen', '<Q=0'),
    )

    def dump(self):
        print("[MASTERKEYFILE]")
        print("Version     : %8x (%d)" % (self['Version'], self['Version']))
        print("Guid        : %s" % self['Guid'].decode('utf-16le'))
        print("Flags       : %8x (%d)" % (self['Flags'], self['Flags']))
        print("Policy      : %8x (%d)" % (self['Policy'], self['Policy']))
        print("MasterKeyLen: %.8x (%d)" % (self['MasterKeyLen'], self['MasterKeyLen']))
        print("BackupKeyLen: %.8x (%d)" % (self['BackupKeyLen'], self['BackupKeyLen']))
        print("CredHistLen : %.8x (%d)" % (self['CredHistLen'], self['CredHistLen']))
        print("DomainKeyLen: %.8x (%d)" % (self['DomainKeyLen'], self['DomainKeyLen']))
        print()

class MasterKey(Structure):
    structure = (
        ('Version', '<L=0'),
        ('Salt', '16s=b""'),
        ('MasterKeyIterationCount', '<L=0'),
        ('HashAlgo', "<L=0"),
        ('CryptAlgo', '<L=0'),
        ('data', ':'),
    )

    def __init__(self, data = None, alignment = 0):
        Structure.__init__(self, data, alignment)
        self.decryptedKey = None

    def dump(self):
        print("[MASTERKEY]")
        print("Version     : %8x (%d)" % (self['Version'], self['Version']))
        print("Salt        : %s" % hexlify(self['Salt']))
        print("Rounds      : %8x (%d)" % (self['MasterKeyIterationCount'], self['MasterKeyIterationCount']))
        print("HashAlgo    : %.8x (%d) (%s)" % (self['HashAlgo'], self['HashAlgo'], ALGORITHMS(self['HashAlgo']).name))
        print("CryptAlgo   : %.8x (%d) (%s)" % (self['CryptAlgo'], self['CryptAlgo'], ALGORITHMS(self['CryptAlgo']).name))
        print("data        : %s" % (hexlify(self['data'])))
        print()

    def deriveKey(self, passphrase, salt, keylen, count, hashFunction):
        keyMaterial = b""
        i = 1
        while len(keyMaterial) < keylen:
            U = salt + pack("!L", i)
            i += 1
            derived = bytearray(hashFunction(passphrase, U))
            for r in range(count - 1):
                actual = bytearray(hashFunction(passphrase, derived))
                if PY3:
                    derived = (int.from_bytes(derived, sys.byteorder) ^ int.from_bytes(actual, sys.byteorder)).to_bytes(len(actual), sys.byteorder)
                else:
                    derived = bytearray([ chr((a) ^ (b)) for (a,b) in zip(derived, actual) ])
            keyMaterial += derived

        return keyMaterial[:keylen]

    def decrypt(self, key):
        if self['HashAlgo'] == ALGORITHMS.CALG_HMAC.value:
            hashModule = SHA1
        else:
            hashModule = ALGORITHMS_DATA[self['HashAlgo']][1]

        prf = lambda p, s: HMAC.new(p, s, hashModule).digest()
        derivedBlob = self.deriveKey(key, self['Salt'],
                                    ALGORITHMS_DATA[self['CryptAlgo']][0] + ALGORITHMS_DATA[self['CryptAlgo']][3],
                                    count=self['MasterKeyIterationCount'], hashFunction=prf)

        cryptKey = derivedBlob[:ALGORITHMS_DATA[self['CryptAlgo']][0]]
        iv = derivedBlob[ALGORITHMS_DATA[self['CryptAlgo']][0]:][:ALGORITHMS_DATA[self['CryptAlgo']][3]]

        cipher = ALGORITHMS_DATA[self['CryptAlgo']][1].new(cryptKey, mode = ALGORITHMS_DATA[self['CryptAlgo']][2], iv = iv)
        cleartext = cipher.decrypt(self['data'])

        decryptedKey = cleartext[-64:]
        hmacSalt = cleartext[:16]
        hmac = cleartext[16:][:ALGORITHMS_DATA[self['HashAlgo']][0]]

        hmacKey = HMAC.new(key, hmacSalt, hashModule).digest()

        hmacCalculated = HMAC.new(hmacKey, decryptedKey, hashModule ).digest()

        if hmacCalculated[:ALGORITHMS_DATA[self['HashAlgo']][0]] == hmac:
            self.decryptedKey = decryptedKey
            return decryptedKey
        else:
            return None

class CredHist(Structure):
    structure = (
        ('Version', '<L=0'),
        ('Guid', "16s=b''"),
    )
    def dump(self):
        print("[CREDHIST]")
        print("Version       : %8x (%d)" % (self['Version'], self['Version']))
        print("Guid          : %s" % bin_to_string(self['Guid']))
        print()

class CREDHIST_ENTRY(Structure):
    structure = (
        ('Version', '<L=0'),
        ('HashAlgo', '<L=0'),
        ('Rounds', '<L=0'),
        ('SidLen', '<L=0'),
        ('_Sid', '_-Sid','self["SidLen"]'),
        ('CryptAlgo', '<L=0'),
        ('shaHashLen', '<L=0'),
        ('ntHashLen', '<L=0'),
        ('Salt', '16s=b'),
        ('Sid', ':'),
        ('_data','_-data','(self["shaHashLen"]+self["ntHashLen"]) + (-(self["shaHashLen"]+self["ntHashLen"])) % 16'),
        ('data', ':'),
        ('Version2', '<L=0'),
        ('Guid', '16s=b'),
    )

    def __init__(self, data = None, alignment = 0):
        Structure.__init__(self, data, alignment)
        self.sid = RPC_SID(b'\x05\x00\x00\x00'+self['Sid']).formatCanonical()
        self.pwdhash = None
        self.nthash = None

    def deriveKey(self, passphrase, salt, keylen, count, hashFunction):
        keyMaterial = b""
        i = 1
        while len(keyMaterial) < keylen:
            U = salt + pack("!L", i)
            i += 1
            derived = bytearray(hashFunction(passphrase, U))
            for r in range(count - 1):
                actual = bytearray(hashFunction(passphrase, derived))
                if PY3:
                    derived = (int.from_bytes(derived, sys.byteorder) ^ int.from_bytes(actual, sys.byteorder)).to_bytes(len(actual), sys.byteorder)
                else:
                    derived = bytearray([ chr((a) ^ (b)) for (a,b) in zip(derived, actual) ])
            keyMaterial += derived

        return keyMaterial[:keylen]

    def decrypt(self, key):
        if self['HashAlgo'] == ALGORITHMS.CALG_HMAC.value:
            hashModule = SHA1
        else:
            hashModule = ALGORITHMS_DATA[self['HashAlgo']][1]

        prf = lambda p, s: HMAC.new(p, s, hashModule).digest()

        derivedBlob = self.deriveKey(key, self['Salt'], ALGORITHMS_DATA[self['CryptAlgo']][0] + ALGORITHMS_DATA[self['CryptAlgo']][3], count=self['Rounds'], hashFunction=prf)

        cryptKey = derivedBlob[:ALGORITHMS_DATA[self['CryptAlgo']][0]]
        iv = derivedBlob[ALGORITHMS_DATA[self['CryptAlgo']][0]:][:ALGORITHMS_DATA[self['CryptAlgo']][3]]

        cipher = ALGORITHMS_DATA[self['CryptAlgo']][1].new(cryptKey, mode = ALGORITHMS_DATA[self['CryptAlgo']][2], iv = iv)
        cleartext = cipher.decrypt(self['data'])

        ntHashSize = 16
        self.pwdhash = cleartext[:self['shaHashLen']]
        self.nthash = cleartext[self['shaHashLen']:self['shaHashLen'] + ntHashSize]

        if cleartext[self['shaHashLen'] + ntHashSize:] != (len(self['data']) - self['shaHashLen'] - ntHashSize) * b'\x00':
            self.pwdhash = None
            self.nthash = None

    def dump(self):
        print("[CREDHIST ENTRY]")
        print("Version    : 0x%.8x (%d)" % (self['Version'], self['Version']))
        print("HashAlgo   : 0x%.8x (%d) (%s)" % (self['HashAlgo'], self['HashAlgo'], ALGORITHMS(self['HashAlgo']).name))
        print("Rounds     : %d" % (self['Rounds']))
        print("CryptAlgo  : 0x%.8x (%d) (%s)" % (self['CryptAlgo'], self['CryptAlgo'], ALGORITHMS(self['CryptAlgo']).name))
        print("shaHashLen : 0x%.8x (%d)" % (self['shaHashLen'], self['shaHashLen']))
        print("ntHashLen  : 0x%.8x (%d)" % (self['ntHashLen'], self['ntHashLen']))
        print("Salt       : %s" % (hexlify(self['Salt']).decode()))
        print('SID        : %s' % self.sid)
        print("Version2   : 0x%.8x (%d)" % (self['Version2'], self['Version2']))
        print("Guid       : %s" % bin_to_string(self['Guid']))
        if self.pwdhash is not None and self.nthash is not None:
            print("pwdHash    : %s" % hexlify(self.pwdhash).decode())
            print("ntHash     : %s" % hexlify(self.nthash).decode())
        else:
            print("Data       : %s" % (hexlify(self['data'])).decode())
        print()

    def summarize(self):
        print("[CREDHIST ENTRY]")
        print("Guid       : %s" % bin_to_string(self['Guid']))
        if self.pwdhash is not None and self.nthash is not None:
            print("pwdHash    : %s" % hexlify(self.pwdhash).decode())
            print("ntHash     : %s" % hexlify(self.nthash).decode())
        else:
            print("Data       : %s" % (hexlify(self['data'])).decode())
        print()

class CREDHIST_FILE:
    def __init__(self, raw):
        self.credhist_entries = {}
        self.credhist_entries_list = []


        self.version = unpack('<L', raw[:4])[0]
        self.current_guid = unpack('16s', raw[4:20])[0]

        i = 0
        next_len = unpack('<L', raw[-i-4:])[0]
        i += 4
        while next_len != 0:
            ch_entry = CREDHIST_ENTRY(data=raw[-(i + next_len - 4):-i])
            i += next_len - 4
            self.credhist_entries[bin_to_string(ch_entry['Guid'])] = ch_entry
            self.credhist_entries_list.append(ch_entry)
            next_len = unpack('<L', raw[-i-4:-i])[0]
            i += 4

    def decrypt_entry_by_index(self, entry_index, key):
        self.credhist_entries_list[entry_index].decrypt(key)

    def decrypt_entry_by_guid(self, guid, key):
        self.credhist_entries[guid].decrypt(key)

    def decrypt(self, key):
        keys = [key]
        for i, e in enumerate(self.credhist_entries_list):
            # Try all keys until success
            for k in keys:
                e.decrypt(k)
                if e.pwdhash is not None:
                    break

            if e.pwdhash is None:
                print('Error decrypting entry #%d' % i)
                return

            keys = deriveKeysFromUserkey(e.sid, e.pwdhash)

    def dump(self):
        print('[CREDHIST FILE]')
        print("Version        : 0x%.8x (%d)" % (self.version, self.version))
        print("Current Guid   : %s" % bin_to_string(self.current_guid))
        print()
        for i, e in enumerate(self.credhist_entries_list):
            print('[Entry #%d]' % i)
            e.dump()

    def summarize(self):
        print('[CREDHIST FILE]')
        print("Version        : 0x%.8x (%d)" % (self.version, self.version))
        print("Current Guid   : %s" % bin_to_string(self.current_guid))
        print()
        for i, e in enumerate(self.credhist_entries_list):
            print('[Entry #%d]' % i)
            e.summarize()

class DomainKey(Structure):
    structure = (
        ('Version', '<L=0'),
        ('SecretLen', '<L=0'),
        ('AccessCheckLen', '<L=0'),
        ('Guid', "16s=b"""),
        ('_SecretData', '_-SecretData', 'self["SecretLen"]'),
        ('SecretData', ':'),
        ('_AccessCheck', '_-AccessCheck', 'self["AccessCheckLen"]'),
        ('AccessCheck', ':'),
    )
    def dump(self):
        print("[DOMAINKEY]")
        print("Version       : %8x (%d)" % (self['Version'], self['Version']))
        print("Guid          : %s" % bin_to_string(self['Guid']))
        print("SecretLen     : %8x (%d)" % (self['SecretLen'], self['SecretLen']))
        print("AccessCheckLen: %.8x (%d)" % (self['AccessCheckLen'], self['AccessCheckLen']))
        print("SecretData    : %s" % (hexlify(self['SecretData'])))
        print("AccessCheck   : %s" % (hexlify(self['AccessCheck'])))
        print()

class DPAPI_SYSTEM(Structure):
    structure = (
        ('Version', '<L=0'),
        ('MachineKey', '20s=b""'),
        ('UserKey', '20s=b""'),
    )

    def dump(self):
        print("[DPAPI_SYSTEM]")
        print("Version    : %8x (%d)" % (self['Version'], self['Version']))
        print("MachineKey : 0x%s" % hexlify(self['MachineKey']).decode('latin-1'))
        print("UserKey    : 0x%s" % hexlify(self['UserKey']).decode('latin-1'))
        print()

class CredentialFile(Structure):
    structure = (
        ('Version', '<L=0'),
        ('Size', '<L=0'),
        ('Unknown', '<L=0'),
        ('_Data', '_-Data', 'self["Size"]'),
        ('Data', ':'),
    )
    #def dump(self):
    #    print("[CREDENTIAL FILE]")
    #    print("Version    : %8x (%d)" % (self['Version'], self['Version']))
    #    print("MachineKey : %s" % hexlify(self['MachineKey']))
    #    print("UserKey    : %s" % hexlify(self['UserKey']))
    #    print("CryptAlgo   : %.8x (%d) (%s)" % (self['CryptAlgo'], self['CryptAlgo'], ALGORITHMS(self['CryptAlgo']).name))
    #    print()


class DPAPI_BLOB(Structure):
    structure = (
        ('Version', '<L=0'),
        ('GuidCredential', "16s=b"""),
        ('MasterKeyVersion', '<L=0'),
        ('GuidMasterKey', "16s=b"""),
        ('Flags', '<L=0'),

        ('DescriptionLen', '<L=0'),
        ('_Description', '_-Description', 'self["DescriptionLen"]'),
        ('Description', ':'),

        ('CryptAlgo', '<L=0'),
        ('CryptAlgoLen', '<L=0'),

        ('SaltLen', '<L=0'),
        ('_Salt', '_-Salt', 'self["SaltLen"]'),
        ('Salt', ':'),

        ('HMacKeyLen', '<L=0'),
        ('_HMacKey', '_-HMacKey', 'self["HMacKeyLen"]'),
        ('HMacKey', ':'),

        ('HashAlgo', '<L=0'),
        ('HashAlgoLen', '<L=0'),

        ('HMac', '<L=0'),
        ('_HMac', '_-HMac', 'self["HMac"]'),
        ('HMac', ':'),

        ('DataLen', '<L=0'),
        ('_Data', '_-Data', 'self["DataLen"]'),
        ('Data', ':'),

        ('SignLen', '<L=0'),
        ('_Sign', '_-Sign', 'self["SignLen"]'),
        ('Sign', ':'),

    )

    def dump(self):
        print("[BLOB]")
        print("Version          : %8x (%d)" % (self['Version'], self['Version']))
        print("Guid Credential  : %s" % bin_to_string(self['GuidCredential']))
        print("MasterKeyVersion : %8x (%d)" % (self['MasterKeyVersion'], self['MasterKeyVersion']))
        print("Guid MasterKey   : %s" % bin_to_string(self['GuidMasterKey']))
        print("Flags            : %8x (%s)" % (self['Flags'], getFlags(FLAGS, self['Flags'])))
        print("Description      : %s" % (self['Description'].decode('utf-16le')))
        print("CryptAlgo        : %.8x (%d) (%s)" % (self['CryptAlgo'], self['CryptAlgo'], ALGORITHMS(self['CryptAlgo']).name))
        print("Salt             : %s" % (hexlify(self['Salt'])))
        print("HMacKey          : %s" % (hexlify(self['HMacKey'])))
        print("HashAlgo         : %.8x (%d) (%s)" % (self['HashAlgo'], self['HashAlgo'], ALGORITHMS(self['HashAlgo']).name))
        print("HMac             : %s" % (hexlify(self['HMac'])))
        print("Data             : %s" % (hexlify(self['Data'])))
        print("Sign             : %s" % (hexlify(self['Sign'])))
        print()


    def deriveKey(self, sessionKey):
        def fixparity(deskey):
            from six import indexbytes, b
            temp = b''
            for i in range(len(deskey)):
                t = (bin(indexbytes(deskey,i))[2:]).rjust(8,'0')
                if t[:7].count('1') %2 == 0:
                    temp+= b(chr(int(t[:7]+'1',2)))
                else:
                    temp+= b(chr(int(t[:7]+'0',2)))
            return temp

        if len(sessionKey) > ALGORITHMS_DATA[self['HashAlgo']][4]:
            derivedKey = HMAC.new(sessionKey,  digestmod = ALGORITHMS_DATA[self['HashAlgo']][1]).digest()
        else:
            derivedKey = sessionKey


        if len(derivedKey) < ALGORITHMS_DATA[self['CryptAlgo']][0]:
            # Extend the key
            derivedKey += b'\x00'*ALGORITHMS_DATA[self['HashAlgo']][4]
            ipad = bytearray([ i ^ 0x36 for i in bytearray(derivedKey)][:ALGORITHMS_DATA[self['HashAlgo']][4]])
            opad = bytearray([ i ^ 0x5c for i in bytearray(derivedKey)][:ALGORITHMS_DATA[self['HashAlgo']][4]])
            derivedKey = ALGORITHMS_DATA[self['HashAlgo']][1].new(ipad).digest() + \
                ALGORITHMS_DATA[self['HashAlgo']][1].new(opad).digest()
            derivedKey = fixparity(derivedKey)

        return derivedKey

    def decrypt(self, key, entropy = None):
        keyHash = SHA1.new(key).digest()
        sessionKey = HMAC.new(keyHash, self['Salt'], ALGORITHMS_DATA[self['HashAlgo']][1])
        if entropy is not None:
            sessionKey.update(entropy)

        sessionKey = sessionKey.digest()

        # Derive the key
        derivedKey = self.deriveKey(sessionKey)

        cipher = ALGORITHMS_DATA[self['CryptAlgo']][1].new(derivedKey[:ALGORITHMS_DATA[self['CryptAlgo']][0]],
                                mode=ALGORITHMS_DATA[self['CryptAlgo']][2], iv=b'\x00'*ALGORITHMS_DATA[self['CryptAlgo']][3])
        cleartext = unpad(cipher.decrypt(self['Data']), ALGORITHMS_DATA[self['CryptAlgo']][1].block_size)

        # Now check the signature

        # ToDo Fix this, it's just ugly, more testing so we can remove one
        toSign = (self.rawData[20:][:len(self.rawData)-20-len(self['Sign'])-4])

        # Calculate the different HMACKeys
        keyHash2 = keyHash + b"\x00"*ALGORITHMS_DATA[self['HashAlgo']][1].block_size
        ipad = bytearray([i ^ 0x36 for i in bytearray(keyHash2)][:ALGORITHMS_DATA[self['HashAlgo']][1].block_size])
        opad = bytearray([i ^ 0x5c for i in bytearray(keyHash2)][:ALGORITHMS_DATA[self['HashAlgo']][1].block_size])
        a = ALGORITHMS_DATA[self['HashAlgo']][1].new(ipad)
        a.update(self['HMac'])

        hmacCalculated1 = ALGORITHMS_DATA[self['HashAlgo']][1].new(opad)
        hmacCalculated1.update(a.digest())

        if entropy is not None:
            hmacCalculated1.update(entropy)

        hmacCalculated1.update(toSign)

        hmacCalculated3 = HMAC.new(keyHash, self['HMac'], ALGORITHMS_DATA[self['HashAlgo']][1])
        if entropy is not None:
            hmacCalculated3.update(entropy)

        hmacCalculated3.update(toSign)

        if hmacCalculated1.digest() == self['Sign'] or hmacCalculated3.digest() == self['Sign']:
            return cleartext
        else:
            return None

class VAULT_ATTRIBUTE(Structure):
    structure = (
        ('Id', '<L=0'),
        ('Unknown1', '<L=0'),
        ('Unknown2', '<L=0'),
        ('Unknown3', '<L=0'),
    )

    padding = (
        ('Pad', '6s=b""'),
    )

    id100 = (
        ('Unknown5', '<L=0'),
    )

    extended = (
        ('Size','<L=0'),
        ('IVPresent', '<B=?&IVSize'),
        ('IVSize', '<L=0'),
        ('_IV', '_-IV', 'self["IVSize"] if self["IVSize"] is not None else 0'),
        ('IV', ':'),
        ('_Data','_-Data', 'self["Size"]-self["IVSize"]-5 if self["IVPresent"] else self["Size"]-1'),
        ('Data',':'),
    )
    def __init__(self, data = None, alignment = 0):
        if len(data) > 20:
            if data[16:][:6] == b'\x00'*6:
                self.structure += self.padding
            if unpack('<L',data[:4])[0] >= 100:
                self.structure += self.id100
            if len(data[16:]) >= 9:
                self.structure += self.extended
        Structure.__init__(self, data, alignment)


    def dump(self):
        print("[ATTRIBUTE %d]" % self['Id'])
        if len(self.rawData) > 28:
            print("Size   : 0x%x" % self['Size'])
            if self['IVPresent'] > 0:
                print("IVSize : 0x%x" % self['IVSize'])
                print("IV     : %s" % hexlify(self['IV']))
            print("Data   : %s" % hexlify(self['Data']))

class VAULT_ATTRIBUTE_MAP_ENTRY(Structure):
    structure = (
        ('Id', '<L=0'),
        ('Offset', '<L=0'),
        ('Unknown1', '<L=0'),
    )
    def dump(self):
        print("[MAP ENTRY %d @ 0x%.8x]" % (self['Id'], self['Offset']))

class VAULT_VCRD(Structure):
    structure = (
        ('SchemaGuid', "16s=b"""),
        ('Unknown0', '<L=0'),
        ('LastWritten', '<Q=0'),
        ('Unknown1', '<L=0'),
        ('Unknown2', '<L=0'),
        ('FriendlyNameLen', '<L=0'),
        ('FriendlyNameL', '_-FriendlyName', 'self["FriendlyNameLen"]'),
        ('FriendlyName', ':'),
        ('AttributesMapsSize', '<L=0'),
        ('AttributeL', '_-AttributeMaps', 'self["AttributesMapsSize"]'),
        ('AttributeMaps', ':'),
        ('Data', ':'),
    )

    def __init__(self, data = None, alignment = 0):
        Structure.__init__(self, data, alignment)
        if data is not None:
            # Process the MAP entries
            self.mapEntries = list()
            data = self['AttributeMaps']
            for i in range(self['AttributesMapsSize']//len(VAULT_ATTRIBUTE_MAP_ENTRY())):
                entry = VAULT_ATTRIBUTE_MAP_ENTRY(data)
                self.mapEntries.append(entry)
                data = data[len(VAULT_ATTRIBUTE_MAP_ENTRY()):]

            self.attributesLen = list()

            for i in range(len(self.mapEntries)):
                if i > 0:
                    self.attributesLen.append(self.mapEntries[i]['Offset']-self.mapEntries[i-1]['Offset'])

            self.attributesLen.append(len(self.rawData) - self.mapEntries[i]['Offset'] )

            self.attributes = list()
            for i, entry in enumerate(self.mapEntries):
                attribute = VAULT_ATTRIBUTE(self.rawData[entry['Offset']:][:self.attributesLen[i]])
                self.attributes.append(attribute)

            # Do we have remaining data?
            self['Data'] = self.rawData[self.mapEntries[-1]['Offset']+len(self.attributes[-1].getData()):]

    def dump(self):
        print("[VCRD]")
        print("SchemaGuid  : %s" % bin_to_string(self['SchemaGuid']))
        print("LastWritten : %s" % (datetime.fromtimestamp(getUnixTime(self['LastWritten']), tz=timezone.utc)))
        print("FriendlyName: %s" % (self['FriendlyName'].decode('utf-16le')))
        print()
        for i,entry in enumerate(self.mapEntries):
            entry.dump()
            self.attributes[i].dump()
        print()
        print("Remaining   : %s" % (hexlify(self['Data'])))
        print()

class VAULT_VPOL(Structure):
    structure = (
        ('Version', '<L=0'),
        ('Guid', "16s=b"""),
        ('DescriptionLen', '<L=0'),
        ('_Description', '_-Description', 'self["DescriptionLen"]'),
        ('Description', ':'),
        ('Unknown', '12s=b""'),
        ('Size', '<L=0'),
        ('Guid2', "16s=b"""),
        ('Guid3', "16s=b"""),
        ('KeySize','<L=0'),
        ('_Blob', '_-Blob', 'self["KeySize"]'),
        ('Blob', ':', DPAPI_BLOB),
    )

    def dump(self):
        print("[VAULT_VPOL]")
        print("Version      : %8x (%d)" % (self['Version'], self['Version']))
        print("Guid         : %s" % bin_to_string(self['Guid']))
        print("Description  : %s" % (self['Description'].decode('utf-16le')))
        print("Size         : 0x%.8x (%d)" % (self['Size'], self['Size']))
        print("Guid2        : %s" % bin_to_string(self['Guid2']))
        print("Guid3        : %s" % bin_to_string(self['Guid3']))
        print("KeySize      : 0x%.8x (%d)" % (self['KeySize'], self['KeySize']))
        self['Blob'].dump()
        print()

# from bcrypt.h
class BCRYPT_KEY_DATA_BLOB_HEADER(Structure):
    structure = (
        ('dwMagic','<L=0'),
        ('dwVersion','<L=0'),
        ('cbKeyData','<L=0'),
        ('_bKey','_-bKey', 'self["cbKeyData"]'),
        ('bKey',':'),
    )

# from https://media.defcon.org/DEF%20CON%2024/DEF%20CON%2024%20presentations/DEFCON-24-Jkambic-Cunning-With-Cng-Soliciting-Secrets-From-Schannel-WP.pdf
class BCRYPT_KSSM_DATA_BLOB_HEADER(Structure):
    structure = (
        ('cbLength','<L=0'),
        ('dwKeyMagic','<L=0'),
        ('dwUnknown2','<L=0'),
        ('dwUnknown3','<L=0'),
        ('dwKeyBitLen','<L=0'),
        ('cbKeyLength','<L=0'),
        #('_bKey','_-bKey', 'self["cbKeyData"]'),
        #('AesKey','32s=""'),
        #('dwUnknown4','<L=0'),
        #('KeySchedule','448s=""'),
        #('dwUnknown5','<L=0'),
        #('cbScheduleLen','<L=0'),
        #('Unknown6','16s=""'),
    )

class BCRYPT_KEY_WRAP(Structure):
    structureKDBM = (
        ('Size','<L=0'),
        ('Version','<L=0'),
        ('Unknown2','<L=0'),
        ('_bKeyBlob','_-bKeyBlob', 'self["Size"]'),
        ('bKeyBlob',':', BCRYPT_KEY_DATA_BLOB_HEADER),
    )
    structureKSSM = (
        ('Size','<L=0'),
        ('Version','<L=0'),
        ('Unknown2','<L=0'),
        ('_bKeyBlob','_-bKeyBlob', 'self["Size"]-8'),
        ('bKeyBlob',':'),
    )
    def __init__(self, data = None, alignment = 0):
        if len(data) >=16:
            if data[0:1] == b'\x24' or data[0:1] == b'\x34':
                self.structure = self.structureKDBM
            else:
                self.structure = self.structureKSSM
        Structure.__init__(self, data, alignment)

class VAULT_VPOL_KEYS(Structure):
    structure = (
        ('Key1',':', BCRYPT_KEY_WRAP),
        ('Key2',':', BCRYPT_KEY_WRAP),
    )
    def dump(self):
        print("[VAULT_VPOL_KEYS]")
        if self['Key1']['Size'] > 0x24:
            print('Key1:')
            hexdump(self['Key1']['bKeyBlob'])
            print('Key2:')
            hexdump(self['Key2']['bKeyBlob'])
        else:
            print('Key1: 0x%s' % hexlify(self['Key1']['bKeyBlob']['bKey']).decode('latin-1'))
            print('Key2: 0x%s' % hexlify(self['Key2']['bKeyBlob']['bKey']).decode('latin-1'))
            print()

class VAULT_INTERNET_EXPLORER(Structure):
    structure = (
        ('Version','<L=0'),
        ('Count','<L=0'),
        ('Unknown','<L=0'),
        ('Id1', '<L=0'),
        ('UsernameLen', '<L=0'),
        ('_Username', '_-Username','self["UsernameLen"]'),
        ('Username', ':'),

        ('Id2', '<L=0'),
        ('ResourceLen', '<L=0'),
        ('_Resource', '_-Resource', 'self["ResourceLen"]'),
        ('Resource', ':'),

        ('Id3', '<L=0'),
        ('PasswordLen', '<L=0'),
        ('_Password', '_-Password', 'self["PasswordLen"]'),
        ('Password', ':'),
    )
    def fromString(self, data):
        Structure.fromString(self, data)

    def dump(self):
        print("[Internet Explorer]")
        print('Username        : %s' % self['Username'].decode('utf-16le'))
        print('Resource        : %s' % self['Resource'].decode('utf-16le'))
        print('Password        : %s' % (hexlify(self['Password'])))
        print()

class VAULT_WIN_BIO_KEY(Structure):
    structure = (
        ('Version','<L=0'),
        ('Count','<L=0'),
        ('Unknown','<L=0'),
        ('Id1', '<L=0'),
        ('SidLen', '<L=0'),
        ('_Sid', '_-Sid','self["SidLen"]'),
        ('Sid', ':'),

        ('Id2', '<L=0'),
        ('NameLen', '<L=0'),
        ('_Name', '_-Name', 'self["NameLen"]'),
        ('Name', ':'),

        ('Id3', '<L=0'),
        ('BioKeyLen', '<L=0'),
        ('_BioKey', '_-BioKey', 'self["BioKeyLen"]'),
        ('BioKey', ':'),
    )
    def fromString(self, data):
        Structure.fromString(self, data)
        if data is not None:
            bioKey = BCRYPT_KEY_DATA_BLOB_HEADER(unhexlify(self['BioKey'].decode('utf-16le')[:-1]))
            self['BioKey'] = bioKey

    def dump(self):
        print("[WINDOWS BIOMETRIC KEY]")
        print('Sid          : %s' % RPC_SID(b'\x05\x00\x00\x00'+self['Sid']).formatCanonical())
        print('Friendly Name: %s' % self['Name'].decode('utf-16le'))
        print('Biometric Key: 0x%s' % (hexlify(self['BioKey']['bKey'])).decode('latin-1'))
        print()

class NGC_LOCAL_ACCOOUNT(Structure):
    structure = (
        ('Version','<L=0'),
        ('UnlockKeySize','<L=0'),
        ('IVSize','<L=0'),
        ('CipherTextSize','<L=0'),
        ('MustBeZeroTest','<L=0'),
        ('_UnlockKey', '_-UnlockKey', 'self["UnlockKeySize"]'),
        ('UnlockKey', ':'),
        ('_IV', '_-IV', 'self["IVSize"]'),
        ('IV', ':'),
        ('_CipherText', '_-CipherText', 'self["CipherTextSize"]'),
        ('CipherText', ':'),
    )
#    def __init__(self, data=None, alignment = 0):
#        hexdump(data)
    def dump(self):
        print("[NGC LOCAL ACCOOUNT]")
        print('UnlockKey    : %s' % hexlify(self["UnlockKey"]))
        print('IV           : %s' % hexlify(self["IV"]))
        print('CipherText   : %s' % hexlify(self["CipherText"]))

class VAULT_NGC_ACCOOUNT(Structure):
    structure = (
        ('Version','<L=0'),
        ('Count','<L=0'),
        ('Unknown','<L=0'),
        ('Id1', '<L=0'),
        ('SidLen', '<L=0'),
        ('_Sid', '_-Sid','self["SidLen"]'),
        ('Sid', ':'),

        ('Id2', '<L=0'),
        ('NameLen', '<L=0'),
        ('_Name', '_-Name', 'self["NameLen"]'),
        ('Name', ':'),

        ('Id3', '<L=0'),
        ('BlobLen', '<L=0'),
        ('Blob', '_-Blob', 'self["BlobLen"]'),
        ('Blob', ':', NGC_LOCAL_ACCOOUNT),
    )
    def dump(self):
        print("[NGC VAULT]")
        print('Sid          : %s' % RPC_SID(b'\x05\x00\x00\x00'+self['Sid']).formatCanonical())
        print('Friendly Name: %s' % self['Name'].decode('utf-16le'))
        self['Blob'].dump()
        print()

VAULT_KNOWN_SCHEMAS = {
    'WinBio Key': VAULT_WIN_BIO_KEY,
    'NGC Local Accoount Logon Vault Credential': VAULT_NGC_ACCOOUNT,
    'Internet Explorer': VAULT_INTERNET_EXPLORER,
}

class CREDENTIAL_ATTRIBUTE(Structure):
    # some info here https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_credential_attributea
    structure = (
        ('Flags','<L=0'),

        ('KeyWordSize', '<L=0'),
        ('_KeyWord', '_-KeyWord', 'self["KeyWordSize"]'),
        ('KeyWord', ':'),

        ('DataSize', '<L=0'),
        ('_Data', '_-Data', 'self["DataSize"]'),
        ('Data', ':'),
    )

    def dump(self):
        print("KeyWord : %s" % (self['KeyWord'].decode('utf-16le')))
        #print("Flags   : %8x (%s)" % (self['Flags'], getFlags(CREDENTIAL_FLAGS, self['Flags'])))
        print("Data    : ")
        hexdump(self['Data'])

class CREDENTIAL_BLOB(Structure):
    # some info here https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_credentiala
    structure = (
        ('Flags','<L=0'),
        ('Size','<L=0'),
        ('Unknown0','<L=0'),
        ('Type','<L=0'),
        ('Flags2','<L=0'),
        ('LastWritten','<Q=0'),
        ('Unknown2','<L=0'),
        ('Persist','<L=0'),
        ('AttrCount','<L=0'),
        ('Unknown3','<Q=0'),

        ('TargetSize','<L=0'),
        ('_Target','_-Target','self["TargetSize"]'),
        ('Target',':'),

        ('TargetAliasSize', '<L=0'),
        ('_TargetAliast', '_-TargetAlias', 'self["TargetAliasSize"]'),
        ('TargetAlias', ':'),

        ('DescriptionSize', '<L=0'),
        ('_Description', '_-Description', 'self["DescriptionSize"]'),
        ('Description', ':'),

        ('UnknownSize', '<L=0'),
        ('_Unknown', '_-Unknown', 'self["UnknownSize"]'),
        ('Unknown', ':'),

        ('UsernameSize', '<L=0'),
        ('_Username', '_-Username', 'self["UsernameSize"]'),
        ('Username', ':'),

        ('Unknown3Size', '<L=0'),
        ('_Unknown3', '_-Unknown3', 'self["Unknown3Size"]'),
        ('Unknown3', ':'),

        ('Remaining', ':'),
    )
    def __init__(self, data = None, alignment = 0):
        Structure.__init__(self, data, alignment)
        self.attributes = 0
        if data is not None:
            # Unpack the attributes
            remaining = self['Remaining']
            self.attributes = list()
            for i in range(self['AttrCount']):
                attr = CREDENTIAL_ATTRIBUTE(remaining)
                self.attributes.append(attr)
                remaining = remaining[len(attr):]

    def dump(self):
        print("[CREDENTIAL]")
        print("LastWritten : %s" % (datetime.fromtimestamp(getUnixTime(self['LastWritten']), tz=timezone.utc)))
        print("Flags       : 0x%.8x (%s)" % (self['Flags'], getFlags(CREDENTIAL_FLAGS, self['Flags'])))
        print("Persist     : 0x%.8x (%s)" % (self['Persist'], CREDENTIAL_PERSIST(self['Persist']).name))
        print("Type        : 0x%.8x (%s)" % (self['Type'], CREDENTIAL_TYPE(self['Type']).name))
        print("Target      : %s" % (self['Target'].decode('utf-16le')))
        print("Description : %s" % (self['Description'].decode('utf-16le')))
        print("Unknown     : %s" % (self['Unknown'].decode('utf-16le')))
        print("Username    : %s" % (self['Username'].decode('utf-16le')))
        try:
            print("Unknown     : %s" % (self['Unknown3'].decode('utf-16le')))
        except UnicodeDecodeError:
            print("Unknown     : %s" % (self['Unknown3'].decode('latin-1')))

        print()
        for entry in self.attributes:
            entry.dump()

ALG_ID = '<L=0'

class P_BACKUP_KEY(Structure):
    structure = (
        ('Version', '<L=0'),
        ('Data', ':'),
    )

class PREFERRED_BACKUP_KEY(Structure):
    structure = (
        ('Version', '<L=0'),
        ('KeyLength', '<L=0'),
        ('CertificateLength', '<L=0'),
        ('Data', ':'),
    )

class PVK_FILE_HDR(Structure):
    structure = (
        ('dwMagic', '<L=0'),
        ('dwVersion', '<L=0'),
        ('dwKeySpec', '<L=0'),
        ('dwEncryptType', '<L=0'),
        ('cbEncryptData', '<L=0'),
        ('cbPvk', '<L=0'),
    )

class PUBLICKEYSTRUC(Structure):
    structure = (
        ('bType', '<B=0'),
        ('bVersion', '<B=0'),
        ('reserved', '<H=0'),
        ('aiKeyAlg', ALG_ID),
    )

class RSAPUBKEY(Structure):
    structure = (
        ('magic', '<L=0'),
        ('bitlen', '<L=0'),
        ('pubexp', '<L=0'),
    )

class PUBLIC_KEY_BLOB(Structure):
    structure = (
        ('publickeystruc', ':', PUBLICKEYSTRUC),
        ('rsapubkey', ':', RSAPUBKEY),
        ('_modulus', '_-modulus', 'self["rsapubkey"]["bitlen"] // 8'),
    )

class PRIVATE_KEY_BLOB(Structure):
    structure = (
        ('publickeystruc', ':', PUBLICKEYSTRUC),
        ('rsapubkey', ':', RSAPUBKEY),
        ('_modulus', '_-modulus', 'self["rsapubkey"]["bitlen"] // 8'),
        ('modulus', ':'),
        ('_prime1', '_-prime1', 'self["rsapubkey"]["bitlen"] // 16'),
        ('prime1', ':'),
        ('_prime2', '_-prime2', 'self["rsapubkey"]["bitlen"] // 16'),
        ('prime2', ':'),
        ('_exponent1', '_-exponent1', 'self["rsapubkey"]["bitlen"] // 16'),
        ('exponent1', ':'),
        ('_exponent2', '_-exponent2', 'self["rsapubkey"]["bitlen"] // 16'),
        ('exponent2', ':'),
        ('_coefficient', '_-coefficient', 'self["rsapubkey"]["bitlen"] // 16'),
        ('coefficient', ':'),
        ('_privateExponent', '_-privateExponent', 'self["rsapubkey"]["bitlen"] // 8'),
        ('privateExponent', ':'),
    )

class SIMPLE_KEY_BLOB(Structure):
    structure = (
        ('publickeystruc', ':', PUBLICKEYSTRUC),
        ('algid', ALG_ID),
        ('encryptedkey', ':'),
    )

class DPAPI_DOMAIN_RSA_MASTER_KEY(Structure):
    structure = (
        ('cbMasterKey', '<L=0'),
        ('cbSuppKey', '<L=0'),
        ('buffer', ':'),
    )

def privatekeyblob_to_pkcs1(key):
    '''
    parse private key into pkcs#1 format
    :param key:
    :return:
    '''
    modulus = bytes_to_long(key['modulus'][::-1]) # n
    prime1 = bytes_to_long(key['prime1'][::-1]) # p
    prime2 = bytes_to_long(key['prime2'][::-1]) # q
    exp1 = bytes_to_long(key['exponent1'][::-1])
    exp2 = bytes_to_long(key['exponent2'][::-1])
    coefficient = bytes_to_long(key['coefficient'][::-1])
    privateExp = bytes_to_long(key['privateExponent'][::-1]) # d
    if PY3:
        long = int
    pubExp = long(key['rsapubkey']['pubexp']) # e
    # RSA.Integer(prime2).inverse(prime1) # u

    r = RSA.construct((modulus, pubExp, privateExp, prime1, prime2))
    return r

def deriveKeysFromUser(sid, password):
        # Will generate two keys, one with SHA1 and another with MD4
        key1 = HMAC.new(SHA1.new(password.encode('utf-16le')).digest(), (sid + '\0').encode('utf-16le'), SHA1).digest()
        key2 = HMAC.new(MD4.new(password.encode('utf-16le')).digest(), (sid + '\0').encode('utf-16le'), SHA1).digest()
        # For Protected users
        tmpKey = pbkdf2_hmac('sha256', MD4.new(password.encode('utf-16le')).digest(), sid.encode('utf-16le'), 10000)
        tmpKey2 = pbkdf2_hmac('sha256', tmpKey, sid.encode('utf-16le'), 1)[:16]
        key3 = HMAC.new(tmpKey2, (sid + '\0').encode('utf-16le'), SHA1).digest()[:20]

        return [key1, key2, key3]

def deriveKeysFromUserkey(sid, pwdhash):
    if len(pwdhash) == 20:
        # SHA1
        key1 = HMAC.new(pwdhash, (sid + '\0').encode('utf-16le'), SHA1).digest()
        return [key1]

    # Assume MD4
    key1 = HMAC.new(pwdhash, (sid + '\0').encode('utf-16le'), SHA1).digest()
    # For Protected users
    tmpKey = pbkdf2_hmac('sha256', pwdhash, sid.encode('utf-16le'), 10000)
    tmpKey2 = pbkdf2_hmac('sha256', tmpKey, sid.encode('utf-16le'), 1)[:16]
    key2 = HMAC.new(tmpKey2, (sid + '\0').encode('utf-16le'), SHA1).digest()[:20]

    return [key1, key2]
