PrivyPrivy
Sign in
  • Documentation
Back to Documentation
Digital ID
Get Started
Changelog
Concepts
Guides
    Web IntegrationNative IntegrationCamera and PermissionsPrivy ButtonData DeliveryVerify the JWTEncrypted Identity DataServer CallbackSecurityProduction Readiness
API Reference
powered by Zudoku
Sign in
Guides

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:

Code
{ "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:

Code
{ "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:

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

Last modified on October 23, 2026
Verify the JWTServer Callback
On this page
  • Key provisioning
  • Encryption format
  • Decrypt with Node.js
JSON
JSON
Javascript