> ## Documentation Index
> Fetch the complete documentation index at: https://developer.moneyone.in/llms.txt
> Use this file to discover all available pages before exploring further.

# Client Encryption

> End-to-end payload encryption for securing API requests and responses

## Overview

FinProServer supports **end-to-end encryption** on API payloads. When enabled, request bodies must be encrypted before sending, and responses are returned encrypted — protecting sensitive financial data in transit.

The encryption scheme uses two layers working together: a **symmetric cipher** (AES-256-GCM) to encrypt the payload efficiently, and an **asymmetric cipher** (RSA-OAEP) to securely exchange the symmetric key with the server. The encrypted key bundle sent in each request is called the **KEK** (Key Encryption Key) — a fresh one is generated per request.

## Encryption Architecture

Two layers work together on every request:

<CardGroup cols={2}>
  <Card title="Layer 1 — RSA (Key Exchange)" icon="key">
    * **Algorithm:** RSA-OAEP (SHA-1 hash, 2048/4096-bit key)
    * **Encrypts:** `"key:iv"` UTF-8 string
    * **Sent in:** Request Header → `kek`
  </Card>

  <Card title="Layer 2 — AES (Payload Encryption)" icon="lock">
    * **Algorithm:** AES-256-GCM
    * **Key:** 32 bytes · **IV:** 12 bytes
    * **Encrypts:** JSON request body
    * **Sent in:** Request Body → `RequestEncryptedValue`
  </Card>
</CardGroup>

## Step-by-Step Encryption Process

Perform these 8 steps for **every API request**:

```mermaid theme={null}
flowchart TD
    A["Your JSON Payload"] --> B["Step 1 · Prepare JSON\n{ field: value, ... }"]
    B --> C["Step 2 · Generate AES Key\n32 random bytes — safe ASCII chars"]
    C --> D["Step 3 · Generate IV\n12 random bytes — safe ASCII chars"]
    D --> E["Step 4 · AES-256-GCM Encrypt payload\nOutput: base64(ciphertext) + base64(auth_tag)"]
    E --> F["Step 5 · Combine Key + IV\n\"key:iv\" plain UTF-8 string"]
    F --> G["Step 6 · RSA-OAEP Encrypt key:iv\nUsing server RSA public key"]
    G --> H["Step 7 · Base64 Encode KEK\n→ goes in kek header"]
    H --> I["Step 8 · Send HTTP Request\nHeaders: kek, appIdentifier\nBody: RequestEncryptedValue"]
```

