summaryrefslogtreecommitdiff
path: root/foreign/client_handling/lazagne/config/DPAPI/crypto.py
diff options
context:
space:
mode:
Diffstat (limited to 'foreign/client_handling/lazagne/config/DPAPI/crypto.py')
-rw-r--r--foreign/client_handling/lazagne/config/DPAPI/crypto.py366
1 files changed, 366 insertions, 0 deletions
diff --git a/foreign/client_handling/lazagne/config/DPAPI/crypto.py b/foreign/client_handling/lazagne/config/DPAPI/crypto.py
new file mode 100644
index 0000000..121a921
--- /dev/null
+++ b/foreign/client_handling/lazagne/config/DPAPI/crypto.py
@@ -0,0 +1,366 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#############################################################################
+# ##
+# This file is part of DPAPIck ##
+# Windows DPAPI decryption & forensic toolkit ##
+# ##
+# ##
+# Copyright (C) 2010, 2011 Cassidian SAS. All rights reserved. ##
+# This document is the property of Cassidian SAS, it may not be copied or ##
+# circulated without prior licence ##
+# ##
+# Author: Jean-Michel Picod <jmichel.p@gmail.com> ##
+# ##
+# This program is distributed under GPLv3 licence (see LICENCE.txt) ##
+# ##
+#############################################################################
+
+import array
+import hashlib
+import hmac
+import struct
+import sys
+
+from foreign.client_handling.lazagne.config.crypto.rc4 import RC4
+from foreign.client_handling.lazagne.config.crypto.pyaes.aes import AESModeOfOperationCBC, AESModeOfOperationECB
+from foreign.client_handling.lazagne.config.crypto.pyDes import triple_des, des, ECB, CBC
+from foreign.client_handling.lazagne.config.winstructure import char_to_int, chr_or_byte
+
+
+try:
+ xrange
+except NameError:
+ xrange = range
+
+AES_BLOCK_SIZE = 16
+
+
+class CryptoAlgo(object):
+ """
+ This class is used to wrap Microsoft algorithm IDs with M2Crypto
+ """
+
+ class Algo(object):
+ def __init__(self, data):
+ self.data = data
+
+ def __getattr__(self, attr):
+ if attr in self.data:
+ return self.data[attr]
+ raise AttributeError(attr)
+
+ _crypto_data = {}
+
+ @classmethod
+ def add_algo(cls, algnum, **kargs):
+ cls._crypto_data[algnum] = cls.Algo(kargs)
+ if 'name' in kargs:
+ kargs['ID'] = algnum
+ cls._crypto_data[kargs['name']] = cls.Algo(kargs)
+
+ @classmethod
+ def get_algo(cls, algnum):
+ return cls._crypto_data[algnum]
+
+ def __init__(self, i):
+ self.algnum = i
+ self.algo = CryptoAlgo.get_algo(i)
+
+ name = property(lambda self: self.algo.name)
+ module = property(lambda self: self.algo.module)
+ keyLength = property(lambda self: self.algo.keyLength / 8)
+ ivLength = property(lambda self: self.algo.IVLength / 8)
+ blockSize = property(lambda self: self.algo.blockLength / 8)
+ digestLength = property(lambda self: self.algo.digestLength / 8)
+
+ def do_fixup_key(self, key):
+ try:
+ return self.algo.keyFixup.__call__(key)
+ except AttributeError:
+ return key
+
+ def __repr__(self):
+ return "%s [%#x]" % (self.algo.name, self.algnum)
+
+
+def des_set_odd_parity(key):
+ _lut = [1, 1, 2, 2, 4, 4, 7, 7, 8, 8, 11, 11, 13, 13, 14, 14, 16, 16, 19,
+ 19, 21, 21, 22, 22, 25, 25, 26, 26, 28, 28, 31, 31, 32, 32, 35, 35, 37,
+ 37, 38, 38, 41, 41, 42, 42, 44, 44, 47, 47, 49, 49, 50, 50, 52, 52, 55,
+ 55, 56, 56, 59, 59, 61, 61, 62, 62, 64, 64, 67, 67, 69, 69, 70, 70, 73,
+ 73, 74, 74, 76, 76, 79, 79, 81, 81, 82, 82, 84, 84, 87, 87, 88, 88, 91,
+ 91, 93, 93, 94, 94, 97, 97, 98, 98, 100, 100, 103, 103, 104, 104, 107,
+ 107, 109, 109, 110, 110, 112, 112, 115, 115, 117, 117, 118, 118, 121,
+ 121, 122, 122, 124, 124, 127, 127, 128, 128, 131, 131, 133, 133, 134,
+ 134, 137, 137, 138, 138, 140, 140, 143, 143, 145, 145, 146, 146, 148,
+ 148, 151, 151, 152, 152, 155, 155, 157, 157, 158, 158, 161, 161, 162,
+ 162, 164, 164, 167, 167, 168, 168, 171, 171, 173, 173, 174, 174, 176,
+ 176, 179, 179, 181, 181, 182, 182, 185, 185, 186, 186, 188, 188, 191,
+ 191, 193, 193, 194, 194, 196, 196, 199, 199, 200, 200, 203, 203, 205,
+ 205, 206, 206, 208, 208, 211, 211, 213, 213, 214, 214, 217, 217, 218,
+ 218, 220, 220, 223, 223, 224, 224, 227, 227, 229, 229, 230, 230, 233,
+ 233, 234, 234, 236, 236, 239, 239, 241, 241, 242, 242, 244, 244, 247,
+ 247, 248, 248, 251, 251, 253, 253, 254, 254]
+ tmp = array.array("B")
+ tmp.fromstring(key)
+ for i, v in enumerate(tmp):
+ tmp[i] = _lut[v]
+ return tmp.tostring()
+
+
+CryptoAlgo.add_algo(0x6601, name="DES", keyLength=64, blockLength=64, IVLength=64, module=des,
+ keyFixup=des_set_odd_parity)
+CryptoAlgo.add_algo(0x6603, name="DES3", keyLength=192, blockLength=64, IVLength=64, module=triple_des,
+ keyFixup=des_set_odd_parity)
+CryptoAlgo.add_algo(0x6611, name="AES", keyLength=128, blockLength=128, IVLength=128)
+CryptoAlgo.add_algo(0x660e, name="AES-128", keyLength=128, blockLength=128, IVLength=128)
+CryptoAlgo.add_algo(0x660f, name="AES-192", keyLength=192, blockLength=128, IVLength=128)
+CryptoAlgo.add_algo(0x6610, name="AES-256", keyLength=256, blockLength=128, IVLength=128)
+CryptoAlgo.add_algo(0x8009, name="HMAC", digestLength=160, blockLength=512)
+CryptoAlgo.add_algo(0x8003, name="md5", digestLength=128, blockLength=512)
+CryptoAlgo.add_algo(0x8004, name="sha1", digestLength=160, blockLength=512)
+CryptoAlgo.add_algo(0x800c, name="sha256", digestLength=256, blockLength=512)
+CryptoAlgo.add_algo(0x800d, name="sha384", digestLength=384, blockLength=1024)
+CryptoAlgo.add_algo(0x800e, name="sha512", digestLength=512, blockLength=1024)
+
+
+def CryptSessionKeyXP(masterkey, nonce, hashAlgo, entropy=None, strongPassword=None, verifBlob=None):
+ """
+ Computes the decryption key for XP DPAPI blob, given the masterkey and optional information.
+
+ This implementation relies on a faulty implementation from Microsoft that does not respect the HMAC RFC.
+ Instead of updating the inner pad, we update the outer pad...
+ This algorithm is also used when checking the HMAC for integrity after decryption
+
+ :param masterkey: decrypted masterkey (should be 64 bytes long)
+ :param nonce: this is the nonce contained in the blob or the HMAC in the blob (integrity check)
+ :param entropy: this is the optional entropy from CryptProtectData() API
+ :param strongPassword: optional password used for decryption or the blob itself
+ :param verifBlob: optional encrypted blob used for integrity check
+ :returns: decryption key
+ :rtype : str
+ """
+ if len(masterkey) > 20:
+ masterkey = hashlib.sha1(masterkey).digest()
+
+ masterkey += b"\x00" * int(hashAlgo.blockSize)
+ ipad = b"".join(chr_or_byte(char_to_int(masterkey[i]) ^ 0x36) for i in range(int(hashAlgo.blockSize)))
+ opad = b"".join(chr_or_byte(char_to_int(masterkey[i]) ^ 0x5c) for i in range(int(hashAlgo.blockSize)))
+ digest = hashlib.new(hashAlgo.name)
+ digest.update(ipad)
+ digest.update(nonce)
+ tmp = digest.digest()
+ digest = hashlib.new(hashAlgo.name)
+ digest.update(opad)
+ digest.update(tmp)
+ if entropy is not None:
+ digest.update(entropy)
+ if strongPassword is not None:
+ strongPassword = hashlib.sha1(strongPassword.rstrip("\x00").encode("UTF-16LE")).digest()
+ digest.update(strongPassword)
+ elif verifBlob is not None:
+ digest.update(verifBlob)
+ return digest.digest()
+
+
+def CryptSessionKeyWin7(masterkey, nonce, hashAlgo, entropy=None, strongPassword=None, verifBlob=None):
+ """
+ Computes the decryption key for Win7+ DPAPI blob, given the masterkey and optional information.
+
+ This implementation relies on an RFC compliant HMAC implementation
+ This algorithm is also used when checking the HMAC for integrity after decryption
+
+ :param masterkey: decrypted masterkey (should be 64 bytes long)
+ :param nonce: this is the nonce contained in the blob or the HMAC in the blob (integrity check)
+ :param entropy: this is the optional entropy from CryptProtectData() API
+ :param strongPassword: optional password used for decryption or the blob itself
+ :param verifBlob: optional encrypted blob used for integrity check
+ :returns: decryption key
+ :rtype : str
+ """
+ if len(masterkey) > 20:
+ masterkey = hashlib.sha1(masterkey).digest()
+
+ digest = hmac.new(masterkey, digestmod=lambda: hashlib.new(hashAlgo.name))
+ digest.update(nonce)
+ if entropy is not None:
+ digest.update(entropy)
+ if strongPassword is not None:
+ strongPassword = hashlib.sha512(strongPassword.rstrip("\x00").encode("UTF-16LE")).digest()
+ digest.update(strongPassword)
+ elif verifBlob is not None:
+ digest.update(verifBlob)
+ return digest.digest()
+
+
+def CryptDeriveKey(h, cipherAlgo, hashAlgo):
+ """
+ Internal use. Mimics the corresponding native Microsoft function
+ """
+ if len(h) > hashAlgo.blockSize:
+ h = hashlib.new(hashAlgo.name, h).digest()
+ if len(h) >= cipherAlgo.keyLength:
+ return h
+ h += b"\x00" * int(hashAlgo.blockSize)
+ ipad = b"".join(chr_or_byte(char_to_int(h[i]) ^ 0x36) for i in range(int(hashAlgo.blockSize)))
+ opad = b"".join(chr_or_byte(char_to_int(h[i]) ^ 0x5c) for i in range(int(hashAlgo.blockSize)))
+ k = hashlib.new(hashAlgo.name, ipad).digest() + hashlib.new(hashAlgo.name, opad).digest()
+ k = k[:cipherAlgo.keyLength]
+ k = cipherAlgo.do_fixup_key(k)
+ return k
+
+
+def decrypt_lsa_key_nt5(lsakey, syskey):
+ """
+ This function decrypts the LSA key using the syskey
+ """
+ dg = hashlib.md5()
+ dg.update(syskey)
+ for i in xrange(1000):
+ dg.update(lsakey[60:76])
+ arcfour = RC4(dg.digest())
+ deskey = arcfour.encrypt(lsakey[12:60])
+ return [deskey[16 * x:16 * (x + 1)] for x in xrange(3)]
+
+
+def decrypt_lsa_key_nt6(lsakey, syskey):
+ """
+ This function decrypts the LSA keys using the syskey
+ """
+ dg = hashlib.sha256()
+ dg.update(syskey)
+ for i in range(1000):
+ dg.update(lsakey[28:60])
+
+ k = AESModeOfOperationECB(dg.digest())
+ keys = b"".join([k.encrypt(lsakey[60:][i:i + AES_BLOCK_SIZE]) for i in range(0, len(lsakey[60:]), AES_BLOCK_SIZE)])
+
+ size = struct.unpack_from("<L", keys)[0]
+ keys = keys[16:16 + size]
+ currentkey = "%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % struct.unpack("<L2H8B", keys[4:20])
+ nb = struct.unpack("<L", keys[24:28])[0]
+ off = 28
+ kd = {}
+ for i in range(nb):
+ g = "%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % struct.unpack("<L2H8B", keys[off:off + 16])
+ t, l = struct.unpack_from("<2L", keys[off + 16:])
+ k = keys[off + 24:off + 24 + l]
+ kd[g] = {"type": t, "key": k}
+ off += 24 + l
+ return (currentkey, kd)
+
+
+def SystemFunction005(secret, key):
+ """
+ This function is used to decrypt LSA secrets.
+ Reproduces the corresponding Windows internal function.
+ Taken from creddump project https://code.google.com/p/creddump/
+ """
+ decrypted_data = ''
+ j = 0
+ algo = CryptoAlgo(0x6603)
+ for i in range(0, len(secret), 8):
+ enc_block = secret[i:i + 8]
+ block_key = key[j:j + 7]
+ des_key = []
+ des_key.append(char_to_int(block_key[0]) >> 1)
+ des_key.append(((char_to_int(block_key[0]) & 0x01) << 6) | (char_to_int(block_key[1]) >> 2))
+ des_key.append(((char_to_int(block_key[1]) & 0x03) << 5) | (char_to_int(block_key[2]) >> 3))
+ des_key.append(((char_to_int(block_key[2]) & 0x07) << 4) | (char_to_int(block_key[3]) >> 4))
+ des_key.append(((char_to_int(block_key[3]) & 0x0F) << 3) | (char_to_int(block_key[4]) >> 5))
+ des_key.append(((char_to_int(block_key[4]) & 0x1F) << 2) | (char_to_int(block_key[5]) >> 6))
+ des_key.append(((char_to_int(block_key[5]) & 0x3F) << 1) | (char_to_int(block_key[6]) >> 7))
+ des_key.append(char_to_int(block_key[6]) & 0x7F)
+ des_key = algo.do_fixup_key("".join([chr(x << 1) for x in des_key]))
+
+ decrypted_data += des(des_key, ECB).decrypt(enc_block)
+ j += 7
+ if len(key[j:j + 7]) < 7:
+ j = len(key[j:j + 7])
+ dec_data_len = struct.unpack("<L", decrypted_data[:4])[0]
+ return decrypted_data[8:8 + dec_data_len]
+
+
+def decrypt_lsa_secret(secret, lsa_keys):
+ """
+ This function replaces SystemFunction005 for newer Windows
+ """
+ keyid = "%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % struct.unpack("<L2H8B", secret[4:20])
+ if keyid not in lsa_keys:
+ return None
+ algo = struct.unpack("<L", secret[20:24])[0]
+ dg = hashlib.sha256()
+ dg.update(lsa_keys[keyid]["key"])
+ for i in xrange(1000):
+ dg.update(secret[28:60])
+
+ c = AESModeOfOperationECB(dg.digest())
+ clear = b"".join([c.encrypt(secret[60:][i:i + AES_BLOCK_SIZE]) for i in range(0, len(secret[60:]), AES_BLOCK_SIZE)])
+
+ size = struct.unpack_from("<L", clear)[0]
+ return clear[16:16 + size]
+
+
+def pbkdf2(passphrase, salt, keylen, iterations, digest='sha1'):
+ """
+ Implementation of PBKDF2 that allows specifying digest algorithm.
+ Returns the corresponding expanded key which is keylen long.
+ """
+ buff = b""
+ i = 1
+ while len(buff) < keylen:
+ U = salt + struct.pack("!L", i)
+ i += 1
+ derived = hmac.new(passphrase, U, digestmod=lambda: hashlib.new(digest)).digest()
+ for r in xrange(iterations - 1):
+ actual = hmac.new(passphrase, derived, digestmod=lambda: hashlib.new(digest)).digest()
+ tmp = b''
+ for x, y in zip(derived, actual):
+ if sys.version_info > (3, 0):
+ tmp += struct.pack(">B", x ^ y)
+ else:
+ tmp += chr(char_to_int(x) ^ char_to_int(y))
+ derived = tmp
+ buff += derived
+ return buff[:int(keylen)]
+
+
+def derivePwdHash(pwdhash, sid, digest='sha1'):
+ """
+ Internal use. Computes the encryption key from a user's password hash
+ """
+ return hmac.new(pwdhash, (sid + "\0").encode("UTF-16LE"), digestmod=lambda: hashlib.new(digest)).digest()
+
+
+def dataDecrypt(cipherAlgo, hashAlgo, raw, encKey, iv, rounds):
+ """
+ Internal use. Decrypts data stored in DPAPI structures.
+ """
+ hname = {"HMAC": "sha1"}.get(hashAlgo.name, hashAlgo.name)
+ derived = pbkdf2(encKey, iv, cipherAlgo.keyLength + cipherAlgo.ivLength, rounds, hname)
+ key, iv = derived[:int(cipherAlgo.keyLength)], derived[int(cipherAlgo.keyLength):]
+ key = key[:int(cipherAlgo.keyLength)]
+ iv = iv[:int(cipherAlgo.ivLength)]
+
+ if "AES" in cipherAlgo.name:
+ cipher = AESModeOfOperationCBC(key, iv=iv)
+ cleartxt = b"".join([cipher.decrypt(raw[i:i + AES_BLOCK_SIZE]) for i in range(0, len(raw), AES_BLOCK_SIZE)])
+ else:
+ cipher = cipherAlgo.module(key, CBC, iv)
+ cleartxt = cipher.decrypt(raw)
+ return cleartxt
+
+
+def DPAPIHmac(hashAlgo, pwdhash, hmacSalt, value):
+ """
+ Internal function used to compute HMACs of DPAPI structures
+ """
+ hname = {"HMAC": "sha1"}.get(hashAlgo.name, hashAlgo.name)
+ encKey = hmac.new(pwdhash, digestmod=lambda: hashlib.new(hname))
+ encKey.update(hmacSalt)
+ encKey = encKey.digest()
+ rv = hmac.new(encKey, digestmod=lambda: hashlib.new(hname))
+ rv.update(value)
+ return rv.digest()