Files
thijooree/docs/mibapi/LOGIN_FLOW.md

11 KiB

MIB Faisanet — Login Flow

Fully reverse engineered from a captured HAR trace of a first-time device registration followed immediately by a regular login.


Key Corrections

The DH parameter names in the app's source are misleading:

App variable DH role Value
A_VALUE Exponent / client private key 15635168026... (shorter)
P_VALUE Prime modulus 24103124269... (longer)
G_VALUE Generator 2

The session key derivation is:

shared = pow(smod, A_VALUE, P_VALUE)   # NOT pow(smod, P_VALUE, A_VALUE)
sha256_hex = SHA256(str(shared)).upper()
session_key = base64(bytes.fromhex(sha256_hex))

Overview

The full login sequence consists of two phases:

Phase Purpose Key used
Phase 1 — Device registration First time this device+account pair is seen DH session key from sfunc=r
Phase 2 — Regular login Every subsequent login key1/key2 (from phase 1) → second DH → new session key

Full Flow Diagram

Client                                        Server
  |                                              |
  |  [0] sfunc=r  (DEFAULT_KEY)                 |
  |  { cmod, appId, routePath:S40, ... }        |
  |--------------------------------------------->|
  |  { smod, nonceGenerator, xxid, ... }        |
  |<---------------------------------------------|
  |  derive session_key_1 = DH(smod)            |
  |                                              |
  |  [1] sfunc=n  routePath:A44  (session_key_1)|
  |  { uname }                                  |
  |--------------------------------------------->|
  |  { loginType, userSalt }                    |
  |<---------------------------------------------|
  |                                              |
  |  [2] sfunc=n  routePath:C41  (session_key_1)| ← device registration init
  |  { uname, pgf03, clientSalt }               |
  |--------------------------------------------->|
  |  { key1, key2, otpTypes, fullName, ... }    |
  |<---------------------------------------------|
  |                                              |
  |  [3] sfunc=n  routePath:C42  (session_key_1)| ← OTP verify (registration)
  |  { otp, uname, otpType }                    |
  |--------------------------------------------->|
  |  { key1, key2, encryptionMethod:2, ... }    |
  |<---------------------------------------------|
  |  store key1, key2 on device                 |
  |                                              |
  |  [4] sfunc=i  (key1)                        | ← second DH key exchange
  |  { cmod, appId, routePath:S40, key2 }       |
  |--------------------------------------------->|
  |  { smod, nonceGenerator, xxid, ... }        |
  |<---------------------------------------------|
  |  derive session_key_2 = DH(smod)            |
  |                                              |
  |  [5] sfunc=n  routePath:A44  (session_key_2)|
  |  { uname }                                  |
  |--------------------------------------------->|
  |  { loginType, userSalt }                    |
  |<---------------------------------------------|
  |                                              |
  |  [6] sfunc=n  routePath:A41  (session_key_2)| ← regular login init
  |  { uname, pgf03, clientSalt, requireBankData:1 }|
  |--------------------------------------------->|
  |  { primaryOTPType, otpTypes, email, uuid, uuid2, ... }|
  |<---------------------------------------------|
  |                                              |
  |  [7] sfunc=n  routePath:P41  (session_key_2)| ← fetch profile image
  |  { imageHash }                              |
  |--------------------------------------------->|
  |  { profileImage (base64 JPEG) }             |
  |<---------------------------------------------|
  |                                              |
  |  [8] sfunc=n  routePath:A42  (session_key_2)| ← OTP verify (regular login)
  |  { otp, uname, otpType }                    |
  |--------------------------------------------->|
  |  { ... session established ... }            |
  |<---------------------------------------------|

Step-by-Step Reference

[0] Initial Key Exchange — sfunc=r

Key: DEFAULT_KEY = 8M3L9SBF1AC4FRE56788M3L9SBF1AC4FRE5678

Request body (inner data field):

{
  "cmod": "<G^A_VALUE mod P_VALUE as decimal string>",
  "appId": "IOS17.2-<15 random chars>",
  "routePath": "S40",
  "sodium": "<random 20-bit int>",
  "xxid": "<random 40-bit int>"
}

Full request (outer wrapper, encrypted together):

{ "sfunc": "r", "data": { ...above... } }

Response (decrypted with DEFAULT_KEY):

{
  "success": true,
  "reasonCode": "201",
  "reasonText": "Key generated successfully.",
  "smod": "<server DH public key>",
  "nonceGenerator": "<instruction string>",
  "xxid": "<session token — carry for all subsequent calls>",
  "sodium": "<server random>",
  "encMethod": 2
}

After this step:

  • Derive session_key_1 = derive_session_key(smod)
  • Save xxid and nonceGenerator

[1] Get Auth Type — sfunc=n, routePath: A44

Key: session_key_1

Request (encrypted):

{
  "sfunc": "n",
  "xxid": "<session xxid>",
  "data": {
    "uname": "<username>",
    "nonce": "<computed from nonceGenerator>",
    "appId": "<appId>",
    "sodium": "<random 20-bit int>",
    "routePath": "A44",
    "xxid": "<session xxid>"
  }
}