<Steps>
  <Step title="Prepare Your JSON Payload">
    Serialize your request data to a valid JSON string with UTF-8 encoding.

    ```json theme={null}
    {
      "partyIdentifierType": "MOBILE",
      "partyIdentifierValue": "9876543210",
      "productID": "MP9",
      "accountID": "1234"
    }
    ```
  </Step>

  <Step title="Generate Random AES Key">
    Generate a cryptographically secure random key:

    * **Length:** 32 bytes (256 bits)
    * **Format:** Printable ASCII characters recommended (alphanumeric, no colon)
    * **Generate fresh for every request**

    ```
    Example (32 ASCII chars): hXD9IaNzqlOtfJdZyy0ABaGeIkER3bO9
    ```
  </Step>

  <Step title="Generate Random IV">
    Generate a cryptographically secure random IV:

    * **Length:** 12 bytes (96 bits) — required for AES-GCM
    * **Format:** Printable ASCII characters (alphanumeric, no colon)
    * **Must be unique per request**

    ```
    Example (12 ASCII chars): xAphVOhcX4Av
    ```

    <Warning>AES-GCM requires a 12-byte IV. AES-CBC uses 16 bytes — using the wrong size will cause decryption failure on the server.</Warning>
  </Step>

  <Step title="Encrypt Payload with AES-256-GCM">
    Encrypt your JSON string:

    ```
    Algorithm : AES-256-GCM
    Key       : <generated in Step 2>
    IV        : <generated in Step 3>
    Input     : JSON string (UTF-8)
    Output    : base64(ciphertext) + base64(auth_tag)  — concatenated
    ```

    **Process:**

    1. Encrypt JSON with AES-256-GCM → produces ciphertext + 16-byte auth tag
    2. Base64-encode ciphertext: `ct_b64 = base64(ciphertext)`
    3. Base64-encode auth tag: `tag_b64 = base64(auth_tag)` — always 24 characters
    4. Concatenate: `encrypted_payload = ct_b64 + tag_b64`

    <Warning>
      **Critical:** Do NOT prepend the IV to the payload. The IV is transmitted only via the KEK header — the server does not read the IV from the request body. The last 24 characters of the payload string are always the base64-encoded auth tag.
    </Warning>
  </Step>

  <Step title="Combine Key + IV">
    Create a single plain UTF-8 string with key and IV separated by a colon:

    ```
    Format  : "key:iv"  (plain UTF-8 — no encoding applied)
    Example : hXD9IaNzqlOtfJdZyy0ABaGeIkER3bO9:xAphVOhcX4Av
    ```

    *32 ASCII chars + `:` + 12 ASCII chars = 45 characters total.*
  </Step>

  <Step title="RSA-OAEP Encrypt the Key:IV String (Create KEK)">
    ```
    Algorithm  : RSA-OAEP
    Hash       : SHA-1
    Public Key : <server RSA public key provided to you>
    Input      : "key:iv" as raw UTF-8 bytes
    Output     : Encrypted bytes (KEK)
    ```

    <Warning>
      **Critical:** Do NOT Base64-encode the `key:iv` string before RSA encryption. Pass the raw UTF-8 bytes of `"key:iv"` directly to the RSA encrypt function.
    </Warning>
  </Step>

  <Step title="Base64 Encode the KEK">
    ```
    Input  : Encrypted KEK bytes (from Step 6)
    Output : Base64 string → goes in the kek request header
    ```

    *Example: `b85X0Yg0aaWRX969R/51SyUkOBiKCVPk5SmFVmhb+eLBgb4e6OH0UE4RxZia...`*
  </Step>

  <Step title="Build and Send the HTTP Request">
    **Headers:**

    | Header          | Required | Value                                    |
    | --------------- | -------- | ---------------------------------------- |
    | `Content-Type`  | Yes      | `application/json`                       |
    | `kek`           | Yes      | Base64-encoded encrypted KEK from Step 7 |
    | `appIdentifier` | Yes      | Your registered application identifier   |

    **Body:**

    ```json theme={null}
    {
      "RequestEncryptedValue": "<ct_b64 + tag_b64 from Step 4>"
    }
    ```
  </Step>
</Steps>

## Request Format

### Complete Example

```http theme={null}
POST /v2/requestconsent HTTP/1.1
Host: your-domain.moneyone.in
Content-Type: application/json
kek: mW3nX5oP7qS9tV1wY3zA5bC7dE9fG1hI3jK5lM7nO9pQ1rS3tU5vW7xY9zA1bC...
appIdentifier: com.example.myapp

{
  "RequestEncryptedValue": "jK8sL2mN4oP6qR9tU1vX3wY5zA7bC9dE1fG3hI5jK7lM9nO1pQ3rS..."
}
```

### Header Reference

| Header          | Required | Description                                               |
| --------------- | -------- | --------------------------------------------------------- |
| `kek`           | Yes      | Base64 string of RSA-encrypted `"key:iv"` UTF-8 bytes     |
| `appIdentifier` | Yes      | Your application identifier, provided during registration |
| `Content-Type`  | Yes      | Must be `application/json`                                |

### Body Reference

| Field                   | Description                                                                 |
| ----------------------- | --------------------------------------------------------------------------- |
| `RequestEncryptedValue` | `base64(ciphertext) + base64(auth_tag)` concatenated — no IV prefix in body |

## Response Decryption

The server responds with an encrypted payload in the same format:

```json theme={null}
{
  "ResponseOfEncryptedValue": "pQ2rS4tU6vW8xY0zB2cD4eF6gH8iJ0kL2mN4oP6qR8sT0uV2wX4yZ6..."
}
```

### Decryption Flow

