Skip to main content

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:

Layer 1 — RSA (Key Exchange)

  • Algorithm: RSA-OAEP (SHA-1 hash, 2048/4096-bit key)
  • Encrypts: "key:iv" UTF-8 string
  • Sent in: Request Header → kek

Layer 2 — AES (Payload Encryption)

  • Algorithm: AES-256-GCM
  • Key: 32 bytes · IV: 12 bytes
  • Encrypts: JSON request body
  • Sent in: Request Body → RequestEncryptedValue

Step-by-Step Encryption Process

Perform these 8 steps for every API request:
1

Prepare Your JSON Payload

Serialize your request data to a valid JSON string with UTF-8 encoding.
{
  "partyIdentifierType": "MOBILE",
  "partyIdentifierValue": "9876543210",
  "productID": "MP9",
  "accountID": "1234"
}
2

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
3

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
AES-GCM requires a 12-byte IV. AES-CBC uses 16 bytes — using the wrong size will cause decryption failure on the server.
4

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
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.
5

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.
6

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)
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.
7

Base64 Encode the KEK

Input  : Encrypted KEK bytes (from Step 6)
Output : Base64 string → goes in the kek request header
Example: b85X0Yg0aaWRX969R/51SyUkOBiKCVPk5SmFVmhb+eLBgb4e6OH0UE4RxZia...
8

Build and Send the HTTP Request

Headers:
HeaderRequiredValue
Content-TypeYesapplication/json
kekYesBase64-encoded encrypted KEK from Step 7
appIdentifierYesYour registered application identifier
Body:
{
  "RequestEncryptedValue": "<ct_b64 + tag_b64 from Step 4>"
}

Request Format

Complete Example

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

HeaderRequiredDescription
kekYesBase64 string of RSA-encrypted "key:iv" UTF-8 bytes
appIdentifierYesYour application identifier, provided during registration
Content-TypeYesMust be application/json

Body Reference

FieldDescription
RequestEncryptedValuebase64(ciphertext) + base64(auth_tag) concatenated — no IV prefix in body

Response Decryption

The server responds with an encrypted payload in the same format:
{
  "ResponseOfEncryptedValue": "pQ2rS4tU6vW8xY0zB2cD4eF6gH8iJ0kL2mN4oP6qR8sT0uV2wX4yZ6..."
}

Decryption Flow

1

Split the payload

Extract the last 24 characters → base64(auth_tag). Everything before = base64(ciphertext).
2

Base64-decode both parts

ct  = base64decode(ct_b64)
tag = base64decode(tag_b64)
3

Decrypt with AES-256-GCM

Use the same Key and IV from your original request. GCM verifies the auth tag automatically.
4

Parse JSON

The decrypted bytes are the original plaintext JSON response.
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)

Prerequisites

Application Registration

Contact FinProServer administrators to register your application. You will receive:
ItemDescriptionExample
Server Public KeyRSA public key in PEM format — used to encrypt your AES key + IVShared by FinPro support over a secure channel
App IdentifierUnique string identifying your applicationcom.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:
PlatformLibrary
Node.jsBuilt-in crypto module
Pythoncryptography or pycryptodome
Javajavax.crypto
Browser (JS)Web Crypto API (window.crypto.subtle)

Algorithm Summary

DataAlgorithmPurposeTransmitted In
AES Key + IVRSA-OAEP (SHA-1)Secure key exchangeHeader: kek
JSON RequestAES-256-GCMAuthenticated payload encryptionBody: RequestEncryptedValue
JSON ResponseAES-256-GCM (same key/IV)Authenticated payload encryptionBody: 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

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'));
}
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'))

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:
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.
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.
Likely cause: Key is not exactly 32 bytes.Fix: Generate exactly 32 alphanumeric ASCII characters (32 UTF-8 bytes = 256 bits).
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.

Appendix

Key Specifications

ParameterValue
RSA Key Size2048 or 4096 bits
RSA PaddingOAEP with SHA-1
AES AlgorithmAES-256-GCM
AES Key Size256 bits (32 bytes)
AES IV Size96 bits (12 bytes)
GCM Auth Tag Size128 bits (16 bytes) — always 24 chars when base64-encoded
Payload EncodingBase64 (standard, not URL-safe)
JSON EncodingUTF-8

Encrypted Payload Structure

Part of stringContentNotes
All characters except last 24base64(ciphertext)AES-GCM ciphertext, base64-encoded
Last 24 charactersbase64(auth_tag)16-byte GCM auth tag, base64-encoded (always 24 chars)
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.

Request / Response Body Fields

FieldDirectionContent
RequestEncryptedValueClient → Serverbase64(ciphertext) + base64(auth_tag)
ResponseOfEncryptedValueServer → Clientbase64(ciphertext) + base64(auth_tag)