Response:

{
  "success": true,
  "reasonCode": "108",
  "reasonText": "Auth type retrieved!",
  "data": [
    {
      "loginType": "1",
      "userSalt": "<server salt for password hashing>"
    }
  ]
}

[2] Device Registration Init — sfunc=n, routePath: C41

First-time only. Registers this device+account pair.

Key: session_key_1

Request:

{
  "sfunc": "n",
  "xxid": "<session xxid>",
  "data": {
    "uname": "<username>",
    "pgf03": "<salted password hash — see below>",
    "clientSalt": "<random 32-char string>",
    "nonce": "<nonce>",
    "appId": "<appId>",
    "sodium": "<random>",
    "routePath": "C41",
    "xxid": "<session xxid>"
  }
}

Response:

{
  "success": true,
  "reasonCode": "201",
  "reasonText": "Registration Initialization Successfully.",
  "primaryOTPType": "3",
  "roleName": "Consumer Premium",
  "otpTypes": [2, 3],
  "fullName": "<user's full name>",
  "lastLoginTime": "<datetime>",
  "customerImgHash": "<hash>"
}

[3] OTP Verification (Registration) — sfunc=n, routePath: C42

Key: session_key_1

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",
  "reasonText": "registration success",
  "data": [
    {
      "appId": "<appId>",
      "createdDate": "<datetime>",
      "key1": "<device credential 1 — store securely>",
      "key2": "<device credential 2 — store securely>",
      "encryptionMethod": "2",
      "appAgent": "android/1.0"
    }
  ]
}

key1 and key2 are long-lived device credentials. key1 is the Blowfish key for the next sfunc=i call. key2 is sent as plaintext in the outer wrapper of that call.


[4] Authenticated Key Exchange — sfunc=i

Second DH exchange, authenticated with the device credentials.

Key: key1

Request (outer wrapper includes key2):

{
  "sfunc": "i",
  "key2": "<key2 from registration>",
  "data": {
    "cmod": "<G^A_VALUE mod P_VALUE>",
    "appId": "<appId>",
    "routePath": "S40",
    "sodium": "<random 20-bit int>",
    "xxid": "<random 40-bit int>"
  }
}

Response (decrypted with key1):

{
  "success": true,
  "reasonCode": "201",
  "reasonText": "Key generated successfully.",
  "smod": "<new server DH public key>",
  "nonceGenerator": "<new instruction string>",
  "xxid": "<new session token>",
  "encMethod": 2
}

After this step:

  • Derive session_key_2 = derive_session_key(smod)
  • Replace xxid and nonceGenerator with new values

[5] Get Auth Type — sfunc=n, routePath: A44

Same as step [1] but with session_key_2. Fetches userSalt for password hashing.


[6] Regular Login Init — sfunc=n, routePath: A41

Key: session_key_2

Request:

{
  "sfunc": "n",
  "xxid": "<session xxid>",
  "data": {
    "uname": "<username>",
    "pgf03": "<salted password hash>",
    "clientSalt": "<random 32-char string>",
    "pmodTime": 0,
    "requireBankData": 1,
    "nonce": "<nonce>",
    "appId": "<appId>",
    "sodium": "<random>",
    "routePath": "A41",
    "xxid": "<session xxid>"
  }
}

Response:

{
  "success": true,
  "reasonCode": "104",
  "reasonText": "Initialization Successful",
  "primaryOTPType": "3",
  "roleName": "Consumer Premium",
  "otpTypes": [2, 3],
  "email": "<masked email>",
  "uuid": "<uuid1>",
  "uuid2": "<uuid2>",
  "xxid": "<xxid>"
}

[7] Get Profile Image — sfunc=n, routePath: P41

Key: session_key_2

Request:

{
  "sfunc": "n",
  "xxid": "<session xxid>",
  "data": {
    "imageHash": "<customerImgHash from step 2/6>",
    "nonce": "<nonce>",
    "appId": "<appId>",
    "sodium": "<random>",
    "routePath": "P41",
    "xxid": "<session xxid>"
  }
}

Response:

{
  "success": true,
  "reasonCode": "201",
  "reasonText": "Image Found",
  "profileImage": "<base64 JPEG>"
}

[8] OTP Verification (Login) — sfunc=n, routePath: A42

Key: session_key_2

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>"
  }
}

Password Hashing (pgf03)

The password is never sent in plaintext. The scheme prevents replay attacks by mixing in a server-provided salt and a client-generated random salt.

pgf03 = SHA256( clientSalt + SHA256( userSalt + SHA256( password ) ) )

All SHA256 values are uppercase hex strings.

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()
    h3 = hashlib.sha256((client_salt + h2).encode()).hexdigest().upper()
    return h3

Route Paths Summary

routePath sfunc Description
S40 r or i DH key exchange
A44 n Get auth type / userSalt
A41 n Regular login initialization
A42 n OTP verification (regular login)
C41 n Device registration initialization
C42 n OTP verification (registration)
P41 n Get profile image
P40 n Update profile image
P42 n Delete profile image