```mermaid theme={null}
flowchart TD
    A["ResponseOfEncryptedValue\n(string from response body)"] --> B["Step 1 · Split the payload\nLast 24 chars = base64(auth_tag)\nRest = base64(ciphertext)"]
    B --> C["Step 2 · Base64-decode both parts\nct = base64decode(ct_b64)\ntag = base64decode(tag_b64)"]
    C --> D["Step 3 · AES-256-GCM Decrypt\nKey + IV: same as used in request\nGCM verifies auth tag automatically"]
    D --> E["Step 4 · Parse JSON\nOriginal plaintext response"]
```

<Steps>
  <Step title="Split the payload">
    Extract the last 24 characters → `base64(auth_tag)`. Everything before = `base64(ciphertext)`.
  </Step>

  <Step title="Base64-decode both parts">
    ```
    ct  = base64decode(ct_b64)
    tag = base64decode(tag_b64)
    ```
  </Step>

  <Step title="Decrypt with AES-256-GCM">
    Use the **same Key and IV** from your original request. GCM verifies the auth tag automatically.
  </Step>

  <Step title="Parse JSON">
    The decrypted bytes are the original plaintext JSON response.
  </Step>
</Steps>

<Note>
  **Key points for response decryption:**

  * Reuse the **same Key and IV** you generated for the request — store them after Steps 2 & 3
  * The response format is identical to the request: `base64(ciphertext) + base64(auth_tag)` — no IV in the payload
  * Auth tag is always the last 24 characters (base64-encoded 16-byte GCM tag)
</Note>

## Prerequisites

### Application Registration

Contact FinProServer administrators to register your application. You will receive:

| Item                  | Description                                                      | Example                                        |
| --------------------- | ---------------------------------------------------------------- | ---------------------------------------------- |
| **Server Public Key** | RSA public key in PEM format — used to encrypt your AES key + IV | Shared by FinPro support over a secure channel |
| **App Identifier**    | Unique string identifying your application                       | `com.example.myapp`                            |

**Client-side capabilities required:**

* Generate cryptographically secure random bytes (for AES key and IV)
* AES-256-GCM encryption and decryption
* RSA-OAEP encryption (asymmetric)
* Base64 encoding and decoding
* HTTP requests with custom headers

**Recommended libraries:**

| Platform     | Library                                 |
| ------------ | --------------------------------------- |
| Node.js      | Built-in `crypto` module                |
| Python       | `cryptography` or `pycryptodome`        |
| Java         | `javax.crypto`                          |
| Browser (JS) | Web Crypto API (`window.crypto.subtle`) |

## Algorithm Summary

| Data          | Algorithm                 | Purpose                          | Transmitted In                   |
| ------------- | ------------------------- | -------------------------------- | -------------------------------- |
| AES Key + IV  | RSA-OAEP (SHA-1)          | Secure key exchange              | Header: `kek`                    |
| JSON Request  | AES-256-GCM               | Authenticated payload encryption | Body: `RequestEncryptedValue`    |
| JSON Response | AES-256-GCM (same key/IV) | Authenticated payload encryption | Body: `ResponseOfEncryptedValue` |

### Encryption Flow Summary

```
Payload Path:
  Original JSON
      → [AES-256-GCM Encrypt]  (random key + IV)
      → ciphertext  +  16-byte auth tag
      → base64(ciphertext) + base64(auth_tag)
      → RequestEncryptedValue  (body)

Key Exchange Path:
  Random Key (32 bytes) + Random IV (12 bytes)
      → Combine as  "key:iv"  plain UTF-8 string
      → [RSA-OAEP SHA-1 Encrypt]  (server public key)
      → Encrypted KEK bytes
      → [Base64 Encode]
      → kek  (header)
```

## Code Examples

