Files
thijooree/docs/mibapi/02-login.md
T
shihaam a8cd22cbe1
Auto Tag on Version Change / check-version (push) Failing after 13m32s
update docs
2026-06-13 21:30:12 +05:00

366 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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( SHA256( password ) + userSalt ) )
```
All SHA-256 values are uppercase hex strings. `clientSalt` is a fresh random 32-character alphanumeric string each time. Note the inner concat order is `passwordHash + userSalt` — getting this backwards produces a valid-looking but wrong hash.
```python
import hashlib
def compute_pgf03(password: str, user_salt: str, client_salt: str) -> str:
h1 = hashlib.sha256(password.encode()).hexdigest().upper()
h2 = hashlib.sha256((h1 + user_salt).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
[48] 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):
```json
{
"sfunc": "r",
"data": {
"cmod": "<G^A mod P as decimal string>",
"appId": "IOS17.2-<15 random alphanumeric chars>",
"routePath": "S40",
"sodium": "<random int in [1_000_000, 16_000_000)>",
"xxid": "<random 40-bit int as string>"
}
}
```
**Response** (decrypted with `DEFAULT_KEY`):
```json
{
"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**:
```json
{
"sfunc": "n",
"xxid": "<session xxid>",
"data": {
"uname": "<username>",
"nonce": "<computed nonce>",
"appId": "<appId>",
"sodium": "<random>",
"routePath": "A44",
"xxid": "<session xxid>"
}
}
```
**Response**:
```json
{
"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**:
```json
{
"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**:
```json
{
"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**:
```json
{
"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**:
```json
{
"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**:
```json
{
"sfunc": "i",
"key2": "<key2>",
"data": {
"cmod": "<G^A mod P>",
"appId": "<appId>",
"routePath": "S40",
"sodium": "<random int in [1_000_000, 16_000_000)>",
"xxid": "<random 40-bit int>"
}
}
```
**Response** (decrypted with `key1`):
```json
{
"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**:
```json
{
"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**:
```json
{
"success": true,
"reasonCode": "104",
"primaryOTPType": "3",
"otpTypes": [2, 3],
"email": "<masked email>",
"uuid": "<uuid1>",
"uuid2": "<uuid2>",
"operatingProfiles": [
{
"customerProfileId": "...",
"annexId": "...",
"customerId": "...",
"name": "...",
"cifType": "...",
"profileType": "...",
"color": "...",
"customerImage": "<image hash, may be missing/blank>"
}
],
"profileSelected": false,
"selectedProfileId": "",
"accountBalance": []
}
```
**Single-profile fast-path** — if the account has exactly one operating profile, the server returns `profileSelected: true`, populates `selectedProfileId`, and includes a non-empty `accountBalance` array in the A41 response itself. In that case the [P47](03-accounts.md) call is **skipped** and balances are read from this response (see `MibLoginFlow.kt:150-184`).
> **Field naming**: the image hash field on each `operatingProfiles[]` entry is `customerImage`. The same conceptual value is called `customerImgHash` in the contacts API response — the two endpoints disagree on the field name.
---
### [3b] Get Profile Image — `sfunc=n`, `routePath: P41`
Optional. Fetch the user's avatar to display on the OTP screen.
**Request**:
```json
{
"sfunc": "n",
"xxid": "<session xxid>",
"data": {
"imageHash": "<customerImgHash from C41/A41>",
"nonce": "<nonce>",
"appId": "<appId>",
"sodium": "<random>",
"routePath": "P41",
"xxid": "<session xxid>"
}
}
```
**Response**:
```json
{
"success": true,
"reasonCode": "201",
"profileImage": "<base64-encoded JPEG>"
}
```
---
### [4b] OTP Verification (Login) — `sfunc=n`, `routePath: A42`
**Request**:
```json
{
"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](README.md) for the cookie format.
---
### [5] Select Profile — `sfunc=n`, `routePath: P47`
See [03-accounts.md](03-accounts.md) for the full P47 reference.
---
&nbsp;
---
[← Encryption](01-encryption.md) &nbsp;&nbsp;&nbsp; **Next →** [Accounts](03-accounts.md)