81 lines
3.0 KiB
Python
Executable File
81 lines
3.0 KiB
Python
Executable File
#!/usr/bin/env python
|
|
"""
|
|
Faisanet MIB API decryption tool.
|
|
Usage:
|
|
./decrypt.py <encrypted_base64>
|
|
./decrypt.py <encrypted_base64> --key <blowfish_key>
|
|
./decrypt.py <encrypted_base64> --smod <server_dh_public_key>
|
|
"""
|
|
|
|
import sys
|
|
import json
|
|
import base64
|
|
import hashlib
|
|
import argparse
|
|
from urllib.parse import unquote
|
|
|
|
try:
|
|
from Crypto.Cipher import Blowfish
|
|
from Crypto.Util.Padding import unpad
|
|
except ImportError:
|
|
print("Missing dependency. Run: pip install pycryptodome", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
DEFAULT_KEY = '8M3L9SBF1AC4FRE56788M3L9SBF1AC4FRE5678'
|
|
|
|
# Hardcoded DH parameters from app
|
|
# NOTE: the variable names in the app's source are misleading —
|
|
# A_VALUE is the exponent (client private key), the shorter number
|
|
# P_VALUE is the prime modulus, the longer number
|
|
A_VALUE = 1563516802667282387226490351799736881442299778484610378722158765594241028592123324764949712696577
|
|
P_VALUE = 2410312426921032588552076022197566074856950548502459942654116941958108831682612228890093858261341614673227141477904012196503648957050582631942730706805009223062734745341073406696246014589361659774041027169249453200378729434170325843778659198143763193776859869524088940195577346119843545301547043747207749969763750084308926339295559968882457872412993810129130294592999947926365264059284647209730384947211681434464714438488520940127459844288859336526896320919633919
|
|
|
|
|
|
def derive_session_key(smod: int) -> str:
|
|
shared_secret = pow(smod, A_VALUE, P_VALUE)
|
|
sha256_hex = hashlib.sha256(str(shared_secret).encode()).hexdigest().upper()
|
|
return base64.b64encode(bytes.fromhex(sha256_hex)).decode()
|
|
|
|
|
|
def decrypt(ciphertext: str, key: str) -> str:
|
|
# Strip URL encoding if present
|
|
ciphertext = unquote(ciphertext).strip()
|
|
key_bytes = key.encode('latin-1')
|
|
ct_bytes = base64.b64decode(ciphertext)
|
|
cipher = Blowfish.new(key_bytes, Blowfish.MODE_ECB)
|
|
plaintext = unpad(cipher.decrypt(ct_bytes), Blowfish.block_size)
|
|
return plaintext.decode('utf-8')
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description='Decrypt Faisanet MIB API payloads')
|
|
parser.add_argument('ciphertext', help='Base64 (or URL-encoded) encrypted payload')
|
|
parser.add_argument('--key', default=None, help='Blowfish key (default: hardcoded DEFAULT_KEY)')
|
|
parser.add_argument('--smod', default=None, help='Server DH public key (decimal) to derive session key')
|
|
args = parser.parse_args()
|
|
|
|
if args.smod:
|
|
key = derive_session_key(int(args.smod))
|
|
print(f"[derived session key] {key}", file=sys.stderr)
|
|
elif args.key:
|
|
key = args.key
|
|
else:
|
|
key = DEFAULT_KEY
|
|
print(f"[using DEFAULT_KEY]", file=sys.stderr)
|
|
|
|
try:
|
|
plaintext = decrypt(args.ciphertext, key)
|
|
except Exception as e:
|
|
print(f"Decryption failed: {e}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
# Pretty-print if JSON
|
|
try:
|
|
print(json.dumps(json.loads(plaintext), indent=2))
|
|
except Exception:
|
|
print(plaintext)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|