<CodeGroup>
  ```javascript Node.js theme={null}
  const crypto = require('crypto');

  // Safe charset: alphanumeric only — no colon, no non-ASCII
  const SAFE_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

  function randomAsciiString(length) {
    const bytes = crypto.randomBytes(length);
    return Array.from(bytes, b => SAFE_CHARS[b % SAFE_CHARS.length]).join('');
  }

  function encryptPayload(jsonPayload, serverPublicKeyPem, appIdentifier) {
    // Step 1: Serialize JSON
    const plaintext = JSON.stringify(jsonPayload);

    // Steps 2 & 3: Generate 32-char AES key and 12-char IV (safe ASCII, no colon)
    const aesKeyStr = randomAsciiString(32);
    const ivStr = randomAsciiString(12);
    const aesKey = Buffer.from(aesKeyStr, 'utf8'); // 32 bytes
    const iv = Buffer.from(ivStr, 'utf8');          // 12 bytes

    // Step 4: AES-256-GCM encrypt
    const cipher = crypto.createCipheriv('aes-256-gcm', aesKey, iv);
    const ciphertext = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
    const authTag = cipher.getAuthTag(); // 16 bytes

    const encryptedPayload = ciphertext.toString('base64') + authTag.toString('base64');

    // Step 5: Combine key:iv as plain UTF-8 string
    const keyIvString = aesKeyStr + ':' + ivStr;

    // Step 6: RSA-OAEP encrypt the raw UTF-8 bytes of key:iv
    const kekBytes = crypto.publicEncrypt(
      { key: serverPublicKeyPem, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING },
      Buffer.from(keyIvString, 'utf8')
    );

    // Step 7: Base64 encode KEK
    const kek = kekBytes.toString('base64');

    return {
      headers: { 'Content-Type': 'application/json', kek, appIdentifier },
      body: { RequestEncryptedValue: encryptedPayload },
      // Store these to decrypt the response:
      _aesKey: aesKey,
      _iv: iv,
    };
  }

  function decryptResponse(responseBody, aesKey, iv) {
    const payload = responseBody.ResponseOfEncryptedValue;
    const tag_b64 = payload.slice(-24);
    const ct_b64 = payload.slice(0, -24);

    const ciphertext = Buffer.from(ct_b64, 'base64');
    const authTag = Buffer.from(tag_b64, 'base64');

    const decipher = crypto.createDecipheriv('aes-256-gcm', aesKey, iv);
    decipher.setAuthTag(authTag);
    const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
    return JSON.parse(plaintext.toString('utf8'));
  }
  ```

  ```python Python theme={null}
  import json
  import base64
  import secrets
  import string
  from cryptography.hazmat.primitives.ciphers.aead import AESGCM
  from cryptography.hazmat.primitives.asymmetric import padding
  from cryptography.hazmat.primitives import hashes, serialization

  # Safe charset: alphanumeric only — no colon, guaranteed ASCII
  SAFE_CHARS = string.ascii_letters + string.digits

  def random_ascii_string(length):
      return ''.join(secrets.choice(SAFE_CHARS) for _ in range(length))

  def encrypt_payload(json_payload, server_public_key_pem, app_identifier):
      plaintext = json.dumps(json_payload).encode('utf-8')

      # Generate 32-char AES key and 12-char IV (safe ASCII, no colon)
      aes_key_str = random_ascii_string(32)
      iv_str = random_ascii_string(12)
      aes_key = aes_key_str.encode('utf-8')  # 32 bytes
      iv = iv_str.encode('utf-8')            # 12 bytes

      # AES-256-GCM encrypt (cryptography lib appends 16-byte tag)
      aesgcm = AESGCM(aes_key)
      ct_with_tag = aesgcm.encrypt(iv, plaintext, None)
      ciphertext = ct_with_tag[:-16]
      auth_tag = ct_with_tag[-16:]

      encrypted_payload = base64.b64encode(ciphertext).decode() + base64.b64encode(auth_tag).decode()

      # Combine key:iv as plain UTF-8 string
      key_iv_string = aes_key_str + ':' + iv_str

      # RSA-OAEP encrypt the raw UTF-8 bytes of key:iv
      public_key = serialization.load_pem_public_key(server_public_key_pem.encode())
      kek_bytes = public_key.encrypt(
          key_iv_string.encode('utf-8'),
          padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA1()), algorithm=hashes.SHA1(), label=None)
      )
      kek = base64.b64encode(kek_bytes).decode()

      return {
          'headers': {'Content-Type': 'application/json', 'kek': kek, 'appIdentifier': app_identifier},
          'body': {'RequestEncryptedValue': encrypted_payload},
          '_aes_key': aes_key,
          '_iv': iv,
      }

  def decrypt_response(response_body, aes_key, iv):
      payload = response_body['ResponseOfEncryptedValue']
      tag_b64 = payload[-24:]
      ct_b64 = payload[:-24]

      ciphertext = base64.b64decode(ct_b64)
      auth_tag = base64.b64decode(tag_b64)

      aesgcm = AESGCM(aes_key)
      plaintext = aesgcm.decrypt(iv, ciphertext + auth_tag, None)
      return json.loads(plaintext.decode('utf-8'))
  ```
