def print_asn1(self, d, l, rl): """ Used for debug """ type_ = char_to_int(d[0]) length = char_to_int(d[1]) if length & 0x80 > 0: # http://luca.ntop.org/Teaching/Appunti/asn1.html, # nByteLength = length & 0x7f length = char_to_int(d[2]) # Long form. Two to 127 octets. Bit 8 of first octet has value "1" and # bits 7-1 give the number of additional length octets. skip = 1 else: skip = 0 if type_ == 0x30: seq_len = length read_len = 0 while seq_len > 0: len2 = self.print_asn1(d[2 + skip + read_len:], seq_len, rl + 1) seq_len = seq_len - len2 read_len = read_len + len2 return length + 2 elif type_ in (0x6, 0x5, 0x4, 0x2): # OID, OCTETSTRING, NULL, INTEGER return length + 2 elif length == l - 2: self.print_asn1(d[2:], length, rl + 1) return length
def extract_secret_key(self, key_data, global_salt, master_password, entry_salt): if unhexlify('f8000000000000000000000000000001') not in key_data: return None priv_key_entry = key_data[unhexlify( 'f8000000000000000000000000000001')] salt_len = char_to_int(priv_key_entry[1]) name_len = char_to_int(priv_key_entry[2]) priv_key_entry_asn1 = decoder.decode(priv_key_entry[3 + salt_len + name_len:]) data = priv_key_entry[3 + salt_len + name_len:] self.print_asn1(data, len(data), 0) # See https://github.com/philsmd/pswRecovery4Moz/blob/master/pswRecovery4Moz.txt entry_salt = priv_key_entry_asn1[0][0][1][0].asOctets() priv_key_data = priv_key_entry_asn1[0][1].asOctets() priv_key = self.decrypt_3des(global_salt, master_password, entry_salt, priv_key_data) self.print_asn1(priv_key, len(priv_key), 0) priv_key_asn1 = decoder.decode(priv_key) pr_key = priv_key_asn1[0][2].asOctets() self.print_asn1(pr_key, len(pr_key), 0) pr_key_asn1 = decoder.decode(pr_key) # id = pr_key_asn1[0][1] key = long_to_bytes(pr_key_asn1[0][3]) return key
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 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 += "\x00" * hashAlgo.blockSize ipad = "".join(chr(char_to_int(h[i]) ^ 0x36) for i in range(hashAlgo.blockSize)) opad = "".join(chr(char_to_int(h[i]) ^ 0x5c) for i in range(hashAlgo.blockSize)) k = hashlib.new(hashAlgo.name, ipad).digest() + hashlib.new(hashAlgo.name, opad).digest() k = cipherAlgo.do_fixup_key(k) return k
def encrypt_bytes(self, data): assert type(data) == str, 'data must be byte string' assert self._lastChunk64, 'previous chunk not multiple of 64 bytes' lendata = len(data) munged = array('c', '\x00' * lendata) for i in xrange(0, lendata, 64): h = salsa20_wordtobyte(self.ctx, self.rounds, check_rounds=False) self.set_counter((self.get_counter() + 1) % 2 ** 64) # Stopping at 2^70 bytes per nonce is user's responsibility. for j in xrange(min(64, lendata - i)): munged[i + j] = chr(char_to_int(data[i + j]) ^ char_to_int(h[j])) self._lastChunk64 = not lendata % 64 return munged.tostring()
def encrypt_bytes(self, data): assert type(data) == str, 'data must be byte string' assert self._lastChunk64, 'previous chunk not multiple of 64 bytes' lendata = len(data) munged = array('c', '\x00' * lendata) for i in xrange(0, lendata, 64): h = salsa20_wordtobyte(self.ctx, self.rounds, check_rounds=False) self.set_counter((self.get_counter() + 1) % 2**64) # Stopping at 2^70 bytes per nonce is user's responsibility. for j in xrange(min(64, lendata - i)): munged[i + j] = chr(char_to_int(data[i + j]) ^ char_to_int(h[j])) self._lastChunk64 = not lendata % 64 return munged.tostring()
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 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 = "" 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() derived = ''.join([chr(char_to_int(x) ^ char_to_int(y)) for (x, y) in zip(derived, actual)]) buff += derived return buff[:keylen]
def decrypt(self, masterkey, entropy=None, strongPassword=None): """Try to decrypt the blob. Returns True/False :rtype : bool :param masterkey: decrypted masterkey value :param entropy: optional entropy for decrypting the blob :param strongPassword: optional password for decrypting the blob """ for algo in [crypto.CryptSessionKeyXP, crypto.CryptSessionKeyWin7]: try: sessionkey = algo(masterkey, self.salt, self.hashAlgo, entropy=entropy, strongPassword=strongPassword) key = crypto.CryptDeriveKey(sessionkey, self.cipherAlgo, self.hashAlgo) if "AES" in self.cipherAlgo.name: cipher = AESModeOfOperationCBC(key[:self.cipherAlgo.keyLength], iv="\x00" * self.cipherAlgo.ivLength) self.cleartext = b"".join([cipher.decrypt(self.cipherText[i:i + AES_BLOCK_SIZE]) for i in range(0, len(self.cipherText), AES_BLOCK_SIZE)]) else: cipher = self.cipherAlgo.module.new(key, CBC, "\x00" * self.cipherAlgo.ivLength) self.cleartext = cipher.decrypt(self.cipherText) padding = char_to_int(self.cleartext[-1]) if padding <= self.cipherAlgo.blockSize: self.cleartext = self.cleartext[:-padding] # check against provided HMAC self.signComputed = algo(masterkey, self.hmac, self.hashAlgo, entropy=entropy, verifBlob=self.blob) self.decrypted = self.signComputed == self.sign if self.decrypted: return True except Exception: pass self.decrypted = False return self.decrypted
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 += "\x00" * hashAlgo.blockSize ipad = "".join( chr(char_to_int(masterkey[i]) ^ 0x36) for i in range(hashAlgo.blockSize)) opad = "".join( chr(char_to_int(masterkey[i]) ^ 0x5c) for i in range(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 decode_password(self, password, jid): result = '' jid = cycle(jid) for n1 in range(0, len(password), 4): x = int(password[n1:n1 + 4], 16) result += chr(x ^ char_to_int(next(jid))) return result
def text_to_bytes(self, text): byte_list = [] # on Windows, default coding for Chinese is GBK # s = s.decode('gbk').encode('utf-8') for byte in text: byte_list.append(char_to_int(byte)) return byte_list
def print_hex(self, src, length=8): N = 0 result = b'' while src: s, src = src[:length], src[length:] hexa = b' '.join([b"%02X" % char_to_int(x) for x in s]) s = s.translate(self.FILTER) result += b"%04X %-*s %s\n" % (N, length * 3, hexa, s) N += length return result
def print_hex(self, src, length=8): N = 0 result = '' while src: s, src = src[:length], src[length:] hexa = ' '.join(["%02X" % char_to_int(x) for x in s]) s = s.translate(self.FILTER) result += "%04X %-*s %s\n" % (N, length * 3, hexa, s) N += length return result
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 = "" 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() derived = ''.join([ chr(char_to_int(x) ^ char_to_int(y)) for (x, y) in zip(derived, actual) ]) buff += derived return buff[:keylen]
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 += "\x00" * hashAlgo.blockSize ipad = "".join(chr(char_to_int(masterkey[i]) ^ 0x36) for i in range(hashAlgo.blockSize)) opad = "".join(chr(char_to_int(masterkey[i]) ^ 0x5c) for i in range(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 decrypt(self, masterkey, entropy=None, strongPassword=None): """Try to decrypt the blob. Returns True/False :rtype : bool :param masterkey: decrypted masterkey value :param entropy: optional entropy for decrypting the blob :param strongPassword: optional password for decrypting the blob """ for algo in [crypto.CryptSessionKeyXP, crypto.CryptSessionKeyWin7]: try: sessionkey = algo(masterkey, self.salt, self.hashAlgo, entropy=entropy, strongPassword=strongPassword) key = crypto.CryptDeriveKey(sessionkey, self.cipherAlgo, self.hashAlgo) if "AES" in self.cipherAlgo.name: cipher = AESModeOfOperationCBC( key[:int(self.cipherAlgo.keyLength)], iv=b"\x00" * int(self.cipherAlgo.ivLength)) self.cleartext = b"".join([ cipher.decrypt(self.cipherText[i:i + AES_BLOCK_SIZE]) for i in range(0, len(self.cipherText), AES_BLOCK_SIZE) ]) else: cipher = self.cipherAlgo.module( key, CBC, b"\x00" * self.cipherAlgo.ivLength) self.cleartext = cipher.decrypt(self.cipherText) padding = char_to_int(self.cleartext[-1]) if padding <= self.cipherAlgo.blockSize: self.cleartext = self.cleartext[:-padding] # check against provided HMAC self.signComputed = algo(masterkey, self.hmac, self.hashAlgo, entropy=entropy, verifBlob=self.blob) self.decrypted = self.signComputed == self.sign if self.decrypted: return True except Exception: print_debug('DEBUG', traceback.format_exc()) self.decrypted = False return self.decrypted
def extract_secret_key(self, key_data, global_salt, master_password, entry_salt): if unhexlify('f8000000000000000000000000000001') not in key_data: return None priv_key_entry = key_data[unhexlify('f8000000000000000000000000000001')] salt_len = char_to_int(priv_key_entry[1]) name_len = char_to_int(priv_key_entry[2]) priv_key_entry_asn1 = decoder.decode(priv_key_entry[3 + salt_len + name_len:]) data = priv_key_entry[3 + salt_len + name_len:] # self.print_asn1(data, len(data), 0) # See https://github.com/philsmd/pswRecovery4Moz/blob/master/pswRecovery4Moz.txt entry_salt = priv_key_entry_asn1[0][0][1][0].asOctets() priv_key_data = priv_key_entry_asn1[0][1].asOctets() priv_key = self.decrypt_3des(global_salt, master_password, entry_salt, priv_key_data) # self.print_asn1(priv_key, len(priv_key), 0) priv_key_asn1 = decoder.decode(priv_key) pr_key = priv_key_asn1[0][2].asOctets() # self.print_asn1(pr_key, len(pr_key), 0) pr_key_asn1 = decoder.decode(pr_key) # id = pr_key_asn1[0][1] key = long_to_bytes(pr_key_asn1[0][3]) return key
def parse(self, data): self.id = data.eat("L") self.attr_unknown_1 = data.eat("L") self.attr_unknown_2 = data.eat("L") self.attr_unknown_3 = data.eat("L") # self.padding = data.eat("6s") if self.id >= 100: self.attr_unknown_4 = data.eat("L") self.size = data.eat("L") if self.size > 0: self.has_iv = char_to_int(data.eat("1s")) # To change for Python 3 compatibility if self.has_iv == 1: self.iv_size = data.eat("L") self.iv = data.eat(str(self.iv_size)+ "s") self.data = data.eat(str(self.size - 1 - 4 - self.iv_size) + "s") else: self.data = data.eat(str(self.size - 1) + "s")
def is_master_password_correct(self, key_data, master_password='', new_version=True): try: if not new_version: # See http://www.drh-consultancy.demon.co.uk/key3.html pwd_check = key_data.get(b'password-check') if not pwd_check: return '', '', '' entry_salt_len = char_to_int(pwd_check[1]) entry_salt = pwd_check[3:3 + entry_salt_len] encrypted_passwd = pwd_check[-16:] global_salt = key_data[b'global-salt'] else: global_salt = key_data[0] # Item1 item2 = key_data[1] self.print_asn1(item2, len(item2), 0) # SEQUENCE { # SEQUENCE { # OBJECTIDENTIFIER 1.2.840.113549.1.12.5.1.3 # SEQUENCE { # OCTETSTRING entry_salt_for_passwd_check # INTEGER 01 # } # } # OCTETSTRING encrypted_password_check # } decoded_item2 = decoder.decode(item2) entry_salt = decoded_item2[0][0][1][0].asOctets() encrypted_passwd = decoded_item2[0][1].asOctets() cleartext_data = self.decrypt_3des(global_salt, master_password, entry_salt, encrypted_passwd) if cleartext_data != convert_to_byte('password-check\x02\x02'): return '', '', '' return global_salt, master_password, entry_salt except Exception: self.debug(traceback.format_exc()) return '', '', ''
def is_master_password_correct(self, key_data, master_password='', new_version=True): try: if not new_version: # See http://www.drh-consultancy.demon.co.uk/key3.html pwd_check = key_data.get(b'password-check') if not pwd_check: return '', '', '' entry_salt_len = char_to_int(pwd_check[1]) entry_salt = pwd_check[3: 3 + entry_salt_len] encrypted_passwd = pwd_check[-16:] global_salt = key_data[b'global-salt'] else: global_salt = key_data[0] # Item1 item2 = key_data[1] # self.print_asn1(item2, len(item2), 0) # SEQUENCE { # SEQUENCE { # OBJECTIDENTIFIER 1.2.840.113549.1.12.5.1.3 # SEQUENCE { # OCTETSTRING entry_salt_for_passwd_check # INTEGER 01 # } # } # OCTETSTRING encrypted_password_check # } decoded_item2 = decoder.decode(item2) entry_salt = decoded_item2[0][0][1][0].asOctets() encrypted_passwd = decoded_item2[0][1].asOctets() cleartext_data = self.decrypt_3des(global_salt, master_password, entry_salt, encrypted_passwd) if cleartext_data != convert_to_byte('password-check\x02\x02'): return '', '', '' return global_salt, master_password, entry_salt except Exception: self.debug(traceback.format_exc()) return '', '', ''
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 xorstring(self, s, k): """ xors the two strings """ return "".join( chr(char_to_int(x) ^ char_to_int(y)) for x, y in zip(s, k))
def unpad(data): extra = char_to_int(data[-1]) return data[:len(data) - extra]
def xorstring(self, s, k): """ xors the two strings """ return b''.join(chr_or_byte(char_to_int(x) ^ char_to_int(y)) for x, y in zip(s, k))
def str_to_key(s): key = [] key.append(char_to_int(s[0]) >> 1) key.append(((char_to_int(s[0]) & 0x01) << 6) | (char_to_int(s[1]) >> 2)) key.append(((char_to_int(s[1]) & 0x03) << 5) | (char_to_int(s[2]) >> 3)) key.append(((char_to_int(s[2]) & 0x07) << 4) | (char_to_int(s[3]) >> 4)) key.append(((char_to_int(s[3]) & 0x0F) << 3) | (char_to_int(s[4]) >> 5)) key.append(((char_to_int(s[4]) & 0x1F) << 2) | (char_to_int(s[5]) >> 6)) key.append(((char_to_int(s[5]) & 0x3F) << 1) | (char_to_int(s[6]) >> 7)) key.append(char_to_int(s[6]) & 0x7F) for i in range(8): key[i] = (key[i] << 1) key[i] = odd_parity[key[i]] return b"".join(chr_or_byte(k) for k in key)
def xorstring(self, s, k): """ xors the two strings """ return "".join(chr(char_to_int(x) ^ char_to_int(y)) for x, y in zip(s, k))
def str_to_key(s): key = [] key.append(char_to_int(s[0]) >> 1) key.append(((char_to_int(s[0]) & 0x01) << 6) | (char_to_int(s[1]) >> 2)) key.append(((char_to_int(s[1]) & 0x03) << 5) | (char_to_int(s[2]) >> 3)) key.append(((char_to_int(s[2]) & 0x07) << 4) | (char_to_int(s[3]) >> 4)) key.append(((char_to_int(s[3]) & 0x0F) << 3) | (char_to_int(s[4]) >> 5)) key.append(((char_to_int(s[4]) & 0x1F) << 2) | (char_to_int(s[5]) >> 6)) key.append(((char_to_int(s[5]) & 0x3F) << 1) | (char_to_int(s[6]) >> 7)) key.append(char_to_int(s[6]) & 0x7F) for i in range(8): key[i] = (key[i] << 1) key[i] = odd_parity[key[i]] return "".join(chr(k) for k in key)