def test_pbkdf(self): ''' Test password based key derivation function ''' expected_key = { 1000: "979d33c5e39bf7fc20ef", 10: "3c28a7f09aa19108a17d", 100: "303155b866685ad279fa" } for key_length in expected_key.keys(): key = binascii.hexlify(pbkdf2("my password", "salt", 10, key_length)) print key, expected_key[key_length] assert key == expected_key[key_length]
def parsePSKCdata(xml , preshared_key_hex=None, password=None, do_checkserial=True, do_feitian=False): ''' This function parses XML data of a PSKC file, (RFC6030) It can read * AES-128-CBC encrypted (preshared_key_bin) data * password based encrypted data * plain text data It returns a dictionary of serial : { hmac_key , counter, .... } ''' TAG_NAME_KEYPACKAGE = "KeyPackage" TAG_TOKEN_ID = "Id" # Feitian Fix if do_feitian: TAG_NAME_KEYPACKAGE = "Device" TAG_TOKEN_ID = "KeyId" do_checkserial = False TOKENS = {} elem_keycontainer = etree.fromstring(xml) ENCRYPTION_KEY_hex = preshared_key_hex if getTagName(elem_keycontainer).lower() != "keycontainer": raise ImportException("No toplevel element KeyContainer") tag = elem_keycontainer.tag match = re.match("^({.*?})Key[Cc]ontainer$", tag) namespace = "" if match: namespace = match.group(1) log.debug("Found namespace %s" % namespace) PSKC_VERSION = elem_keycontainer.get("Version") KEYNAME = None MACKEY_bin = None ENC_ALGO = None ENC_MODE = None PBE_DERIVE_ALGO = None PBE_SALT = None PBE_ITERATION_COUNT = None PBE_KEY_LENGTH = None # check for any encryption method 6.1, 6.2 ### Do the Encryption Key elem_encKey = elem_keycontainer.find(namespace + "EncryptionKey") if elem_encKey: # Check for AES-128-CBC, preshared key (chapter 6.1) enckeyTag = getTagName(list(elem_encKey)[0]) # This will hold the name of the preshared key if "KeyName" == enckeyTag: ENC_MODE = "AES128" KEYNAME = list(elem_encKey)[0].text log.debug("The keyname of preshared encryption is <<%s>>" % KEYNAME) # check for PasswordBasedEncyprion (chapter 6.2) elif "DerivedKey" == enckeyTag: ENC_MODE = "PBE" log.debug("We found PBE.") # Now we check for KeyDerivationMethod elem_keyderivation = list(list(elem_encKey)[0]) for e in elem_keyderivation: if "KeyDerivationMethod" == getTagName(e): deriv_algo = e.get("Algorithm") m = re.search("\#(.*)$", deriv_algo) PBE_DERIVE_ALGO = m.group(1) log.debug("Algorithm of the PBE: %s" % PBE_DERIVE_ALGO) if "pbkdf2" == PBE_DERIVE_ALGO: for p in list(e): if "PBKDF2-params" == getTagName(p): for sp in list(p): spTag = getTagName(sp) if "Salt" == spTag: for salt in list(sp): if "Specified" == getTagName(salt): PBE_SALT = salt.text else: log.warning("Unknown element in element Salt: %s" % getTagName(salt)) elif "IterationCount" == spTag: PBE_ITERATION_COUNT = sp.text elif "KeyLength" == spTag: PBE_KEY_LENGTH = sp.text else: # probably pbkdf1 log.error("We do not support key derivation method %s" % deriv_algo) raise ImportException("We do not support key derivation method %s" % deriv_algo) log.debug("found the salt <<%s>>" % PBE_SALT) if password and len(password) > 5 and len(password) <= 64: log.debug("calculation encryption key from password [%s], salt: [%s] and length: [%s], count: [%s]" % (password, PBE_SALT, PBE_KEY_LENGTH, PBE_ITERATION_COUNT)) ENCRYPTION_KEY_bin = pbkdf2.pbkdf2(password.encode('ascii'), base64.b64decode(PBE_SALT), int(PBE_KEY_LENGTH), int(PBE_ITERATION_COUNT)) ENCRYPTION_KEY_hex = binascii.hexlify(ENCRYPTION_KEY_bin) log.debug("calculated encryption key: %s" % ENCRYPTION_KEY_hex) else: log.error("You must provide a password that is longer than 5 characters and up to 64 characters long.") raise ImportException("You must provide a password that is longer than 5 characters and up to 64 characters long.") ### Do the MAC Key # This will hold the MAC key macmethod = elem_keycontainer.find(namespace + "MACMethod") MAC_Method = getMacMethod(macmethod) elem_mackey = macmethod.find(namespace + "MACKey") # Find the MAC: ENC_ALGO and MAC_bin for e in list(elem_mackey): tag = getTagName(e) if "CipherData" == tag: for c in list(e): cipher_tag = getTagName(c) if "CipherValue" == cipher_tag: cipherValue = c.text.strip() log.debug("Found this MAC Key cipherValue: <<%s>>" % cipherValue) MACKEY_bin = aes_decrypt(cipherValue, ENCRYPTION_KEY_hex) else: log.error("Found unsupported child in CipherData: %s" % cipher_tag) raise ImportException("Found unsupported child in CipherData: %s" % cipher_tag) elif "EncryptionMethod" == tag: ENC_ALGO = getEncMethod(e) else: log.warning("Found unknown tag: %s" % tag) ## End of Encryption Key # There is a keypackage per key # Now we get the list of keypackages elem_KeyPackageList = elem_keycontainer.findall(namespace + TAG_NAME_KEYPACKAGE) if 0 == len(elem_KeyPackageList): raise ImportException("No element %s contained!" % TAG_NAME_KEYPACKAGE) # Now parsing all the keys for elem_package in elem_KeyPackageList: ### Do the keys elem_key = elem_package.find(namespace + "Key") serial = elem_key.get(TAG_TOKEN_ID) log.info("Processing token with serial (Key Id=%s)" % serial) elem_deviceInfo = elem_package.find(namespace + "DeviceInfo") if elem_deviceInfo: # Try to find the real serial number elem_serial = elem_deviceInfo.find(namespace + "SerialNo") serial = elem_serial.text log.info("Processing token with the real SerialNo %s" % serial) algorithm = elem_key.get("Algorithm") if algorithm: # <Key Id="12345678" Algorithm="urn:ietf:params:xml:ns:keyprov:pskc:hotp"> algorithm = algorithm.split(":")[-1] else: # Some draft say, this would be KeyAlgorithm algorithm = elem_key.get("KeyAlgorithm") # <Key KeyAlgorithm="http://www.ietf.org/keyprov/pskc#totp" algorithm = algorithm.split("#")[-1] TOKEN_TYPE = None if algorithm: if 'hotp' == algorithm.lower(): TOKEN_TYPE = "hmac" elif 'totp' == algorithm.lower(): TOKEN_TYPE = "totp" elif 'ocra' == algorithm.lower(): TOKEN_TYPE = "ocra" if do_checkserial and not checkSerial(serial): log.warning("serial %s is not a valid OATH serial" % serial) else: # Now we do the Parameters, which can hold # the number of the digits : # <pskc:AlgorithmParameters> # <Suite>OCRA-1:HOTP-SHA1-6:C-QN08-PSHA1</Suite> # <pskc:ResponseFormat Length="6" Encoding="DECIMAL"/> # </pskc:AlgorithmParameters> KD_otplen = 6 KD_hashlib = None KD_Suite = None elem_algoParam = elem_key.find(namespace + "AlgorithmParameters") for e in list(elem_algoParam): eTag = getTagName(e) log.debug("Evaluating element <<%s>>" % eTag) if "ResponseFormat" == eTag: KD_otplen = int (e.get("Length")) log.debug("Found length = %s" % e.get("Length")) elif "Suite" == eTag: if TOKEN_TYPE == "ocra": KD_Suite = e.text log.debug("Found OCRA Suite = %s" % KD_Suite) else: # This can be HMAC-SHA256 KD_hashlib = e.text if KD_hashlib.lower() == "hmac-sha256": KD_hashlib = "sha256" if KD_hashlib.lower() == "hmac-sha1": KD_hashlib = "sha1" log.debug("Found hashlib = %s" % KD_hashlib) # Now we do all the Key Data: <pskc:Data> elem_keydata = elem_key.find(namespace + "Data") # Parse through the data of the Key KD_hmac_key_b64 = None KD_cipher_b64 = None KD_mac_b64 = None KD_algo = None KD_counter = None KD_TimeInterval = None KD_TimeOffset = None for e in list(elem_keydata): eTag = getTagName(e) log.debug("Evaluating element <<%s>>" % eTag) if "Secret" == eTag: for se in list(e): seTag = getTagName(se) if "EncryptedValue" == seTag: for ev in list(se): evTag = getTagName(ev) if "EncryptionMethod" == evTag: KD_algo = getEncMethod(ev) elif "CipherData" == evTag: for ciph in list(ev): ciphTag = getTagName(ciph) if "CipherValue" == ciphTag: KD_cipher_b64 = ciph.text.strip() elif "PlainValue" == seTag: KD_hmac_key_b64 = se.text.strip() elif "ValueMAC" == seTag: KD_mac_b64 = se.text.strip() elif "Counter" == eTag: for se in list(e): seTag = getTagName(se) if "PlainValue" == seTag: KD_counter = se.text else: log.warning("We do only support PlainValue counters") elif "TimeInterval" == eTag: for se in list(e): seTag = getTagName(se) if "PlainValue" == seTag: KD_TimeInterval = se.text log.debug("Found TimeInterval = %s" % KD_TimeInterval) else: log.warning("We do only support PlainValue for TimeInterval") elif "Time" == eTag: for se in list(e): seTag = getTagName(se) if "PlainValue" == seTag: KD_Time = se.text log.debug("Found Time offset = %s" % KD_Time) else: log.warning("We do only support PlainValue for Time") else: log.warning("Unparsed Tag in Key: %s" % eTag) if KD_algo and KD_hmac_key_b64: log.warning("The key %s contained a secret with PlainValue and EncryptedValue!" % serial) else: if "aes128-cbc" == ENC_ALGO: # # Verifiy the MAC Value # if "hmac-sha1" == MAC_Method: MAC_digest_bin = hmac.new(MACKEY_bin, base64.b64decode(KD_cipher_b64), sha).digest() MAC_digest_b64 = base64.b64encode(MAC_digest_bin) log.debug("AES128-CBC secret cipher: %s" % KD_cipher_b64) log.debug("calculated MAC value : %s" % MAC_digest_b64) log.debug("read MAC value : %s" % KD_mac_b64) # decrypt key HMAC_KEY_bin = aes_decrypt(KD_cipher_b64, ENCRYPTION_KEY_hex, serial) if MAC_digest_b64 == KD_mac_b64: TOKENS[serial] = { 'hmac_key' : binascii.hexlify(HMAC_KEY_bin), 'counter' : KD_counter, 'type' : TOKEN_TYPE, 'timeStep' : KD_TimeInterval, 'otplen' : KD_otplen, 'hashlib' : KD_hashlib, 'ocrasuite' : KD_Suite } else: log.error("The MAC value for %s does not fit. The HMAC secrets could be compromised!" % serial) raise ImportException("The MAC value for %s does not fit. The HMAC secrets could be compromised!" % serial) #TOKENS[serial] = { 'hmac_key' : binascii.hexlify(HMAC_KEY_bin), # 'counter' : KD_counter, 'type' : TOKEN_TYPE, # 'timeStep' : KD_TimeInterval, 'otplen' : KD_otplen, # 'hashlib' : KD_hashlib } else: log.warning("At the moment we only support hmac-sha1. We found %s" % MAC_Method) elif KD_hmac_key_b64: TOKENS[serial] = { 'hmac_key' : binascii.hexlify(base64.b64decode(KD_hmac_key_b64)), 'counter' : KD_counter, 'type' : TOKEN_TYPE, 'timeStep' : KD_TimeInterval, 'otplen' : KD_otplen, 'hashlib' : KD_hashlib, 'ocrasuite' : KD_Suite } else: log.warning("neither a PlainValue nor an EncryptedValue was found for the secret of key %s" % serial) return TOKENS