Files
basedbank/docs/mibapi/ENCRYPTION.md

181 lines
6.1 KiB
Markdown

# 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": "<function code>",
"xxid": "<session token>",
"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=<value>&data=<url-encoded-ciphertext>`
```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=<base64 of Blowfish(DEFAULT_KEY, {"sfunc":"r","data":{"cmod":"...","appId":"...","routePath":"S40","sodium":"...","xxid":"..."}})>
```
Response (decrypted with DEFAULT_KEY):
```json
{
"success": true,
"smod": "<large decimal integer — server DH public key>",
"nonceGenerator": "<instruction string, e.g. 'M26 C16 C4 C5 M64 ...'>",
"xxid": "<session token>",
"sodium": "<server random hex string>",
"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 |