</CodeGroup>

## Troubleshooting

**Debug Checklist:**

* Server RSA public key is correct and in PEM format
* Application identifier is registered and matches exactly
* AES key is exactly 32 bytes (256 bits)
* IV is exactly 12 bytes (96 bits) — GCM requires 12-byte IV
* New random Key and IV are generated for every request
* Request payload is valid JSON before encryption
* Encrypted payload format: `base64(ciphertext) + base64(auth_tag)` — NO IV prepended in body
* KEK input is plain UTF-8 `"key:iv"` string — NOT Base64-encoded before RSA
* RSA padding is OAEP with SHA-1 (not PKCS1 v1.5, not SHA-256)
* RSA-encrypted KEK bytes are Base64-encoded before putting in `kek` header
* Both `kek` and `appIdentifier` headers are present on every request
* Request body contains only the `RequestEncryptedValue` field
* Same Key and IV are used to decrypt the response
* Response: last 24 chars = `base64(auth_tag)`; remaining = `base64(ciphertext)`
* Auth tag is passed into GCM decrypt — not stripped from the payload first

**Common Errors:**

<AccordionGroup>
  <Accordion title="oaep decoding error">
    **Likely cause:** Wrong RSA public key, or `key:iv` was Base64-encoded before RSA encryption.

    **Fix:** Use the correct server public key. Pass the raw UTF-8 bytes of `"key:iv"` directly to RSA encrypt — do not Base64-encode first.
  </Accordion>

  <Accordion title="GCM auth tag mismatch">
    **Likely cause:** Wrong key/IV used for decryption, or IV was prepended to the payload body.

    **Fix:** Do not prepend the IV to the payload — it lives only in the `kek` header. Use the exact same key + IV from your original request to decrypt the response.
  </Accordion>

  <Accordion title="AES key invalid size">
    **Likely cause:** Key is not exactly 32 bytes.

    **Fix:** Generate exactly 32 alphanumeric ASCII characters (32 UTF-8 bytes = 256 bits).
  </Accordion>

  <Accordion title="Decryption returns garbage">
    **Likely cause:** Payload parts split incorrectly.

    **Fix:** Ensure last 24 chars → `auth_tag`; all remaining chars → `ciphertext`. Base64-decode each part separately before passing to GCM.
  </Accordion>
</AccordionGroup>

## Appendix

### Key Specifications

| Parameter         | Value                                                     |
| ----------------- | --------------------------------------------------------- |
| RSA Key Size      | 2048 or 4096 bits                                         |
| RSA Padding       | OAEP with SHA-1                                           |
| AES Algorithm     | AES-256-GCM                                               |
| AES Key Size      | 256 bits (32 bytes)                                       |
| AES IV Size       | 96 bits (12 bytes)                                        |
| GCM Auth Tag Size | 128 bits (16 bytes) — always 24 chars when base64-encoded |
| Payload Encoding  | Base64 (standard, not URL-safe)                           |
| JSON Encoding     | UTF-8                                                     |

### Encrypted Payload Structure

| Part of string                | Content              | Notes                                                  |
| ----------------------------- | -------------------- | ------------------------------------------------------ |
| All characters except last 24 | `base64(ciphertext)` | AES-GCM ciphertext, base64-encoded                     |
| Last 24 characters            | `base64(auth_tag)`   | 16-byte GCM auth tag, base64-encoded (always 24 chars) |

<Warning>
  The IV is NOT part of the payload string. It is embedded only in the KEK header and must be stored by the client to decrypt the response.
</Warning>

### Request / Response Body Fields

| Field                      | Direction       | Content                                 |
| -------------------------- | --------------- | --------------------------------------- |
| `RequestEncryptedValue`    | Client → Server | `base64(ciphertext) + base64(auth_tag)` |
| `ResponseOfEncryptedValue` | Server → Client | `base64(ciphertext) + base64(auth_tag)` |
