diff options
Diffstat (limited to 'decode-qr-uri.py')
-rwxr-xr-x | decode-qr-uri.py | 107 |
1 files changed, 107 insertions, 0 deletions
diff --git a/decode-qr-uri.py b/decode-qr-uri.py new file mode 100755 index 0000000..f376e38 --- /dev/null +++ b/decode-qr-uri.py @@ -0,0 +1,107 @@ +#!/bin/env python3 +import hashlib +import hmac +import urllib.parse +from hashlib import pbkdf2_hmac +import base64 +import argparse +import logging +from Crypto.Cipher import AES + +logging.basicConfig(level=logging.WARNING) + +parser = argparse.ArgumentParser(description='Decrypt the encrypted data from an Entrust IdentityGuard QR code') +parser.add_argument('URI', type=str, nargs=1, help='Example: igmobileotp://?action=secactivate&enc=VRUq6IoLWQRCMRITZEHtHUSWJiPwgu%2FN1BFyUHE5kxuHIEYoE3zmNTrAHeeUM5S3gzCnTy%2F%2Bdnbu%2FsjjQW%2BNEISx8C4ra8rLpxOl8E8w4KXHgjeBRgdvSzl%2BbzX5RYRrQlWgK8hsBT4pQYE0eFgW2TmRbzXu1Mu7XjKDcwsJLew32jQC2qyPLP8hljnv2rHwwsMfhQwgJUJYfctwLWWEDUFukEckaZ4O&v=1&mac=mhVL8BWKaishMa5%2B'.replace("%", "%%")) +parser.add_argument('Password', type=str, nargs=1, help='The password given with the QR code. Example: 54998317') + +args = parser.parse_args() + +# Parse URL +o = urllib.parse.urlparse(args.URI[0]) + +# Validate scheme +if o.scheme != 'igmobileotp': + logging.warning("Only the scheme igmobileotp is currently supported") + +logging.info("Scheme: %s", o.scheme) + +# Parse query string +query = urllib.parse.parse_qs(o.query) + +# Validate action +try: + if query['action'][0] != 'secactivate': + logging.warning("Only the secactivate action is currently supported") + logging.info("Action: %s", query['action'][0]) +except: + logging.warning("No action was found in the URI. Are you sure this is from a valid QR code?") + +# Validate some encrypted data actually exists +enc = False +try: + enc = query['enc'][0] +except: + raise Exception('An "enc" parameter is a required part of the URI') + +# Decode the enc parameter from base64 +try: + enc = base64.b64decode(enc, validate=True) +except: + raise Exception('Could not decode base64 from enc paramater') + +# Get the salt from enc +kdfSalt = enc[0:8] + +logging.debug("KDF Salt: 0x%s", kdfSalt.hex()) + +# Run PBKDF2 to obtain our AES key +key = pbkdf2_hmac( + hash_name = 'sha256', + password = args.Password[0].encode('utf-8'), + salt = kdfSalt, + iterations = 1000, + dklen = 64 +) + +logging.debug("KDF Output: 0x%s", key.hex()) + +# Validate whether our key is correct using the provided MAC +# TODO: Fix +''' +hmacKey = key[16:48] +hmacer = hmac.new(hmacKey, digestmod=hashlib.sha256) +hmacer.update(urllib.parse.unquote(o.query).encode("utf-8")) +hmacDigest = hmacer.digest() + +logging.info('HMAC Digest: 0x%s', hmacDigest.hex()) + +try: + mac = query['mac'][0] + if base64.b64decode(mac) != hmacDigest: + logging.warning("Falied to validate HMAC") +except: + logging.warning("No MAC was provided in URI. Cannot verify if key is correct") + +print(query['mac'][0]) +print(o.query.encode('utf-8')) +print(hmacDigest) +print(base64.b64decode(query['mac'][0])) +''' + +# Remove the KDF salt from the encrypted data +encdata = enc[8:] + +# Get our parameters required for decryption +iv = key[48:] +aesKey = key[0:16] + +logging.debug("IV: 0x%s", iv.hex()) +logging.debug("AES Key: 0x%s", aesKey.hex()) + +# custom unpad, as pycrytodome does not support pkcs5 +unpad = lambda s : s[0:-(s[-1])] + +cipher = AES.new(aesKey, mode=AES.MODE_CBC, iv=iv) +decrypted_data = unpad(cipher.decrypt(encdata)) + +print(decrypted_data.decode("utf-8")) |