summaryrefslogtreecommitdiffstats
path: root/decode-qr-uri.py
diff options
context:
space:
mode:
Diffstat (limited to 'decode-qr-uri.py')
-rwxr-xr-xdecode-qr-uri.py107
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"))