7.1 KiB
Login Flow
MIB uses a two-phase authentication model:
| Phase | Trigger | Key used |
|---|---|---|
| Device Registration | First time this device+account pair is seen | DEFAULT_KEY → DH session key |
| Regular Login | Every subsequent login (stored key1/key2) |
key1 → DH session key |
Password Hashing (pgf03)
The password is never sent in plaintext. Required by both C41 (registration) and A41 (login).
pgf03 = SHA256( clientSalt + SHA256( userSalt + SHA256( password ) ) )
All SHA-256 values are uppercase hex strings. clientSalt is a fresh random 32-character alphanumeric string each time.
import hashlib
def compute_pgf03(password: str, user_salt: str, client_salt: str) -> str:
h1 = hashlib.sha256(password.encode()).hexdigest().upper()
h2 = hashlib.sha256((user_salt + h1).encode()).hexdigest().upper()
return hashlib.sha256((client_salt + h2).encode()).hexdigest().upper()
Device Registration Flow (first time only)
[0] sfunc=r DEFAULT_KEY → DH exchange → derive session_key_1, get xxid + nonceGenerator
[1] sfunc=n A44 → get userSalt
[2] sfunc=n C41 → submit credentials → returns key1, key2 (persist!)
[3] sfunc=n C42 → verify OTP
[4–8] regular login (below) using the key1/key2 just received
Regular Login Flow
[0] sfunc=i key1 → DH exchange → derive session_key_2, get xxid + nonceGenerator
[1] sfunc=n A44 → get userSalt
[2] sfunc=n A41 → submit credentials → returns otpTypes, email, uuid
[3] sfunc=n P41 → fetch profile image (optional)
[4] sfunc=n A42 → verify OTP → session established
[5] sfunc=n P47 → select profile → returns accountBalance array
Step-by-Step Reference
[0] Initial Key Exchange — sfunc=r
Key: DEFAULT_KEY = 8M3L9SBF1AC4FRE56788M3L9SBF1AC4FRE5678
Request (outer + inner are encrypted together):
{
"sfunc": "r",
"data": {
"cmod": "<G^A mod P as decimal string>",
"appId": "IOS17.2-<15 random alphanumeric chars>",
"routePath": "S40",
"sodium": "<random 20-bit int as string>",
"xxid": "<random 40-bit int as string>"
}
}
Response (decrypted with DEFAULT_KEY):
{
"success": true,
"reasonCode": "201",
"reasonText": "Key generated successfully.",
"smod": "<server DH public key as decimal string>",
"nonceGenerator": "<instruction string>",
"xxid": "<session token — use for all subsequent calls>",
"sodium": "<server random>",
"encMethod": 2
}
After: session_key = derive_session_key(int(smod)). Save xxid and nonceGenerator.
[1] Get Auth Type — sfunc=n, routePath: A44
Key: session key
Request:
{
"sfunc": "n",
"xxid": "<session xxid>",
"data": {
"uname": "<username>",
"nonce": "<computed nonce>",
"appId": "<appId>",
"sodium": "<random>",
"routePath": "A44",
"xxid": "<session xxid>"
}
}
Response:
{
"success": true,
"data": [{ "loginType": "1", "userSalt": "<server salt>" }]
}
Use userSalt in pgf03 computation.
[2a] Device Registration Init — sfunc=n, routePath: C41
First-time only.
Request:
{
"sfunc": "n",
"xxid": "<session xxid>",
"data": {
"uname": "<username>",
"pgf03": "<computed>",
"clientSalt": "<random 32-char string>",
"nonce": "<nonce>",
"appId": "<appId>",
"sodium": "<random>",
"routePath": "C41",
"xxid": "<session xxid>"
}
}
Response:
{
"success": true,
"reasonCode": "201",
"primaryOTPType": "3",
"otpTypes": [2, 3],
"fullName": "<user full name>",
"customerImgHash": "<hash>"
}
[3a] OTP Verification (Registration) — sfunc=n, routePath: C42
First-time only. Receive and persist key1/key2.
Request:
{
"sfunc": "n",
"xxid": "<session xxid>",
"data": {
"otp": "<6-digit OTP>",
"uname": "<username>",
"otpType": "3",
"nonce": "<nonce>",
"appId": "<appId>",
"sodium": "<random>",
"routePath": "C42",
"xxid": "<session xxid>"
}
}
Response:
{
"success": true,
"reasonCode": "101",
"data": [{
"key1": "<store securely — Blowfish key for next sfunc=i>",
"key2": "<store securely — sent plaintext in sfunc=i wrapper>",
"appId": "<appId>",
"encryptionMethod": "2"
}]
}
key1 and key2 are long-lived device credentials. Store them securely on the device.
[0b] Authenticated Key Exchange — sfunc=i
Regular login. key2 is a separate unencrypted outer field.
Key: key1
Form body: key2=<key2>&sfunc=i&data=<encrypted payload>
Encrypted payload:
{
"sfunc": "i",
"key2": "<key2>",
"data": {
"cmod": "<G^A mod P>",
"appId": "<appId>",
"routePath": "S40",
"sodium": "<random 20-bit int>",
"xxid": "<random 40-bit int>"
}
}
Response (decrypted with key1):
{
"success": true,
"smod": "<new server DH public key>",
"nonceGenerator": "<new instruction string>",
"xxid": "<new session token>",
"encMethod": 2
}
After: derive new session key, replace xxid and nonceGenerator.
[2b] Regular Login Init — sfunc=n, routePath: A41
Key: session key
Request:
{
"sfunc": "n",
"xxid": "<session xxid>",
"data": {
"uname": "<username>",
"pgf03": "<computed>",
"clientSalt": "<random 32-char>",
"pmodTime": 0,
"requireBankData": 1,
"nonce": "<nonce>",
"appId": "<appId>",
"sodium": "<random>",
"routePath": "A41",
"xxid": "<session xxid>"
}
}
Response:
{
"success": true,
"reasonCode": "104",
"primaryOTPType": "3",
"otpTypes": [2, 3],
"email": "<masked email>",
"uuid": "<uuid1>",
"uuid2": "<uuid2>"
}
[3b] Get Profile Image — sfunc=n, routePath: P41
Optional. Fetch the user's avatar to display on the OTP screen.
Request:
{
"sfunc": "n",
"xxid": "<session xxid>",
"data": {
"imageHash": "<customerImgHash from C41/A41>",
"nonce": "<nonce>",
"appId": "<appId>",
"sodium": "<random>",
"routePath": "P41",
"xxid": "<session xxid>"
}
}
Response:
{
"success": true,
"reasonCode": "201",
"profileImage": "<base64-encoded JPEG>"
}
[4b] OTP Verification (Login) — sfunc=n, routePath: A42
Request:
{
"sfunc": "n",
"xxid": "<session xxid>",
"data": {
"otp": "<6-digit OTP>",
"uname": "<username>",
"otpType": "3",
"nonce": "<nonce>",
"appId": "<appId>",
"sodium": "<random>",
"routePath": "A42",
"xxid": "<session xxid>"
}
}
After successful A42, the xxid and nonceGenerator from the sfunc=i response become the WebView session cookies. See README for the cookie format.
[5] Select Profile — sfunc=n, routePath: P47
See 03-accounts.md for the full P47 reference.
← Encryption Next → Accounts