NewDash Dots are hereMini apps in Dash, with payments built inLearn more

Products · Security

Decrypting & Responding

Server code to decrypt a request from Monime and encrypt your reply, step by step and as a full handler.

Each request holds two base64 fields: encryptedAesKey, the AES key encrypted with your RSA public key, and encryptedExchangeData, the request encrypted with that AES key. Your server decrypts the AES key, decrypts the request, and encrypts its reply with the same AES key. Encryption Model describes the format.

Step 1: Load your private key at startup

Read the private key once when your server starts. The samples expect a PKCS#8 key, which starts with -----BEGIN PRIVATE KEY-----. Key Setup shows how to make one.

const crypto = require("crypto");

// Load at startup. MONIME_RSA_PRIVATE_KEY is the full PEM string.
const PRIVATE_KEY = process.env.MONIME_RSA_PRIVATE_KEY;

Step 2: Decode the two fields

Decode both fields from base64 to bytes.

JSON
{
  "encryptedAesKey": "gqRhH8xT...==",
  "encryptedExchangeData": "aBx92Kz...=="
}
function decodeRequestFields(body) {
  return {
    encryptedKeyBuf:  Buffer.from(body.encryptedAesKey, "base64"),
    encryptedDataBuf: Buffer.from(body.encryptedExchangeData, "base64"),
  };
}

Step 3: Decrypt the AES key

Decrypt encryptedAesKey with your RSA private key, using OAEP with SHA-256. The result is the 16-byte AES key for this request. Keep it: you need it again in step 5.

function unwrapAesKey(encryptedKeyBuf) {
  return crypto.privateDecrypt(
    {
      key:     PRIVATE_KEY,
      padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
      oaepHash: "sha256",
    },
    encryptedKeyBuf,
  );
  // Returns the 16-byte AES key as a Buffer.
}

Step 4: Decrypt the request

Byte 0 of the decoded encryptedExchangeData is the IV length (12). The next 12 bytes are the IV. The rest is the ciphertext, followed by the 16-byte GCM tag.

function decryptPayload(encryptedDataBuf, aesKey) {
  // Blob layout: [1 byte IV length][IV][ciphertext][16 byte auth tag]
  const ivLength   = encryptedDataBuf[0];                                        // 12
  const iv         = encryptedDataBuf.subarray(1, 1 + ivLength);
  const tag        = encryptedDataBuf.subarray(encryptedDataBuf.length - 16);    // last 16 bytes
  const ciphertext = encryptedDataBuf.subarray(1 + ivLength, encryptedDataBuf.length - 16);

  const decipher = crypto.createDecipheriv("aes-128-gcm", aesKey, iv, {
    authTagLength: 16,
  });
  decipher.setAuthTag(tag);

  const decrypted = Buffer.concat([
    decipher.update(ciphertext),
    decipher.final(),
  ]);

  return JSON.parse(decrypted.toString("utf8"));
  // Returns { global, currentPage, flowData, exportedData? }
}

Step 5: Encrypt and return your reply

Build your navigate or stop reply and encrypt it with the same AES key as the request, using a new random 12-byte IV. Return the base64 text as the body with HTTP status 200. Don't wrap it in JSON or add encryptedAesKey.

function encryptResponse(responseObject, aesKey) {
  const iv     = crypto.randomBytes(12);  // new IV for each reply
  const cipher = crypto.createCipheriv("aes-128-gcm", aesKey, iv, {
    authTagLength: 16,
  });

  const ciphertext = Buffer.concat([
    cipher.update(JSON.stringify(responseObject), "utf8"),
    cipher.final(),
  ]);
  const tag = cipher.getAuthTag();  // 16 bytes

  // Blob layout: [1 byte IV length][12 byte IV][ciphertext][16 byte auth tag]
  const blob = Buffer.alloc(1 + iv.length + ciphertext.length + tag.length);
  blob.writeUInt8(iv.length, 0);
  iv.copy(blob, 1);
  ciphertext.copy(blob, 1 + iv.length);
  tag.copy(blob, 1 + iv.length + ciphertext.length);

  return blob.toString("base64");
}

// Example usage:
const response = { action: "navigate", pageId: "confirm", pageData: { fee: "50" } };
// const response = { action: "stop", message: "Transfer complete. Ref: TXN-0042." };
const encrypted = encryptResponse(response, aesKey);
res.setHeader("Content-Type", "text/plain");
return res.send(encrypted);

Full handler

All five steps in one HTTP handler.

The same handler works for exchange actions and dynamic pages. The request and reply formats are the same for both.

Keep reading