All checks were successful
Auto Tag on Version Change / check-version (push) Successful in 4s
347 lines
7.1 KiB
Markdown
347 lines
7.1 KiB
Markdown
# 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.
|
||
|
||
```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((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):
|
||
```json
|
||
{
|
||
"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`):
|
||
```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 20-bit int>",
|
||
"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>"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### [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.
|
||
|
||
---
|
||
|
||
|
||
|
||
---
|
||
|
||
[← Encryption](01-encryption.md) **Next →** [Accounts](03-accounts.md)
|