Skip to content

Instantly share code, notes, and snippets.

@joncardasis
Created May 3, 2018 20:17
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joncardasis/ca7b267feffa06f7e277f1851ca54650 to your computer and use it in GitHub Desktop.
Save joncardasis/ca7b267feffa06f7e277f1851ca54650 to your computer and use it in GitHub Desktop.
A python script to check if a FileVault password has ever been used on macOS before.
import os, sys
import re
import base64
import hashlib
import plistlib
from binascii import unhexlify
class FVPasswordManager(object):
PL_SALTED_HASH_ARRAY_KEY = 'SALTED-SHA512-PBKDF2'
PL_ENTROPY_KEY = 'entropy'
PL_SALT_KEY = 'salt'
PL_ITERATION_KEY = 'iterations'
DK_LEN = 128 # Length of derived key for sha512
@staticmethod
def _get_user_plist_location(user_shortname):
return '/var/db/dslocal/nodes/Default/users/{}.plist'.format(user_shortname.lower())
@staticmethod
def pwd_was_used_previously(user_shortname, password):
'''
Checks if the provided password has ever been used before by checking local filevault
dscl records. (Records use SALTED-SHA512-PBKDF2 algorithm in macOS 10.8 and higher.)
See /var/db/dslocal/nodes/Default/users/<username>.plist files.
@param user_shortname: A string of the user account
@param password: A plain-text string password to check
@return: True if the filevault password has been set as a filevault password before
@raise Exception: Exception raised if failed to parse an existing dictionary
'''
user_plist = FVPasswordManager._get_user_plist_location(user_shortname)
raw_data = os.popen('sudo defaults read {plist} ShadowHashData 2> /dev/null'.format(plist=user_plist)).read()
plist_datum = re.findall(r'\<([0-9a-f ]+)\>', raw_data)
for plist_data in plist_datum:
cleaned_plist_data = plist_data.replace(' ', '')
plist_str = os.popen('echo "{}" | xxd -r -p | plutil -convert xml1 - -o - 2> /dev/null'.format(cleaned_plist_data)).read()
try:
hash_dict = plistlib.readPlistFromString(plist_str)[FVPasswordManager.PL_SALTED_HASH_ARRAY_KEY]
# Obtain salt and entropy in their base64 encoded formats
entropy_64encoded = hash_dict[FVPasswordManager.PL_ENTROPY_KEY].asBase64(maxlinelength=1000000).strip()
salt_64encoded = hash_dict[FVPasswordManager.PL_SALT_KEY].asBase64(maxlinelength=1000000).strip()
iterations = int(hash_dict[FVPasswordManager.PL_ITERATION_KEY])
# Decode values and represent as hex
entropy_hex = base64.b64decode(entropy_64encoded).encode('hex')
salt_hex = base64.b64decode(salt_64encoded).encode('hex')
# Get shadow hash and compare with the entropy
salt_hex_str = unhexlify(salt_hex) # hashlib needs hex string to be in safe hex representation
shadow_hash = hashlib.pbkdf2_hmac('sha512', password, salt_hex_str, iterations, FVPasswordManager.DK_LEN).encode('hex')
if shadow_hash == entropy_hex:
return True
except KeyError as e:
raise KeyError("No key '{}' found for {}".format(e.args[0], FVPasswordManager))
except Exception as e:
raise e
return False
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment