summaryrefslogblamecommitdiffstats
path: root/decode-qr-uri.py
blob: f376e38781f7a194bafab487832e9f4e3fd7b43b (plain) (tree)










































































































                                                                                                                                                                                                                                                                                                                                                                                               
#!/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"))