# MIB Faisanet API — Encryption & Decryption ## Overview All API traffic is encrypted using **Blowfish** in ECB mode with PKCS5 padding. Every request and response body is a single base64-encoded Blowfish ciphertext. There are two keys in play: | Key | Used for | |---|---| | `DEFAULT_KEY` (hardcoded) | The initial key exchange request and response (`sfunc=r`) | | Session key (DH-derived) | Every request and response after the key exchange | --- ## The DEFAULT_KEY ``` 8M3L9SBF1AC4FRE56788M3L9SBF1AC4FRE5678 ``` This key is hardcoded in the app's JavaScript bundle. It is only used for the first call (`sfunc=r`) which establishes a session key via Diffie-Hellman. --- ## Session Key Derivation (Diffie-Hellman) The app uses a custom DH key exchange to derive a per-session Blowfish key. All three DH parameters are hardcoded in the app: ``` G = 2 P = 1563516802667282387226490351799736881442299778484610378722158765594241028592123324764949712696577 A = 2410312426921032588552076022197566074856950548502459942654116941958108831682612228890093858261341614673227141477904012196503648957050582631942730706805009223062734745341073406696246014589361659774041027169249453200378729434170325843778659198143763193776859869524088940195577346119843545301547043747207749969763750084308926339295559968882457872412993810129130294592999947926365264059284647209730384947211681434464714438488520940127459844288859336526896320919633919 ``` `A` is the client's private key. Because it is hardcoded and never rotates, anyone with the APK can derive the session key from a captured `smod`. ### Step-by-step 1. Client computes `cmod = G^A mod P` and sends it in the `sfunc=r` request. 2. Server computes its own keypair and responds with `smod` (its public key). 3. Client computes the shared secret: `shared = smod^A mod P` 4. Client SHA-256 hashes the decimal string of the shared secret (uppercased hex). 5. Client converts that hex string to raw bytes, then base64-encodes it. 6. The result is the Blowfish key for the rest of the session. ```python import hashlib, base64 def derive_session_key(smod: int) -> str: # A_VALUE in app = exponent (shorter), P_VALUE in app = modulus (longer) A = 1563516802667282387226490351799736881442299778484610378722158765594241028592123324764949712696577 P = 2410312426921032588552076022197566074856950548502459942654116941958108831682612228890093858261341614673227141477904012196503648957050582631942730706805009223062734745341073406696246014589361659774041027169249453200378729434170325843778659198143763193776859869524088940195577346119843545301547043747207749969763750084308926339295559968882457872412993810129130294592999947926365264059284647209730384947211681434464714438488520940127459844288859336526896320919633919 shared = pow(smod, A, P) sha256_hex = hashlib.sha256(str(shared).encode()).hexdigest().upper() return base64.b64encode(bytes.fromhex(sha256_hex)).decode() ``` --- ## Encrypting a Request All request payloads follow this JSON structure before encryption: ```json { "sfunc": "", "xxid": "", "data": { ... } } ``` ### Encryption steps 1. `JSON.stringify` the payload. 2. Use the raw UTF-8 bytes of the payload as plaintext. 3. Use the raw UTF-8 bytes of the key string as the Blowfish key. 4. Encrypt: Blowfish / ECB / PKCS5 padding. 5. Base64-encode the ciphertext. 6. URL-encode the base64 string. 7. Send as form field: `sfunc=&data=` ```python import json, base64 from urllib.parse import quote from Crypto.Cipher import Blowfish from Crypto.Util.Padding import pad def encrypt(payload: dict, key: str) -> str: plaintext = json.dumps(payload, separators=(',', ':')).encode('utf-8') key_bytes = key.encode('latin-1') cipher = Blowfish.new(key_bytes, Blowfish.MODE_ECB) ct = cipher.encrypt(pad(plaintext, Blowfish.block_size)) return base64.b64encode(ct).decode() def build_request_body(payload: dict, key: str) -> str: sfunc = payload.get('sfunc', '') encrypted = encrypt(payload, key) return f"sfunc={sfunc}&data={quote(encrypted)}" ``` --- ## Decrypting a Response The response body is a raw base64-encoded Blowfish ciphertext (no form encoding). ### Decryption steps 1. Base64-decode the response body to get the ciphertext bytes. 2. Decrypt with Blowfish / ECB / PKCS5 padding using the appropriate key. 3. Parse the result as JSON. ```python import json, base64 from Crypto.Cipher import Blowfish from Crypto.Util.Padding import unpad def decrypt(ciphertext_b64: str, key: str) -> dict: key_bytes = key.encode('latin-1') ct = base64.b64decode(ciphertext_b64) cipher = Blowfish.new(key_bytes, Blowfish.MODE_ECB) plaintext = unpad(cipher.decrypt(ct), Blowfish.block_size) return json.loads(plaintext.decode('utf-8')) ``` --- ## Full Session Example ### 1. Send key exchange (`sfunc=r`) — use DEFAULT_KEY Request form body: ``` sfunc=r&data= ``` Response (decrypted with DEFAULT_KEY): ```json { "success": true, "smod": "", "nonceGenerator": "", "xxid": "", "sodium": "", "encMethod": 2 } ``` ### 2. Derive the session key from `smod` ```python session_key = derive_session_key(int(response['smod'])) # → a 44-character base64 string, e.g. "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=" # The key is 32 bytes (256-bit SHA-256 output) encoded as base64. ``` ### 3. All subsequent requests — use session key Encrypt with `session_key`, decrypt responses with `session_key`. --- ## Quick reference | What | How | |---|---| | Cipher | Blowfish, ECB mode, PKCS5 padding | | Key for `sfunc=r` | `8M3L9SBF1AC4FRE56788M3L9SBF1AC4FRE5678` | | Key for everything else | `derive_session_key(smod)` | | Request encoding | JSON → encrypt → base64 → URL-encode → form field `data=` | | Response encoding | base64 → decrypt → JSON | | Key input | raw UTF-8 bytes of key string | | Plaintext input | raw UTF-8 bytes of JSON string |