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

# RSA-AES Hybrid

> Complete guide for RSA-2048 + AES-256-CBC hybrid encryption implementation in PFM APIs

## Overview

RSA-AES Hybrid encryption combines RSA-2048 for secure key exchange and AES-256-CBC for high-performance data encryption, providing both the security of asymmetric encryption and the efficiency of symmetric encryption.

### Key Features

* **RSA-2048** for encrypting session keys
* **AES-256-CBC** with 256-bit session keys for data encryption
* **SHA256withRSA** digital signatures for integrity verification
* **Per-request session keys** for forward secrecy

## How It Works

RSA-AES Hybrid encryption uses a two-layer approach — asymmetric encryption for securely exchanging a temporary key, and symmetric encryption for fast bulk data encryption. Below is a step-by-step breakdown of the entire process:

### 1. Key Exchange (One-Time Setup)

Both parties generate their own RSA-2048 key pairs and exchange **only their public keys** with each other. Private keys are never shared.

* An RSA-2048 key pair is generated on the **server side**, and the server's **public key** is shared with the client.
* An RSA-2048 key pair is generated on the **client side**, and the client's **public key** is shared with the server.

This one-time setup establishes the trust foundation for all subsequent encrypted communication.

### 2. Sending an Encrypted Request (Client → Server)

When an API request is sent:

1. **A random session key is generated** — A fresh 32-byte AES key is created for each request, ensuring every request uses a unique encryption key.
2. **The request data is encrypted** — The JSON payload is encrypted using AES-256-CBC with the session key. The first 16 bytes of the session key serve as the IV.
3. **The session key is encrypted** — The session key itself is encrypted using the **server's public key** with RSA. Only the server can decrypt it using its private key.
4. **The encrypted data is signed** — The encrypted data is signed using the **client's private key**. This proves the request originated from the client and has not been tampered with.
5. **The payload is sent** — The encrypted session key, encrypted data, and signature are combined into a single JSON payload and transmitted to the server.

### 3. Receiving an Encrypted Response (Server → Client)

When a response is sent back:

1. **The response is encrypted** by the server using an AES session key, and the session key is encrypted with the **client's public key** — ensuring only the client can decrypt it.
2. **The encrypted data is signed** with the **server's private key** — allowing the client to verify its authenticity.
3. **The session key is decrypted** by the client using the **client's private key**.
4. **The signature is verified** using the **server's public key** to confirm authenticity.
5. **The response data is decrypted** using the decrypted session key.

This ensures that every message is **confidential** (only the intended recipient can read it), **authenticated** (signed by the sender), and **integrity-protected** (any tampering invalidates the signature).

## Key Setup

### Step 1: Generate Client RSA Key Pair

An RSA-2048 key pair must be generated on the client side. The client's **public key** is to be shared with the server. The client's **private key** must be kept secret and securely stored.

