# Encrypted Identity Data

This guide applies only to the Identity package.

Privy encrypts the `my_identity` data with the merchant public key. The
merchant backend decrypts it with the matching private key.

## Key provisioning

Each Identity-package merchant has a unique RSA key pair. Privy gives the
private key one time, at onboarding, with the other merchant credentials.

Store the private key in the merchant backend secret manager. Do not put the
private key in a browser or mobile application.

Privy keeps the matching public key and uses it to encrypt identity data.

## Encryption format

Privy encrypts only the `my_identity` section. It sends `my_contact_data` as
plaintext in the same JWT.

The identity JSON fits in one RSA-4096 block. Privy encrypts it directly with
RSA-4096 and PKCS#1 v1.5 padding. The format does not use hybrid or AES
encryption.

The JWT `data` claim has this form:

```json
{
  "my_contact_data": {
    "email": "user@example.com",
    "phone": "+62..."
  },
  "my_identity": "<base64 RSA-4096 ciphertext>"
}
```

The decrypted `my_identity` JSON has this form. All values are fictitious:

```json
{
  "privy_id": "XXX0000",
  "name": "Example User",
  "identity_number": "0000...",
  "dob": "1990-01-01",
  "email": "user@example.com",
  "phone": "+62...",
  "identity_card": "https://example.test/ktp?token=...",
  "selfie": "https://example.test/selfie?token=..."
}
```

`identity_card` and `selfie` are download URLs. They are not inline images.

## Decrypt with Node.js

Verify the JWT first. Then decrypt `data.my_identity` on the merchant backend:

```js
import forge from "node-forge";

function decryptIdentity(myIdentityB64, rsaPrivateKeyPem) {
  const privateKey = forge.pki.privateKeyFromPem(rsaPrivateKeyPem);
  const plaintext = privateKey.decrypt(
    forge.util.decode64(myIdentityB64),
    "RSAES-PKCS1-V1_5",
  );
  return JSON.parse(plaintext);
}
```

The built-in Node.js `crypto.privateDecrypt` rejects `RSA_PKCS1_PADDING` for
private decryption after the CVE-2023-46809 fix. This behavior applies to
current Node.js 18.19, 20, and 22 releases. The `node-forge` example performs
PKCS#1 v1.5 unpadding.

Other languages provide direct equivalents:

- Go: `rsa.DecryptPKCS1v15`
- PHP: `openssl_private_decrypt`
- Python: `PKCS1v15`

> Verify the JWT signature before decryption. Decrypt identity data only on the
> merchant backend.
