# Impacket - Collection of Python classes for working with network protocols.
#
# Copyright (C) 2023 Fortra. 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
from binascii import unhexlify, hexlify
from struct import pack
from Cryptodome.Hash import HMAC, SHA512, SHA1
from Cryptodome.Cipher import AES, DES3
from Cryptodome.Util.Padding import unpad
from Cryptodome.PublicKey import RSA
from Cryptodome.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 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.utcfromtimestamp(getUnixTime(self['LastWritten']))))
        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.utcfromtimestamp(getUnixTime(self['LastWritten']))))
        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