<CodeGroup>
  ```java Java theme={null}
  import java.security.KeyPair;
  import java.security.KeyPairGenerator;
  import java.security.SecureRandom;
  import java.util.Base64;

  public class GenerateKeys {
      public static void main(String[] args) throws Exception {
          KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
          keyGen.initialize(2048, SecureRandom.getInstanceStrong());
          KeyPair keyPair = keyGen.generateKeyPair();

          String publicKey = Base64.getEncoder().encodeToString(
              keyPair.getPublic().getEncoded()
          );
          String privateKey = Base64.getEncoder().encodeToString(
              keyPair.getPrivate().getEncoded()
          );

          // Share publicKey with the server
          System.out.println("Client Public Key (share with server): " + publicKey);
          // Keep privateKey secret
          System.out.println("Client Private Key (keep secret): " + privateKey);
      }
  }
  ```

  ```python Python theme={null}
  from cryptography.hazmat.primitives.asymmetric import rsa
  from cryptography.hazmat.primitives import serialization
  import base64

  # Generate RSA-2048 key pair
  private_key = rsa.generate_private_key(
      public_exponent=65537,
      key_size=2048
  )
  public_key = private_key.public_key()

  # Export public key (share with server)
  public_key_bytes = public_key.public_bytes(
      encoding=serialization.Encoding.DER,
      format=serialization.PublicFormat.SubjectPublicKeyInfo
  )
  public_key_b64 = base64.b64encode(public_key_bytes).decode('ascii')

  # Export private key (keep secret)
  private_key_bytes = private_key.private_bytes(
      encoding=serialization.Encoding.DER,
      format=serialization.PrivateFormat.PKCS8,
      encryption_algorithm=serialization.NoEncryption()
  )
  private_key_b64 = base64.b64encode(private_key_bytes).decode('ascii')

  print("Client Public Key (share with server):", public_key_b64)
  print("Client Private Key (keep secret):", private_key_b64)
  ```

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

  // Generate RSA-2048 key pair
  const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
      modulusLength: 2048,
      publicKeyEncoding: {
          type: 'spki',
          format: 'der'
      },
      privateKeyEncoding: {
          type: 'pkcs8',
          format: 'der'
      }
  });

  // Export as Base64
  const publicKeyB64 = publicKey.toString('base64');
  const privateKeyB64 = privateKey.toString('base64');

  // Share publicKeyB64 with the server
  console.log('Client Public Key (share with server):', publicKeyB64);
  // Keep privateKeyB64 secret
  console.log('Client Private Key (keep secret):', privateKeyB64);
  ```
</CodeGroup>

### Step 2: Exchange Public Keys

| Action                 | Description                                                                                                |
| ---------------------- | ---------------------------------------------------------------------------------------------------------- |
| **Provided by server** | Server's RSA public key — used by the client to encrypt session keys and verify server signatures          |
| **Provided by client** | Client's RSA public key — used by the server to encrypt response session keys and verify client signatures |

<Warning>
  Private keys must never be shared with anyone. Only public keys are exchanged between parties.
</Warning>

## Algorithms & Parameters

| Component               | Detail                                    |
| ----------------------- | ----------------------------------------- |
| **RSA Key Size**        | 2048 bits                                 |
| **RSA Transformation**  | RSA/ECB/PKCS1Padding                      |
| **RSA Purpose**         | Encrypting/decrypting the AES session key |
| **AES Key Size**        | 256 bits (32 bytes)                       |
| **AES Transformation**  | AES/CBC/PKCS5Padding                      |
| **AES IV**              | First 16 bytes of the session key         |
| **AES Purpose**         | Encrypting/decrypting the data payload    |
| **Signature Algorithm** | SHA256withRSA                             |
| **Signature Purpose**   | Signing and verifying data integrity      |
| **Base64 Encoding**     | Standard Base64 (not URL-safe)            |

## Encrypted Payload Format

All encrypted requests and responses use the following JSON structure:

### Request Payload

```json theme={null}
{
  "encrypted_session_key": "<Base64 encoded RSA-encrypted session key>",
  "encrypted_data": "<Base64 encoded AES-encrypted data>",
  "signature": "<Base64 encoded digital signature>"
}
```

### Response Payload

```json theme={null}
{
  "encrypted_session_key": "c6+Ma3Crcqr3VGRGseTB9xQIS8fqQxgzUlLpXGEcBO005fKbx+/giMXbvrSPwf7bHHQeTfw6+Iz6TSRUW7Q/W2cqr4SpBvKl6nXqzvaQLYZQ8LPXIFmYuJgjsHUvUqMnbHKTuoxlw1EhCXH/RkutXjTOGUZAOQyqwmnr4iiVxWffI4Agfpc7wKaaobyBLKOYP5shh9YN08qmr9SF1v4ryNs+4fAD9WQIHZKnAf/9wDYf9tCo3wtPFk5tI6sAlZMSBRA0gKGnsv0mHbEh7YJhJghDA2RuN5soVdAe8ZfcgWIbcysJ5xy9YsXxHVwjAH7BBhAyD5XXOhe/yvuFCgzkUA==",
  "encrypted_data": "0lLtSONcFxekoWDKy3d4iKoOnJ70X89T7wA/3XeHG1yvYWGP8Mtnv43eggsnM7NhtuM9pZWUHusaeeqWVx/mvTnC9xGFcqGTXqMesjoSGJTt38hY5LNznNgM6jHz69D/9E9uZ8uyVo30ywiwu8edE2Bb0C1r94JxThSnlcsohROuMx4EH1CaMyxhKfHkGasPj8L/rjqcweeOSzt6ART9RfCWsGggCpB63ajXsvaRBbznAdE4sD0qqN5s2mEzM7E48nemvnmEeuGQYETvnv/rbMxc+huFs4txlRHcM0nZgxt4W2Kf26Iy8+44i5rEUHpSdJeo7CYITKuPiqn44H1opAZrFii+3i4HAA7xGOnrQjMdhl9zuld/NkWx/iSvOpg+Io4y0PySf9lwAFUnzszF8/GDyYsCLI44IkU7hgS/AQQSGxbKcxnQWBh2qxD0ezGKnCoKdnVGUutjFpfzVfRzO0GOWRUB2CMsqEEUUmiNC0+Fr7XTFlQh1WJkXkvpUJFtwV3nQ6rLXDXqLndlRm/ORx9+M77/ESpEgelIvDlq6EP5CTYrHXkTTFtNHtFUc+gvc9nhf4Trp509caQ4gT5VraT5ttsCuAUqso/SzTsfWbgsip+lrYs5UDAtHE7lCVzmIKb8awdhClyOZmqJCdvykTGSe1UUh8krB5jOkXhzXZiNGhE+CT9T/qusjHr4d+zTDIzhyb751vQUr5w7cNwFvq3oz594uFxjVf2KpkmddRN14bKdgNw5ZxLI2pBulHEcOPj7yZzaUtkwDNJXg5d9kLoBwMyiK4kF5rWbx8LPZ/Nuks+2j2iQkvqkwQoSGAxYTjEaQB2p7mJ4JV6qlZFFsw==",
  "signature": "gw5WO2U03M7hyE6+PluUWeo18B7lQCB7hWwwkum4C/ZtQboKwv51kv8KfK9xIQmL6dMKDnQObGHZtfHE0y2IpxlKekms0t6VjJCSagEdDU9NMOBQtaTuWxU4WExjGUbOgqR/nTA63x03/B70NEWx1mfVR5/4Ekr1orIWuDpKeqkzU0xPVbRDCGYU3OZLrRm1NsPS8gy39/0KV8BWRIvjjsKacXFCMg5pj8ORBYCBLnyXnJem025f8p7XKdpz/31SLYh6ilyght+07SMQwYOKLNPYnYCRNaZTzaYiC2vn8+m02NWSKnGfGqTOk/ShR8ShTP0W2sh0Zd3QXIuoGevGYQ=="
}
```

| Field                   | Description                                                                                                                     |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `encrypted_session_key` | The AES session key, encrypted with the client's public key using RSA. Decrypted using the client's private key.                |
| `encrypted_data`        | The response data, encrypted with AES-256-CBC using the session key.                                                            |
| `signature`             | Digital signature of the `encrypted_data` string, signed with the server's private key. Verified using the server's public key. |

## Encryption Flow

```mermaid theme={null}
sequenceDiagram
    participant Client
    participant Server

    Note over Client,Server: Request Flow
    Client->>Client: Generate random AES session key (32 bytes)
    Client->>Client: Encrypt data with AES session key
    Client->>Client: Encrypt session key with server's public key
    Client->>Client: Sign encrypted_data with client's private key
    Client->>Server: Send encrypted payload
    Server->>Server: Decrypt session key with server's private key
    Server->>Server: Verify signature with client's public key
    Server->>Server: Decrypt data with AES session key
    Server->>Server: Process request

    Note over Client,Server: Response Flow
    Server->>Server: Encrypt response with AES session key
    Server->>Server: Encrypt session key with client's public key
    Server->>Server: Sign encrypted_data with server's private key
    Server->>Client: Send encrypted response
    Client->>Client: Decrypt session key with client's private key
    Client->>Client: Verify signature with server's public key
    Client->>Client: Decrypt data with AES session key
