# Encryption Model (/docs/products/ussd-flows/developers/security/overview)

Monime encrypts each request to your exchange and dynamic URLs, and your server must encrypt its reply. The scheme uses two layers: AES-128-GCM encrypts the data in both directions, and RSA-OAEP delivers the AES key to your server.

A flow with an exchange action or a dynamic page can't be published until it has an RSA public key. [Flow Security](/docs/products/ussd-flows/security) shows where to add it.

## How it works

**Request, from Monime to your server:**

1. Monime makes a new random 16-byte AES key for this request only.
2. Monime encrypts the request JSON with that key, using AES-128-GCM and a random 12-byte IV.
3. Monime encrypts the AES key with your flow's RSA public key, using OAEP with SHA-256.
4. Monime sends both, base64-encoded, in a JSON body.

**Reply, from your server to Monime:**

1. Your server decrypts the AES key with its RSA private key.
2. It decrypts the request with the AES key.
3. It encrypts its reply JSON with the same AES key and a new random 12-byte IV.
4. It returns the result as base64 text. There's no `encryptedAesKey` in the reply, because Monime already has the key.

[Decrypting & Responding](/docs/products/ussd-flows/developers/security/implement) has the code for each step.

## Wire format

**Request body:**

```json
{
  "encryptedAesKey": "<base64: the AES key, encrypted with your RSA public key>",
  "encryptedExchangeData": "<base64: the encrypted request>"
}
```

**Reply body:** the base64 text of the encrypted reply, and nothing else. Don't wrap it in JSON. Use `Content-Type: text/plain`, and leave out line breaks.

**Encrypted data**, in both directions, is laid out like this before base64 encoding:

| Offset        | Length   | Content                         |
| ------------- | -------- | ------------------------------- |
| 0             | 1 byte   | IV length, always `12` (`0x0C`) |
| 1             | 12 bytes | IV                              |
| 13            | varies   | Ciphertext                      |
| last 16 bytes | 16 bytes | GCM authentication tag          |

There's no additional authenticated data (AAD).

## Algorithms

| Step                              | Algorithm   | Settings                                     |
| --------------------------------- | ----------- | -------------------------------------------- |
| Encrypt the AES key               | RSA-OAEP    | SHA-256 hash, SHA-256 for MGF1, 2048-bit key |
| Encrypt the request and the reply | AES-128-GCM | 16-byte key, 12-byte IV, 16-byte tag         |

The RSA key must be exactly 2048 bits. Other sizes are rejected when you save the key.

Both the OAEP hash and the MGF1 hash are SHA-256. The defaults in Java and PHP use SHA-1 for one or both, so decryption fails unless you set them. See [Troubleshooting](/docs/products/ussd-flows/developers/security/troubleshooting).

## Next steps

- [**Key Setup**](/docs/products/ussd-flows/developers/security/key-setup)

  Make an RSA key pair, store it, and rotate it.

- [**Decrypting & Responding**](/docs/products/ussd-flows/developers/security/implement)

  Server code in Node.js, Python, PHP, Java and Go.

- [**Testing**](/docs/products/ussd-flows/developers/security/testing)

  Deploy a reference server in one of five languages and test against it.

- [**Troubleshooting**](/docs/products/ussd-flows/developers/security/troubleshooting)

  Common decryption failures and how to fix them.