```

## Implementation Examples

### Encrypting a Request (Client → Server)

<CodeGroup>
  ```java Java theme={null}
  import javax.crypto.Cipher;
  import javax.crypto.spec.IvParameterSpec;
  import javax.crypto.spec.SecretKeySpec;
  import java.nio.charset.StandardCharsets;
  import java.security.*;
  import java.security.spec.PKCS8EncodedKeySpec;
  import java.security.spec.X509EncodedKeySpec;
  import java.util.Arrays;
  import java.util.Base64;

  public class EncryptRequest {

      private static final String RSA_ALGORITHM = "RSA";
      private static final String RSA_TRANSFORMATION = "RSA/ECB/PKCS1Padding";
      private static final String AES_ALGORITHM = "AES";
      private static final String AES_TRANSFORMATION = "AES/CBC/PKCS5Padding";
      private static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
      private static final int AES_KEY_SIZE = 32; // 256 bits
      private static final int IV_SIZE = 16; // 128 bits

      private static PublicKey loadPublicKey(String base64Key) throws Exception {
          byte[] keyBytes = Base64.getDecoder().decode(base64Key);
          X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
          KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
          return keyFactory.generatePublic(spec);
      }

      private static PrivateKey loadPrivateKey(String base64Key) throws Exception {
          byte[] keyBytes = Base64.getDecoder().decode(base64Key);
          PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
          KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
          return keyFactory.generatePrivate(spec);
      }

      public static void main(String[] args) {
          // Server's public key (provided by server during key exchange)
          String serverPublicKeyBase64 = "<SERVER_PUBLIC_KEY>";
          // Client's private key (generated by client, kept secret)
          String clientPrivateKeyBase64 = "<CLIENT_PRIVATE_KEY>";
          String requestJson = "{\"your\": \"request data\"}";

          try {
              // 1. Generate random AES-256 session key
              SecureRandom secureRandom = new SecureRandom();
              byte[] sessionKey = new byte[AES_KEY_SIZE];
              secureRandom.nextBytes(sessionKey);

              // 2. Extract IV from first 16 bytes
              byte[] iv = Arrays.copyOfRange(sessionKey, 0, IV_SIZE);

              // 3. Encrypt data with AES-256-CBC
              SecretKeySpec aesKeySpec = new SecretKeySpec(sessionKey, AES_ALGORITHM);
              IvParameterSpec ivSpec = new IvParameterSpec(iv);
              Cipher aesCipher = Cipher.getInstance(AES_TRANSFORMATION);
              aesCipher.init(Cipher.ENCRYPT_MODE, aesKeySpec, ivSpec);
              byte[] encryptedDataBytes = aesCipher.doFinal(
                  requestJson.getBytes(StandardCharsets.UTF_8)
              );
              String encryptedData = Base64.getEncoder().encodeToString(encryptedDataBytes);

              // 4. Encrypt session key with server's public key
              PublicKey serverPublicKey = loadPublicKey(serverPublicKeyBase64);
              Cipher rsaCipher = Cipher.getInstance(RSA_TRANSFORMATION);
              rsaCipher.init(Cipher.ENCRYPT_MODE, serverPublicKey);
              byte[] encryptedSessionKey = rsaCipher.doFinal(sessionKey);
              String sessionKeyBase64 = Base64.getEncoder().encodeToString(encryptedSessionKey);

              // 5. Sign encrypted_data with client's private key
              PrivateKey clientPrivateKey = loadPrivateKey(clientPrivateKeyBase64);
              Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
              signature.initSign(clientPrivateKey);
              signature.update(encryptedData.getBytes(StandardCharsets.UTF_8));
              byte[] signatureBytes = signature.sign();
              String signBase64 = Base64.getEncoder().encodeToString(signatureBytes);

              // 6. Build the encrypted payload
              // {
              //   "encrypted_session_key": sessionKeyBase64,
              //   "encrypted_data": encryptedData,
              //   "signature": signBase64
              // }

          } catch (Exception e) {
              System.err.println("Encryption failed: " + e.getMessage());
              e.printStackTrace();
          }
      }
  }
  ```

  ```python Python theme={null}
  import os
  import base64
  from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
  from cryptography.hazmat.primitives.asymmetric import padding as asym_padding
  from cryptography.hazmat.primitives import hashes, serialization
  from cryptography.hazmat.primitives.asymmetric.utils import Prehashed

  def load_public_key(base64_key):
      key_bytes = base64.b64decode(base64_key)
      return serialization.load_der_public_key(key_bytes)

  def load_private_key(base64_key):
      key_bytes = base64.b64decode(base64_key)
      return serialization.load_der_private_key(key_bytes, password=None)

  def encrypt_request(request_json, server_public_key_b64, client_private_key_b64):
      """
      Encrypt a request payload.

      Args:
          request_json (str): JSON string to encrypt
          server_public_key_b64 (str): Server's public key (provided by server)
          client_private_key_b64 (str): Client's private key (generated by client)
      """
      # 1. Generate random AES-256 session key (32 bytes)
      session_key = os.urandom(32)

      # 2. Extract IV from first 16 bytes
      iv = session_key[:16]

      # 3. Encrypt data with AES-256-CBC
      cipher = Cipher(algorithms.AES(session_key), modes.CBC(iv))
      encryptor = cipher.encryptor()
      # PKCS5/PKCS7 padding
      pad_len = 16 - (len(request_json.encode('utf-8')) % 16)
      padded_data = request_json.encode('utf-8') + bytes([pad_len] * pad_len)
      encrypted_data_bytes = encryptor.update(padded_data) + encryptor.finalize()
      encrypted_data = base64.b64encode(encrypted_data_bytes).decode('ascii')

      # 4. Encrypt session key with server's public key
      server_public_key = load_public_key(server_public_key_b64)
      encrypted_session_key = server_public_key.encrypt(
          session_key,
          asym_padding.PKCS1v15()
      )
      session_key_b64 = base64.b64encode(encrypted_session_key).decode('ascii')

      # 5. Sign encrypted_data with client's private key
      client_private_key = load_private_key(client_private_key_b64)
      signature = client_private_key.sign(
          encrypted_data.encode('utf-8'),
          asym_padding.PKCS1v15(),
          hashes.SHA256()
      )
      signature_b64 = base64.b64encode(signature).decode('ascii')

      # 6. Build the encrypted payload
      return {
          "encrypted_session_key": session_key_b64,
          "encrypted_data": encrypted_data,
          "signature": signature_b64
      }
  ```

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

  function loadPublicKey(base64Key) {
      const keyBuffer = Buffer.from(base64Key, 'base64');
      return crypto.createPublicKey({
          key: keyBuffer,
          format: 'der',
          type: 'spki'
      });
  }

  function loadPrivateKey(base64Key) {
      const keyBuffer = Buffer.from(base64Key, 'base64');
      return crypto.createPrivateKey({
          key: keyBuffer,
          format: 'der',
          type: 'pkcs8'
      });
  }

  /**
   * Encrypt a request payload.
   * @param {string} requestJson - JSON string to encrypt
   * @param {string} serverPublicKeyB64 - Server's public key (provided by server)
   * @param {string} clientPrivateKeyB64 - Client's private key (generated by client)
   */
  function encryptRequest(requestJson, serverPublicKeyB64, clientPrivateKeyB64) {
      // 1. Generate random AES-256 session key (32 bytes)
      const sessionKey = crypto.randomBytes(32);

      // 2. Extract IV from first 16 bytes
      const iv = sessionKey.subarray(0, 16);

      // 3. Encrypt data with AES-256-CBC
      const aesCipher = crypto.createCipheriv('aes-256-cbc', sessionKey, iv);
      let encryptedDataBytes = aesCipher.update(requestJson, 'utf8');
      encryptedDataBytes = Buffer.concat([encryptedDataBytes, aesCipher.final()]);
      const encryptedData = encryptedDataBytes.toString('base64');

      // 4. Encrypt session key with server's public key
      const serverPublicKey = loadPublicKey(serverPublicKeyB64);
      const encryptedSessionKey = crypto.publicEncrypt(
          { key: serverPublicKey, padding: crypto.constants.RSA_PKCS1_PADDING },
          sessionKey
      );
      const sessionKeyB64 = encryptedSessionKey.toString('base64');

      // 5. Sign encrypted_data with client's private key
      const clientPrivateKey = loadPrivateKey(clientPrivateKeyB64);
      const signer = crypto.createSign('SHA256');
      signer.update(encryptedData, 'utf8');
      const signatureB64 = signer.sign(clientPrivateKey, 'base64');

      // 6. Build the encrypted payload
      return {
          encrypted_session_key: sessionKeyB64,
          encrypted_data: encryptedData,
          signature: signatureB64
      };
  }
  ```
</CodeGroup>

#### Steps Breakdown

1. **Generate Session Key**: 32 random bytes are generated using a secure random generator
2. **Extract IV**: The first 16 bytes of the session key are used as the IV
3. **Encrypt Data**: The request JSON is encrypted with AES/CBC/PKCS5Padding using the session key and IV
4. **Encrypt Session Key**: The session key is encrypted with the **server's public key** using RSA/ECB/PKCS1Padding
5. **Sign**: The `encrypted_data` string (Base64 string, as UTF-8 bytes) is signed with the **client's private key** using SHA256withRSA
6. **Send**: All three fields are combined into the JSON payload

### Decrypting a Response (Server → Client)

<CodeGroup>
  ```java Java theme={null}
  import javax.crypto.Cipher;
  import javax.crypto.spec.IvParameterSpec;
  import javax.crypto.spec.SecretKeySpec;
  import java.nio.charset.StandardCharsets;
  import java.security.*;
  import java.security.spec.PKCS8EncodedKeySpec;
  import java.security.spec.X509EncodedKeySpec;
  import java.util.Arrays;
  import java.util.Base64;

  public class DecryptResponse {

      private static final String RSA_ALGORITHM = "RSA";
      private static final String RSA_TRANSFORMATION = "RSA/ECB/PKCS1Padding";
      private static final String AES_ALGORITHM = "AES";
      private static final String AES_TRANSFORMATION = "AES/CBC/PKCS5Padding";
      private static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
      private static final int IV_SIZE = 16;

      private static PublicKey loadPublicKey(String base64Key) throws Exception {
          byte[] keyBytes = Base64.getDecoder().decode(base64Key);
          X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
          KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
          return keyFactory.generatePublic(spec);
      }

      private static PrivateKey loadPrivateKey(String base64Key) throws Exception {
          byte[] keyBytes = Base64.getDecoder().decode(base64Key);
          PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
          KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
          return keyFactory.generatePrivate(spec);
      }

      public static void main(String[] args) {
          // Server's public key (provided by server during key exchange)
          String serverPublicKeyBase64 = "<SERVER_PUBLIC_KEY>";
          // Client's private key (generated by client, kept secret)
          String clientPrivateKeyBase64 = "<CLIENT_PRIVATE_KEY>";

          // Response fields received from server
          String encryptedSessionKey = "<encrypted_session_key from response>";
          String encryptedData = "<encrypted_data from response>";
          String signatureStr = "<signature from response>";

          try {
              // 1. Decrypt session key with client's private key
              PrivateKey clientPrivateKey = loadPrivateKey(clientPrivateKeyBase64);
              Cipher rsaCipher = Cipher.getInstance(RSA_TRANSFORMATION);
              rsaCipher.init(Cipher.DECRYPT_MODE, clientPrivateKey);
              byte[] sessionKey = rsaCipher.doFinal(
                  Base64.getDecoder().decode(encryptedSessionKey)
              );

              // 2. Verify signature with server's public key
              PublicKey serverPublicKey = loadPublicKey(serverPublicKeyBase64);
              Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
              signature.initVerify(serverPublicKey);
              signature.update(encryptedData.getBytes(StandardCharsets.UTF_8));
              boolean isValid = signature.verify(
                  Base64.getDecoder().decode(signatureStr)
              );

              if (!isValid) {
                  throw new SecurityException("Signature verification failed!");
              }

              // 3. Extract IV from session key
              byte[] iv = Arrays.copyOfRange(sessionKey, 0, IV_SIZE);

              // 4. Decrypt data with AES-256-CBC
              SecretKeySpec aesKeySpec = new SecretKeySpec(sessionKey, AES_ALGORITHM);
              IvParameterSpec ivSpec = new IvParameterSpec(iv);
              Cipher aesCipher = Cipher.getInstance(AES_TRANSFORMATION);
              aesCipher.init(Cipher.DECRYPT_MODE, aesKeySpec, ivSpec);
              byte[] decryptedBytes = aesCipher.doFinal(
                  Base64.getDecoder().decode(encryptedData)
              );
              String decryptedJson = new String(decryptedBytes, StandardCharsets.UTF_8);

              System.out.println(decryptedJson);

          } catch (Exception e) {
              System.err.println("Decryption failed: " + e.getMessage());
              e.printStackTrace();
          }
      }
  }
  ```

  ```python Python theme={null}
  import base64
  from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
  from cryptography.hazmat.primitives.asymmetric import padding as asym_padding
  from cryptography.hazmat.primitives import hashes, serialization

  def load_public_key(base64_key):
      key_bytes = base64.b64decode(base64_key)
      return serialization.load_der_public_key(key_bytes)

  def load_private_key(base64_key):
      key_bytes = base64.b64decode(base64_key)
      return serialization.load_der_private_key(key_bytes, password=None)

  def decrypt_response(encrypted_session_key_b64, encrypted_data_b64,
                       signature_b64, server_public_key_b64, client_private_key_b64):
      """
      Decrypt a response payload.

      Args:
          encrypted_session_key_b64 (str): Encrypted session key from response
          encrypted_data_b64 (str): Encrypted data from response
          signature_b64 (str): Signature from response
          server_public_key_b64 (str): Server's public key (provided by server)
          client_private_key_b64 (str): Client's private key (generated by client)
      """
      # 1. Decrypt session key with client's private key
      client_private_key = load_private_key(client_private_key_b64)
      session_key = client_private_key.decrypt(
          base64.b64decode(encrypted_session_key_b64),
          asym_padding.PKCS1v15()
      )

      # 2. Verify signature with server's public key
      server_public_key = load_public_key(server_public_key_b64)
      try:
          server_public_key.verify(
              base64.b64decode(signature_b64),
              encrypted_data_b64.encode('utf-8'),
              asym_padding.PKCS1v15(),
              hashes.SHA256()
          )
      except Exception:
          raise Exception("Signature verification failed!")

      # 3. Extract IV from session key
      iv = session_key[:16]

      # 4. Decrypt data with AES-256-CBC
      cipher = Cipher(algorithms.AES(session_key), modes.CBC(iv))
      decryptor = cipher.decryptor()
      decrypted_padded = decryptor.update(
          base64.b64decode(encrypted_data_b64)
      ) + decryptor.finalize()

      # Remove PKCS5/PKCS7 padding
      pad_len = decrypted_padded[-1]
      decrypted_json = decrypted_padded[:-pad_len].decode('utf-8')

      return decrypted_json
  ```

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

  function loadPublicKey(base64Key) {
      const keyBuffer = Buffer.from(base64Key, 'base64');
      return crypto.createPublicKey({
          key: keyBuffer,
          format: 'der',
          type: 'spki'
      });
  }

  function loadPrivateKey(base64Key) {
      const keyBuffer = Buffer.from(base64Key, 'base64');
      return crypto.createPrivateKey({
          key: keyBuffer,
          format: 'der',
          type: 'pkcs8'
      });
  }

  /**
   * Decrypt a response payload.
   * @param {string} encryptedSessionKeyB64 - Encrypted session key from response
   * @param {string} encryptedDataB64 - Encrypted data from response
   * @param {string} signatureB64 - Signature from response
   * @param {string} serverPublicKeyB64 - Server's public key (provided by server)
   * @param {string} clientPrivateKeyB64 - Client's private key (generated by client)
   */
  function decryptResponse(encryptedSessionKeyB64, encryptedDataB64,
                           signatureB64, serverPublicKeyB64, clientPrivateKeyB64) {
      // 1. Decrypt session key with client's private key
      const clientPrivateKey = loadPrivateKey(clientPrivateKeyB64);
      const sessionKey = crypto.privateDecrypt(
          { key: clientPrivateKey, padding: crypto.constants.RSA_PKCS1_PADDING },
          Buffer.from(encryptedSessionKeyB64, 'base64')
      );

      // 2. Verify signature with server's public key
      const serverPublicKey = loadPublicKey(serverPublicKeyB64);
      const verifier = crypto.createVerify('SHA256');
      verifier.update(encryptedDataB64, 'utf8');
      const isValid = verifier.verify(serverPublicKey, signatureB64, 'base64');

      if (!isValid) {
          throw new Error('Signature verification failed!');
      }

      // 3. Extract IV from session key
      const iv = sessionKey.subarray(0, 16);

      // 4. Decrypt data with AES-256-CBC
      const decipher = crypto.createDecipheriv('aes-256-cbc', sessionKey, iv);
      let decrypted = decipher.update(
          Buffer.from(encryptedDataB64, 'base64')
      );
      decrypted = Buffer.concat([decrypted, decipher.final()]);

      return decrypted.toString('utf8');
  }
  ```
</CodeGroup>

#### Steps Breakdown

1. **Decrypt Session Key**: The `encrypted_session_key` is decrypted with the **client's private key** using RSA/ECB/PKCS1Padding
2. **Verify Signature**: The `signature` is verified against the `encrypted_data` string using the **server's public key** with SHA256withRSA. The response should be rejected if verification fails.
3. **Extract IV**: The first 16 bytes of the decrypted session key are used as the IV
4. **Decrypt Data**: The `encrypted_data` is decrypted with AES/CBC/PKCS5Padding using the session key and IV

## Important Notes

* All Base64 encoding uses **standard Base64** (not URL-safe)
* The signature is computed over the Base64-encoded `encrypted_data` **string**, not over the raw encrypted bytes
* The IV is derived from the session key (first 16 bytes) — it is **not** sent separately in the payload
* The session key is re-encrypted with the client's public key in the response, allowing the client to decrypt it with its private key
* For **POST requests**, the server reuses the same AES session key from the request for encrypting the response
* For **GET requests/callbacks**, a new session key is generated by the server

## Security Best Practices

### Key Management

* Keys should be generated using cryptographically secure random number generators
* Keys must be stored securely and never logged or transmitted in plain text
* A request can be placed with the tech team to rotate keys

### Implementation Guidelines

* The digital signature should always be verified before decrypting the data
* Proper error handling should be implemented to avoid information leakage
* Secure key storage mechanisms must be in place
* Private keys must never be shared or exposed
