> For the complete documentation index, see [llms.txt](https://pivot-payment.gitbook.io/pivot-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://pivot-payment.gitbook.io/pivot-docs/api-references/api-lists/payments/card-encryption.md).

# Card Encryption

## Flow

<figure><img src="/files/xZgJ1Rt1QdDx94spWxsf" alt=""><figcaption></figcaption></figure>

## Encryption Code&#x20;

{% tabs %}
{% tab title="Encryption UI" %}
Download and simulate the encryption without installing a programming language

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Card Encryption</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 600px;
            margin: 0 auto;
            padding: 20px;
            background-color: #f5f5f5;
        }

        .container {
            background-color: white;
            padding: 30px;
            border-radius: 10px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }

        h1 {
            color: #333;
            text-align: center;
            margin-bottom: 30px;
        }

        .form-group {
            margin-bottom: 20px;
        }

        label {
            display: block;
            margin-bottom: 5px;
            color: #555;
            font-weight: bold;
        }

        .required {
            color: #d32f2f;
        }

        .radio-group {
            display: flex;
            gap: 20px;
        }

        .input-method-group {
            margin-top: 20px;
            margin-bottom: 20px;
        }

        .radio-inline {
            display: flex;
            align-items: center;
            gap: 6px;
            font-weight: normal;
            color: #555;
            margin-bottom: 0;
            cursor: pointer;
        }

        .token-mode-field {
            display: none;
        }

        /* Token mode: a single class on the form toggles field visibility */
        #cardForm.token-mode .card-mode-field {
            display: none;
        }

        #cardForm.token-mode .token-mode-field {
            display: block;
        }

        input[type="text"],
        input[type="number"],
        textarea {
            width: 100%;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 5px;
            font-size: 14px;
            box-sizing: border-box;
        }

        input[type="text"]:focus,
        input[type="number"]:focus,
        textarea:focus {
            outline: none;
            border-color: #4CAF50;
        }

        .row {
            display: flex;
            gap: 15px;
        }

        .col {
            flex: 1;
        }

        .btn {
            background-color: #4CAF50;
            color: white;
            padding: 12px 30px;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            font-size: 16px;
            width: 100%;
            margin-top: 20px;
        }

        .btn:hover {
            background-color: #45a049;
        }

        .btn:disabled {
            background-color: #ccc;
            cursor: not-allowed;
        }

        .result {
            margin-top: 20px;
            padding: 15px;
            background-color: #f9f9f9;
            border-radius: 5px;
            border: 1px solid #ddd;
        }

        .error {
            color: #d32f2f;
            background-color: #ffebee;
            border-color: #f8bbd9;
        }

        .success {
            color: #388e3c;
            background-color: #e8f5e8;
            border-color: #c8e6c9;
        }

        .public-key-section {
            margin-bottom: 30px;
            padding: 20px;
            background-color: #f8f9fa;
            border-radius: 5px;
        }

        .card-form {
            border: 1px solid #ddd;
            padding: 20px;
            border-radius: 5px;
            background-color: #fafafa;
        }

        .card-form h3 {
            margin: 0;
        }

        .loading {
            display: none;
            text-align: center;
            margin-top: 10px;
        }

        .spinner {
            border: 4px solid #f3f3f3;
            border-top: 4px solid #3498db;
            border-radius: 50%;
            width: 30px;
            height: 30px;
            animation: spin 1s linear infinite;
            margin: 0 auto;
        }

        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>Card Encryption</h1>
        
        <!-- Public Key Input Section -->
        <div class="public-key-section">
            <div class="form-group">
                <label for="publicKey">RSA Public Key (Base64 PKIX format):</label>
                <textarea id="publicKey" rows="8" placeholder="Enter your Base64-encoded RSA public key here..."></textarea>
            </div>
        </div>

        <!-- Card Details Form -->
        <div class="card-form">
            <h3>Card Holder Details</h3>
            <form id="cardForm">
                <div class="form-group input-method-group">
                    <label>Input Method:</label>
                    <div class="radio-group">
                        <label class="radio-inline">
                            <input type="radio" name="inputMethod" value="card" checked> Card Details
                        </label>
                        <label class="radio-inline">
                            <input type="radio" name="inputMethod" value="token"> Network Token
                        </label>
                    </div>
                </div>

                <div class="form-group card-mode-field">
                    <label for="cardNumber">Card Number <span class="required">*</span>:</label>
                    <input type="text" id="cardNumber" placeholder="1234 5678 9012 3456" maxlength="19" inputmode="numeric" autocomplete="off">
                </div>

                <div class="form-group token-mode-field">
                    <label for="networkTokenNumber">Network Token Number <span class="required">*</span>:</label>
                    <input type="text" id="networkTokenNumber" placeholder="1234 5678 9012 3456" maxlength="19" inputmode="numeric" autocomplete="off">
                </div>

                <div class="form-group token-mode-field">
                    <label for="networkTokenCryptogram">Network Token Cryptogram:</label>
                    <input type="text" id="networkTokenCryptogram" placeholder="Enter token cryptogram" autocomplete="off">
                </div>

                <div class="form-group token-mode-field">
                    <label for="networkTokenRequestorId">Network Token Requestor ID:</label>
                    <input type="text" id="networkTokenRequestorId" placeholder="Enter token requestor ID">
                </div>

                <div class="form-group">
                    <label for="cardholderName">Cardholder Name <span class="required">*</span>:</label>
                    <input type="text" id="cardholderName" placeholder="John Doe" autocomplete="off">
                </div>

                <div class="row">
                    <div class="col">
                        <div class="form-group">
                            <label for="expiryMonth">Expiry Month <span class="required">*</span>:</label>
                            <input type="number" id="expiryMonth" min="1" max="12" placeholder="MM">
                        </div>
                    </div>
                    <div class="col">
                        <div class="form-group">
                            <label for="expiryYear">Expiry Year <span class="required">*</span>:</label>
                            <input type="number" id="expiryYear" min="01" max="9999" placeholder="YY">
                        </div>
                    </div>
                </div>

                <div class="form-group card-mode-field">
                    <label for="cvv">CVV:</label>
                    <input type="text" id="cvv" placeholder="123" maxlength="4" inputmode="numeric" autocomplete="off">
                </div>

                <button type="submit" class="btn" id="encryptBtn">Encrypt Card Details</button>
            </form>
        </div>

        <!-- Loading Indicator -->
        <div class="loading" id="loadingIndicator">
            <div class="spinner"></div>
            <p>Encrypting...</p>
        </div>

        <!-- Result Display -->
        <div id="result" class="result" style="display: none;"></div>
    </div>

    <script>
        // Hybrid Encryption Class
        class HybridEncryption {
            /**
             * Encrypts plaintext using hybrid encryption (RSA-OAEP + AES-GCM)
             * @param {string} plaintext - The text to encrypt
             * @param {string} base64PublicKey - Base64-encoded PKIX/SubjectPublicKeyInfo public key
             * @returns {Promise<string>} Base64-encoded encrypted response
             */
            static async encryptHybrid(plaintext, base64PublicKey) {
                try {
                    // Decode the base64 public key
                    const publicKeyBytes = this.base64ToArrayBuffer(base64PublicKey);

                    // Import the public key
                    const publicKey = await crypto.subtle.importKey(
                        'spki', // PKIX/SubjectPublicKeyInfo format
                        publicKeyBytes,
                        { name: 'RSA-OAEP',hash: 'SHA-256' },
                        false,
                        ['encrypt']
                    );

                    // Generate 32-byte AES key
                    const aesKey = await crypto.subtle.generateKey(
                        { name: 'AES-GCM', length: 256 }, true, ['encrypt']
                    );

                    // Generate 12-byte nonce for AES-GCM
                    const nonce = crypto.getRandomValues(new Uint8Array(12));

                    // Encrypt plaintext with AES-GCM
                    const plaintextBuffer = new TextEncoder().encode(plaintext);
                    const ciphertext = await crypto.subtle.encrypt(
                        { name: 'AES-GCM', iv: nonce }, aesKey, plaintextBuffer
                    );

                    // Export AES key as raw bytes
                    const aesKeyBytes = await crypto.subtle.exportKey('raw', aesKey);

                    // Encrypt AES key with RSA-OAEP
                    const encryptedKey = await crypto.subtle.encrypt(
                        { name: 'RSA-OAEP' }, publicKey, aesKeyBytes
                    );

                    // Create response payload
                    const payload = {
                        encryptedKey: this.arrayBufferToBase64(encryptedKey),
                        nonce: this.arrayBufferToBase64(nonce),
                        ciphertext: this.arrayBufferToBase64(ciphertext)
                    };

                    // Convert to JSON and encode as base64
                    const jsonString = JSON.stringify(payload);
                    const jsonBuffer = new TextEncoder().encode(jsonString);

                    return this.arrayBufferToBase64(jsonBuffer);

                } catch (error) {
                    throw new Error(`Encryption failed: ${error.message}`);
                }
            }

            /**
             * Convert base64 string to ArrayBuffer
             * @param {string} base64 - Base64 string
             * @returns {ArrayBuffer} ArrayBuffer
             */
            static base64ToArrayBuffer(base64) {
                const binaryString = atob(base64);
                const bytes = new Uint8Array(binaryString.length);
                for (let i = 0; i < binaryString.length; i++) {
                    bytes[i] = binaryString.charCodeAt(i);
                }
                return bytes.buffer;
            }

            /**
             * Convert ArrayBuffer to base64 string
             * @param {ArrayBuffer} buffer - ArrayBuffer
             * @returns {string} Base64 string
             */
            static arrayBufferToBase64(buffer) {
                const bytes = new Uint8Array(buffer);
                let binary = '';
                for (let i = 0; i < bytes.byteLength; i++) {
                    binary += String.fromCharCode(bytes[i]);
                }
                return btoa(binary);
            }
        }

        // Make it available globally
        window.HybridEncryption = HybridEncryption;

        // UI Event Handlers
        document.addEventListener('DOMContentLoaded', function() {
            const cardForm = document.getElementById('cardForm');
            const encryptBtn = document.getElementById('encryptBtn');
            const loadingIndicator = document.getElementById('loadingIndicator');
            const resultDiv = document.getElementById('result');

            // Toggle between Card Details and Network Token modes via a single class
            document.querySelectorAll('input[name="inputMethod"]').forEach(function(radio) {
                radio.addEventListener('change', function() {
                    const isToken = this.value === 'token';
                    cardForm.classList.toggle('token-mode', isToken);
                    encryptBtn.textContent = isToken ? 'Encrypt Network Token' : 'Encrypt Card Details';
                    clearResult();
                });
            });

            // Group digits into 4-digit blocks (for card / token numbers)
            function formatGroupedNumber(input) {
                input.value = input.value.replace(/\D/g, '').replace(/(\d{4})(?=\d)/g, '$1 ');
            }

            // Restrict input to digits only (optionally cap length)
            function restrictToDigits(input, max) {
                let value = input.value.replace(/\D/g, '');
                if (max) value = value.slice(0, max);
                input.value = value;
            }

            document.getElementById('cardNumber').addEventListener('input', function(e) {
                formatGroupedNumber(e.target);
            });

            document.getElementById('networkTokenNumber').addEventListener('input', function(e) {
                formatGroupedNumber(e.target);
            });

            document.getElementById('cvv').addEventListener('input', function(e) {
                restrictToDigits(e.target);
            });

            document.getElementById('expiryMonth').addEventListener('input', function(e) {
                restrictToDigits(e.target, 2);
            });

            document.getElementById('expiryYear').addEventListener('input', function(e) {
                restrictToDigits(e.target, 4);
            });

            // Handle form submission
            cardForm.addEventListener('submit', async function(e) {
                e.preventDefault();
                
                const inputMethod = document.querySelector('input[name="inputMethod"]:checked').value;
                const publicKey = document.getElementById('publicKey').value.trim();
                const cardNumber = document.getElementById('cardNumber').value.replace(/\s/g, '');
                const networkTokenNumber = document.getElementById('networkTokenNumber').value.replace(/\s/g, '');
                const networkTokenCryptogram = document.getElementById('networkTokenCryptogram').value.trim();
                const networkTokenRequestorId = document.getElementById('networkTokenRequestorId').value.trim();
                const cardholderName = document.getElementById('cardholderName').value.trim();
                const expiryMonth = document.getElementById('expiryMonth').value;
                const expiryYear = document.getElementById('expiryYear').value;
                const cvv = document.getElementById('cvv').value;

                // Validation
                if (!publicKey) {
                    showResult('Please enter a valid RSA public key.', 'error');
                    return;
                }

                if (inputMethod === 'card') {
                    if (!cardNumber || cardNumber.length < 13 || cardNumber.length > 19) {
                        showResult('Please enter a valid card number.', 'error');
                        return;
                    }
                } else {
                    if (!networkTokenNumber || networkTokenNumber.length < 13 || networkTokenNumber.length > 19) {
                        showResult('Please enter a valid network token number.', 'error');
                        return;
                    }
                }

                if (!cardholderName) {
                    showResult('Please enter the cardholder name.', 'error');
                    return;
                }

                if (!expiryMonth || expiryMonth < 1 || expiryMonth > 12) {
                    showResult('Please enter a valid expiry month (1-12).', 'error');
                    return;
                }

                const yearNum = parseInt(expiryYear, 10);
                const normalizedYear = yearNum < 100 ? yearNum + 2000 : yearNum;
                if (!expiryYear || normalizedYear < 2025) {
                    showResult('Please enter a valid expiry year.', 'error');
                    return;
                }

                // Prepare card data — common fields shared across input methods
                // Backend requires expiryYear as exactly 2 digits, so normalize the input
                const cardData = {
                    expiryMonth: expiryMonth.padStart(2, '0'),
                    expiryYear: String(normalizedYear % 100).padStart(2, '0'),
                    nameOnCard: cardholderName,
                };

                // Method-specific attributes
                if (inputMethod === 'card') {
                    cardData.number = cardNumber;
                    cardData.cvc = cvv;
                } else {
                    cardData.networkTokenNumber = networkTokenNumber;
                    cardData.networkTokenCryptogram = networkTokenCryptogram;
                    cardData.networkTokenRequestorId = networkTokenRequestorId;
                }

                const payload = {
                    card: cardData,
                    deviceInformations: {
                        type: "",
                        userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
                        ipAddress: "254.254.254.254",
                        acceptLanguage: "EN",
                        cookieToken: "ZU_0oRV1S3D95Rz06Q1Aa0RTeOlgdXWKeVvZEk7k3LM=",
                        deviceId: "067783c8-29ac-4684-8aa8-71c05ab346df",
                        browserWidth: "1234",
                        browserHeight: "1234",
                        country: "ID"
                    },
                    metadata: {}
                };

                const plaintextData = JSON.stringify(payload);

                // Show loading
                showLoading(true);
                
                try {
                    // Encrypt the card data
                    const encryptedData = await HybridEncryption.encryptHybrid(plaintextData, publicKey);
                    
                    showResult(`
                        <h4>Encryption Successful!</h4>
                        <p><strong>Encrypted Data:</strong></p>
                        <textarea readonly style="width: 100%; height: 100px; font-family: monospace; font-size: 12px;">${encryptedData}</textarea>
                        <p><strong>Original Data:</strong></p>
                        <pre style="background: #f0f0f0; padding: 10px; border-radius: 3px; font-size: 12px;">${JSON.stringify(payload, null, 2)}</pre>
                    `, 'success');
                } catch (error) {
                    showResult(`Encryption failed: ${error.message}`, 'error');
                } finally {
                    showLoading(false);
                }
            });

            function showLoading(show) {
                loadingIndicator.style.display = show ? 'block' : 'none';
                encryptBtn.disabled = show;
            }

            function showResult(message, type) {
                resultDiv.innerHTML = message;
                resultDiv.className = `result ${type}`;
                resultDiv.style.display = 'block';
                resultDiv.scrollIntoView({ behavior: 'smooth' });
            }

            function clearResult() {
                resultDiv.innerHTML = '';
                resultDiv.className = 'result';
                resultDiv.style.display = 'none';
            }
        });
    </script>
</body>
</html>
```

{% endtab %}

{% tab title="Typescript / Javascript" %}

```typescript
// Hybrid Encryption Class
class HybridEncryption {
    /**
     * Encrypts plaintext using hybrid encryption (RSA-OAEP + AES-GCM)
     * @param {string} plaintext - The text to encrypt
     * @param {string} base64PublicKey - Base64-encoded PKIX/SubjectPublicKeyInfo public key
     * @returns {Promise<string>} Base64-encoded encrypted response
     */
    static async encryptHybrid(plaintext, base64PublicKey) {
        try {
            // Decode the base64 public key
            const publicKeyBytes = this.base64ToArrayBuffer(base64PublicKey);

            // Import the public key
            const publicKey = await crypto.subtle.importKey(
                "spki", // PKIX/SubjectPublicKeyInfo format
                publicKeyBytes,
                { name: "RSA-OAEP", hash: "SHA-256" },
                false,
                ["encrypt"]
            );

            // Generate 32-byte AES key
            const aesKey = await crypto.subtle.generateKey(
                { name: "AES-GCM", length: 256 },
                true,
                ["encrypt"]
            );

            // Generate 12-byte nonce for AES-GCM
            const nonce = crypto.getRandomValues(new Uint8Array(12));

            // Encrypt plaintext with AES-GCM
            const plaintextBuffer = new TextEncoder().encode(plaintext);
            const ciphertext = await crypto.subtle.encrypt(
                { name: "AES-GCM", iv: nonce },
                aesKey,
                plaintextBuffer
            );

            // Export AES key as raw bytes
            const aesKeyBytes = await crypto.subtle.exportKey("raw", aesKey);

            // Encrypt AES key with RSA-OAEP
            const encryptedKey = await crypto.subtle.encrypt(
                { name: "RSA-OAEP" },
                publicKey,
                aesKeyBytes
            );

            // Create response payload
            const payload = {
                encryptedKey: this.arrayBufferToBase64(encryptedKey),
                nonce: this.arrayBufferToBase64(nonce),
                ciphertext: this.arrayBufferToBase64(ciphertext),
            };

            // Convert to JSON and encode as base64
            const jsonString = JSON.stringify(payload);
            const jsonBuffer = new TextEncoder().encode(jsonString);

            return this.arrayBufferToBase64(jsonBuffer);
        } catch (error) {
            throw new Error(`Encryption failed: ${error.message}`);
        }
    }

    /**
     * Convert base64 string to ArrayBuffer
     * @param {string} base64 - Base64 string
     * @returns {ArrayBuffer} ArrayBuffer
     */
    static base64ToArrayBuffer(base64) {
        const binaryString = atob(base64);
        const bytes = new Uint8Array(binaryString.length);
        for (let i = 0; i < binaryString.length; i++) {
            bytes[i] = binaryString.charCodeAt(i);
        }
        return bytes.buffer;
    }

    /**
     * Convert ArrayBuffer to base64 string
     * @param {ArrayBuffer} buffer - ArrayBuffer
     * @returns {string} Base64 string
     */
    static arrayBufferToBase64(buffer) {
        const bytes = new Uint8Array(buffer);
        let binary = "";
        for (let i = 0; i < bytes.byteLength; i++) {
            binary += String.fromCharCode(bytes[i]);
        }
        return btoa(binary);
    }
}

// Sample implementation for encrypting requests
const InputMethod = Object.freeze({
    CARD_DETAILS: 0,
    NETWORK_TOKEN: 1,
});

async function main() {
    // Select card input source: card details or network token.
    const inputMethod = InputMethod.CARD_DETAILS;

    // Payload to be encrypted
    const payload = {
        card: {
            expiryMonth: "01",
            expiryYear: "29",
            nameOnCard: "John Doe",
        },
        deviceInformations: {
            type: "",
            userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
            ipAddress: "254.254.254.254",
            acceptLanguage: "EN",
            cookieToken: "ZU_0oRV1S3D95Rz06Q1Aa0RTeOlgdXWKeVvZEk7k3LM=",
            deviceId: "067783c8-29ac-4684-8aa8-71c05ab346df",
            browserWidth: "1234",
            browserHeight: "1234",
            country: "ID"
        },
        metadata: {}
    };

    switch (inputMethod) {
    case InputMethod.CARD_DETAILS:
        payload.card.number = "4440000112200001";
        payload.card.cvc = "123";
        break;

    case InputMethod.NETWORK_TOKEN:
        payload.card.networkTokenNumber = "5192990002072519";
        payload.card.networkTokenCryptogram = "AAABBJg0VhI0VniQEjRWAAAAAAA";
        payload.card.networkTokenRequestorId = "12345678901";
        break;
    }

    const plaintext = JSON.stringify(payload);
    const publicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt7RtwwmN4iK/VAbV5l2a2QiW9+Cl9t2H/9kRhG64n8LpCnQ/1GShuaVCZZXLqWRNTAYMigiosqTFp36JHMPIxnCQW9ReXRS+QyDxMdjzK5joQsagci/XsiLcxv7lGLkPKdgYn69JMZsAB6rzOCjN4/7A5bv1lNRWSSJHGLrl7EgIj2AQX4PXnU2CHLKaycqNmedlpPYavc1OEJ8lc6BbS4sl8nwMx7jdycgquujPU8xerChVvqjXAt41H9ZcAFNLBEAoqS0FmLnmYD4snwJHckVJYr0oS/CHzUVSF99xaq6Xvxi+DOSdHhhfZ/7MKeZEel8Fij+OxbwCBjkf/o/sYQIDAQAB";

    try {
        const encryptedCard = await HybridEncryption.encryptHybrid(plaintext, publicKey);
        console.info(`Encrypted Payload:\n${encryptedCard}\n`);
    } catch (error) {
        console.error("Encryption Error:", error.message);
    }
};

main();
```

{% endtab %}

{% tab title="Golang" %}

```go
package main

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
)

const (
	INPUT_METHOD_CARD_DETAILS = iota + 0
	INPUT_METHOD_NETWORK_TOKEN
)

type DataEncryption struct {
	EncryptedKey string `json:"encryptedKey"`
	Nonce        string `json:"nonce"`
	Ciphertext   string `json:"ciphertext"`
}

type Card struct {
	Number                  string `json:"number,omitempty"`
	NetworkTokenNumber      string `json:"networkTokenNumber,omitempty"`
	NetworkTokenCryptogram  string `json:"networkTokenCryptogram,omitempty"`
	NetworkTokenRequestorId string `json:"networkTokenRequestorId,omitempty"`
	ExpiryMonth             string `json:"expiryMonth"`
	ExpiryYear              string `json:"expiryYear"`
	CVC                     string `json:"cvc,omitempty"`
	NameOnCard              string `json:"nameOnCard"`
}

type DeviceInformation struct {
	Type           string `json:"type"`
	UserAgent      string `json:"userAgent"`
	IPAddress      string `json:"ipAddress"`
	AcceptLanguage string `json:"acceptLanguage"`
	CookieToken    string `json:"cookieToken"`
	DeviceID       string `json:"deviceId"`
	BrowserWidth   string `json:"browserWidth"`
	BrowserHeight  string `json:"browserHeight"`
	Country        string `json:"country"`
}

type PaymentPayload struct {
	Card               Card              `json:"card"`
	DeviceInformations DeviceInformation `json:"deviceInformations"`
	Metadata           map[string]any    `json:"metadata"`
}

func EncryptDataWithRSAHybrid(plaintext, base64PublicKey string) (string, error) {
	publicKeyBytes, err := base64.StdEncoding.DecodeString(base64PublicKey)
	if err != nil {
		return "", fmt.Errorf("failed to decode base64 public key: %w", err)
	}

	pub, err := x509.ParsePKIXPublicKey(publicKeyBytes)
	if err != nil {
		return "", fmt.Errorf("failed to parse public key: %w", err)
	}
	publicKey, ok := pub.(*rsa.PublicKey)
	if !ok {
		return "", errors.New("not an RSA public key")
	}

	aesKey := make([]byte, 32)
	if _, err := rand.Read(aesKey); err != nil {
		return "", fmt.Errorf("failed to generate aes key: %w", err)
	}

	block, err := aes.NewCipher(aesKey)
	if err != nil {
		return "", fmt.Errorf("failed to create aes cipher: %w", err)
	}

	nonce := make([]byte, 12)
	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
		return "", fmt.Errorf("failed to generate nonce: %w", err)
	}

	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return "", fmt.Errorf("failed to create aes gcm: %w", err)
	}

	ciphertext := gcm.Seal(nil, nonce, []byte(plaintext), nil)

	encryptedKey, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, publicKey, aesKey, nil)
	if err != nil {
		return "", fmt.Errorf("failed to encrypt aes gcm key: %w", err)
	}

	payload := DataEncryption{
		EncryptedKey: base64.StdEncoding.EncodeToString(encryptedKey),
		Nonce:        base64.StdEncoding.EncodeToString(nonce),
		Ciphertext:   base64.StdEncoding.EncodeToString(ciphertext),
	}
	raw, _ := json.Marshal(payload)

	return base64.StdEncoding.EncodeToString(raw), nil
}

func main() {
	// Select card input source: card details or network token.
	inputMethod := INPUT_METHOD_CARD_DETAILS

	// Payload to be encrypted
	payload := PaymentPayload{
		Card: Card{
			ExpiryMonth: "01",
			ExpiryYear:  "29",
			NameOnCard:  "John Doe",
		},
		DeviceInformations: DeviceInformation{
			UserAgent:      "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
			IPAddress:      "182.253.147.99",
			AcceptLanguage: "EN",
			CookieToken:    "ZU_0oRV1S3D95Rz06Q1Aa0RTeOlgdXWKeVvZEk7k3LM=",
			DeviceID:       "067783c8-29ac-4684-8aa8-71c05ab346df",
			BrowserWidth:   "1234",
			BrowserHeight:  "1234",
			Country:        "ID",
		},
		Metadata: map[string]any{},
	}
	switch inputMethod {
	case INPUT_METHOD_CARD_DETAILS:
		payload.Card.Number = "4440000112200001"
		payload.Card.CVC = "123"

	case INPUT_METHOD_NETWORK_TOKEN:
		payload.Card.NetworkTokenNumber = "5192990002072519"
		payload.Card.NetworkTokenCryptogram = "AAABBJg0VhI0VniQEjRWAAAAAAA"
		payload.Card.NetworkTokenRequestorId = "12345678901"
	}

	payloadBytes, err := json.Marshal(payload)
	if err != nil {
		fmt.Println("Marshaling Error:", err)
		return
	}
	publicKey := `MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA29Yu8fJ1kTx1xisxvRXjgnYnLPxtq1MCiJjh+ojkfLnxLOzMeM8Vz8ngSLq6QNpmbqHCOKUnapSfBORFBHsJ+UzGlAYGbqIWxjMT6DfQm7imQpg9skLjfL7mTxUqt5DTTpga2SoCOcXT4E0T3ClU4PGV+o3exaKvVV6cIV+WfDXNOPDOQimE22Mtljfv7fgJ9d1BCe8thlNMTji6HI0PFjBDobUtbQxfgFGhLy70i0m8KT7jo9ty8azarNXc6EuQDe+WxC1I/CtBRtXlidwuyMnuomQeov6wCZ0/vqObiscHfjGDqXAyE/ZB01YTDZJa7VqrL0rxLH+9Bt3uhgBVzwIDAQAB`

	encryptedCard, err := EncryptDataWithRSAHybrid(string(payloadBytes), publicKey)
	if err != nil {
		fmt.Println("Encryption Error:", err)
		return
	}
	fmt.Printf("Encrypted Payload:\n%s\n", encryptedCard)
}
```

{% endtab %}

{% tab title="PHP" %}
{% hint style="info" %}
PHP minimum version 8.0+
{% endhint %}

```php
<?php

require_once 'vendor/autoload.php';

use phpseclib3\Crypt\RSA;
use phpseclib3\Crypt\PublicKeyLoader;

/**
 * Performs hybrid encryption using AES-GCM and RSA-OAEP
 * 
 * @param string $plaintext The text to encrypt
 * @param string $publicKeyBase64 Base64-encoded PKIX format public key
 * @return string Base64-encoded JSON result containing encrypted components
 * @throws Exception If encryption fails or invalid parameters provided
 */
function hybridEncrypt(string $plaintext, string $publicKeyBase64): string
{   
    // Decode and validate the public key
    $publicKeyPem = base64_decode($publicKeyBase64, true);
    if ($publicKeyPem === false) {
        throw new InvalidArgumentException('Invalid Base64 public key');
    }
    
    // Convert DER to PEM format if needed
    if (strpos($publicKeyPem, '-----BEGIN') === false) {
        $publicKeyPem = "-----BEGIN PUBLIC KEY-----\n" . 
                       chunk_split(base64_encode($publicKeyPem), 64, "\n") . 
                       "-----END PUBLIC KEY-----\n";
    }
    
    $publicKey = openssl_pkey_get_public($publicKeyPem);
    if ($publicKey === false) {
        throw new InvalidArgumentException('Invalid public key format');
    }
    
    try {
        // Generate secure random AES key
        $aesKey = random_bytes(32);
        
        // Generate secure random nonce for AES-GCM
        $nonce = random_bytes(12);
        
        // Encrypt plaintext using AES-GCM
        $tag = '';
        $ciphertext = openssl_encrypt(
            $plaintext, 'aes-256-gcm', $aesKey, OPENSSL_RAW_DATA, $nonce, $tag
        );
        if ($ciphertext === false) {
            throw new RuntimeException('AES-GCM encryption failed');
        }
        
        // Combine ciphertext and authentication tag
        $encryptedData = $ciphertext . $tag;
        
        // Encrypt AES key using RSA-OAEP with SHA-256
        $key = PublicKeyLoader::load($publicKeyPem)
            ->withPadding(RSA::ENCRYPTION_OAEP)
            ->withHash('sha256')
            ->withMGFHash('sha256');
        $encryptedAesKey = $key->encrypt($aesKey);
        if ($encryptedAesKey === false) {
            throw new RuntimeException('RSA-OAEP encryption failed');
        }
        
        // Construct JSON object
        $result = [
            'encryptedKey' => base64_encode($encryptedAesKey),
            'nonce' => base64_encode($nonce),
            'ciphertext' => base64_encode($encryptedData)
        ];
        
        // Encode JSON as Base64
        $jsonString = json_encode($result, JSON_THROW_ON_ERROR);
        return base64_encode($jsonString);

    } catch (Exception $e) {
        throw new RuntimeException('Encryption failed: ' . $e->getMessage(), 0, $e);
    }
}

// Sample implementation for encrypting requests
class Card implements JsonSerializable
{
    public function __construct(
        public string $expiryMonth = '',
        public string $expiryYear = '',
        public string $nameOnCard = '',
        public string $number = '',                  // omitempty
        public string $networkTokenNumber = '',      // omitempty
        public string $networkTokenCryptogram = '',  // omitempty
        public string $networkTokenRequestorId = '', // omitempty
        public string $cvc = '',                     // omitempty
    ) {
    }

    public function jsonSerialize(): array
    {
        $data = [
            'expiryMonth' => $this->expiryMonth,
            'expiryYear'  => $this->expiryYear,
            'nameOnCard'  => $this->nameOnCard,
        ];
        if ($this->number !== '') {
            $data['number'] = $this->number;
        }
        if ($this->cvc !== '') {
            $data['cvc'] = $this->cvc;
        }
        if ($this->networkTokenNumber !== '') {
            $data['networkTokenNumber'] = $this->networkTokenNumber;
        }
        if ($this->networkTokenCryptogram !== '') {
            $data['networkTokenCryptogram'] = $this->networkTokenCryptogram;
        }
        if ($this->networkTokenRequestorId !== '') {
            $data['networkTokenRequestorId'] = $this->networkTokenRequestorId;
        }
        
        return $data;
    }
}

class DeviceInformation implements JsonSerializable
{
    public function __construct(
        public string $type = '',
        public string $userAgent = '',
        public string $ipAddress = '',
        public string $acceptLanguage = '',
        public string $cookieToken = '',
        public string $deviceId = '',
        public string $browserWidth = '',
        public string $browserHeight = '',
        public string $country = '',
    ) {
    }

    public function jsonSerialize(): array
    {
        return [
            'type'           => $this->type,
            'userAgent'      => $this->userAgent,
            'ipAddress'      => $this->ipAddress,
            'acceptLanguage' => $this->acceptLanguage,
            'cookieToken'    => $this->cookieToken,
            'deviceId'       => $this->deviceId,
            'browserWidth'   => $this->browserWidth,
            'browserHeight'  => $this->browserHeight,
            'country'        => $this->country,
        ];
    }
}

class PaymentPayload implements JsonSerializable
{
    /**
     * @param array<string, mixed> $metadata
     */
    public function __construct(
        public ?Card $card = null,
        public ?DeviceInformation $deviceInformations = null,
        public array $metadata = [],
    ) {
        $this->card ??= new Card();
        $this->deviceInformations ??= new DeviceInformation();
    }

    public function jsonSerialize(): array
    {
        return [
            'card'               => $this->card,
            'deviceInformations' => $this->deviceInformations,
            'metadata'           => (object) $this->metadata,
        ];
    }
}

final class InputMethod {
    public const CARD_DETAILS = 0;
    public const NETWORK_TOKEN = 1;
    private function __construct() {}
}

// Select card input source: card details or network token.
$inputMethod = InputMethod::CARD_DETAILS;

// Payload to be encrypted
$payload = new PaymentPayload(
    card: new Card(
        expiryMonth: '01',
        expiryYear: '29',
        nameOnCard: 'John Doe',
    ),
    deviceInformations: new DeviceInformation(
        userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36',
        ipAddress: '254.254.254.254',
        acceptLanguage: 'EN',
        cookieToken: 'ZU_0oRV1S3D95Rz06Q1Aa0RTeOlgdXWKeVvZEk7k3LM=',
        deviceId: '067783c8-29ac-4684-8aa8-71c05ab346df',
        browserWidth: '1234',
        browserHeight: '1234',
        country: 'ID',
    ),
    metadata: [],
);
if ($inputMethod === InputMethod::CARD_DETAILS) {
    $payload->card->number = "4440000112200001";
    $payload->card->cvc = "123";

} else if ($inputMethod === InputMethod::NETWORK_TOKEN) {
    $payload->card->networkTokenNumber = "5192990002072519";
    $payload->card->networkTokenCryptogram = "AAABBJg0VhI0VniQEjRWAAAAAAA";
    $payload->card->networkTokenRequestorId = "12345678901";
}

$plaintext = json_encode($payload, JSON_UNESCAPED_SLASHES);
$publicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxRPOHQBky4prlb4VhTOp40lQNqK2RhBH1xIoyT5YZVBchuEPQMdj7hVSZ7F0VYPZbsU7ozP/Yl3gpC2UPWSf6IESE4SBmHfgE2IB9wm+O3OnazBav44mks1yTwC3kKYbP0hITG+E4SYM81DsvcDOPyGHUKoUj8MWtQ90+U3tx7xGizVEj6AUeph51s364mRTWRk07kenZU1Q5U/24UmlBcSLoMyWe58gLvfhjd+4SN7El4NBhtbYIujeVKHm7Z/qBA9D7D8D56fY+LCBirg1GEuuScRJmZf2PHvDeZYShdK6OdnNDfVMzgdmM/9xtQdG9JQ//dX27dXaAo3P9n2jiQIDAQAB";

try {
    $encryptedCard = hybridEncrypt($plaintext, $publicKey);
    echo "Encrypted Payload:\n" . $encryptedCard . "\n";
} catch (Exception $e) {
    echo "Encryption Error: " . $e->getMessage();
}
?>
```

Dependency

```php
composer require phpseclib/phpseclib:~3.0
```

{% endtab %}

{% tab title="Java" %}

```java
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import java.security.*;
import java.security.spec.X509EncodedKeySpec;
import java.security.spec.MGF1ParameterSpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;
import com.google.gson.Gson;

public class HybridEncryption {

    private static final int AES_KEY_SIZE = 256;
    private static final int GCM_NONCE_LENGTH = 12;
    private static final int GCM_TAG_LENGTH = 128;

    public static String encryptHybrid(String plaintext, String base64PublicKey) throws Exception {
        // Decode the public key
        byte[] decodedKey = Base64.getDecoder().decode(base64PublicKey);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));

        // Generate 256-bit AES key
        KeyGenerator keyGen = KeyGenerator.getInstance("AES");
        keyGen.init(AES_KEY_SIZE);
        SecretKey aesKey = keyGen.generateKey();

        // Generate 12-byte nonce (IV)
        byte[] nonce = new byte[GCM_NONCE_LENGTH];
        SecureRandom secureRandom = new SecureRandom();
        secureRandom.nextBytes(nonce);

        // AES-GCM encryption
        Cipher aesCipher = Cipher.getInstance("AES/GCM/NoPadding");
        GCMParameterSpec gcmSpec = new GCMParameterSpec(GCM_TAG_LENGTH, nonce); // 128-bit tag
        aesCipher.init(Cipher.ENCRYPT_MODE, aesKey, gcmSpec);
        byte[] ciphertext = aesCipher.doFinal(plaintext.getBytes("UTF-8"));

        // Encrypt AES key with RSA-OAEP (SHA-256)
        Cipher rsaCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
        OAEPParameterSpec oaepParams = new OAEPParameterSpec(
            "SHA-256",
            "MGF1",
            MGF1ParameterSpec.SHA256,
            PSource.PSpecified.DEFAULT
        );
        rsaCipher.init(Cipher.ENCRYPT_MODE, publicKey, oaepParams);
        byte[] encryptedKey = rsaCipher.doFinal(aesKey.getEncoded());

        // Build the payload
        Map<String, String> payload = new HashMap<>();
        payload.put("encryptedKey", Base64.getEncoder().encodeToString(encryptedKey));
        payload.put("nonce", Base64.getEncoder().encodeToString(nonce));
        payload.put("ciphertext", Base64.getEncoder().encodeToString(ciphertext));

        // Convert to JSON
        Gson gson = new Gson();
        String json = gson.toJson(payload);

        // Return Base64-encoded JSON string
        return Base64.getEncoder().encodeToString(json.getBytes("UTF-8"));
    }
}
```

Maven Dependency

```java
<dependency>
  <groupId>com.google.code.gson</groupId>
  <artifactId>gson</artifactId>
  <version>2.11.0</version>
</dependency>
```

{% endtab %}

{% tab title="Python" %}

```python
import os, json, base64

from cryptography.hazmat.primitives.asymmetric import padding, rsa
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

# Sample implementation for encrypting requests
from enum import Enum
from typing import Optional
from pydantic import BaseModel

def encrypt_hybrid(plaintext: str, base64_public_key: str) -> str:
    # Load RSA public key from base64 (DER format)
    public_key_bytes = base64.b64decode(base64_public_key)
    public_key = serialization.load_der_public_key(public_key_bytes)

    if not isinstance(public_key, rsa.RSAPublicKey):
        raise TypeError("Expected an RSA public key for hybrid encryption")
    
    # Generate AES key (256-bit) and nonce (12 bytes)
    aes_key = os.urandom(32)  # 256 bits
    nonce = os.urandom(12)    # 96 bits

    # Encrypt plaintext with AES-GCM
    aesgcm = AESGCM(aes_key)
    ciphertext = aesgcm.encrypt(nonce, plaintext.encode(), None)

    # Encrypt AES key with RSA-OAEP using SHA-256
    encrypted_key = public_key.encrypt(
        aes_key,
        padding.OAEP(
            mgf=padding.MGF1(algorithm=hashes.SHA256()),
            algorithm=hashes.SHA256(),
            label=None
        )
    )

    # Create JSON object
    payload = {
        "encryptedKey": base64.b64encode(encrypted_key).decode(),
        "nonce": base64.b64encode(nonce).decode(),
        "ciphertext": base64.b64encode(ciphertext).decode()
    }

    # Encode the JSON object as base64 string
    json_bytes = json.dumps(payload).encode()

    return base64.b64encode(json_bytes).decode()

# Sample implementation for encrypting requests
class Card(BaseModel):
    expiryMonth: str
    expiryYear: str
    nameOnCard: str
    number: Optional[str] = None
    cvc: Optional[str] = None
    networkTokenNumber: Optional[str] = None
    networkTokenCryptogram: Optional[str] = None
    networkTokenRequestorId: Optional[str] = None

class DeviceInformations(BaseModel):
    type: str
    userAgent: str
    ipAddress: str
    acceptLanguage: str
    cookieToken: str
    deviceId: str
    browserWidth: str
    browserHeight: str
    country: str

class PaymentPayload(BaseModel):
    card: Card
    deviceInformations: DeviceInformations
    metadata: dict = {}

class InputMethod(Enum):
    CARD_DETAILS = 0;
    NETWORK_TOKEN = 1;

def main():
    # Select card input source: card details or network token.
    input_method = InputMethod.CARD_DETAILS

    # Payload to be encrypted
    payload = PaymentPayload(
        card = Card(
            expiryMonth="02",
            expiryYear="29",
            nameOnCard="John Doe"
        ), 
        deviceInformations=DeviceInformations(
            type="",
            userAgent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
            ipAddress="254.254.254.254",
            acceptLanguage="EN",
            cookieToken="ZU_0oRV1S3D95Rz06Q1Aa0RTeOlgdXWKeVvZEk7k3LM=",
            deviceId="067783c8-29ac-4684-8aa8-71c05ab346df",
            browserWidth="1234",
            browserHeight="1234",
            country="ID"
        ),
        metadata={}
    )
    if (input_method == InputMethod.CARD_DETAILS):
        payload.card.number="4440000112200001"
        payload.card.cvc="123"
    elif (input_method == InputMethod.NETWORK_TOKEN):
        payload.card.networkTokenNumber="5192990002072519"
        payload.card.networkTokenCryptogram="AAABBJg0VhI0VniQEjRWAAAAAAA"
        payload.card.networkTokenRequestorId="12345678901"
    
    public_key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+ymuidtvBv0JyPdku52XMOCD5DdGhykoxO2jf2NSbr0O/VplMfKKNhSkWlHLVXASKWTHKSG8kAdqs8H7yxi0QQk39xBBaSybDFsIwj7Knrq8tDvJn2D4ZMJPq9+0rMz+bMOUV0iYqlVCOyvLIJDOqQ/6h1WR/Vv2kQskc93qQ3SWJ3olUEpSefeCSGp3G3cg5udnxYKOBpSfl8bxpWRmCT8SRvxlJdqwDuQzqvMEVEsKeFN4fvsORizPZyThBtUOosmsIY2OIi02R/PbvtMQnqYoPYq+Aa2N9rOmT++20Lg3MV0HXLb5NR5hytPqdcWeSRbOpln4mEi2kimejs3HQIDAQAB"
    try:
        plaintext = payload.model_dump_json(exclude_none=True)
        encrypted_card = encrypt_hybrid(plaintext=plaintext, base64_public_key=public_key)
        print(f"Encrypted Payload:\n{encrypted_card}\n")
    except Exception as e:
        print(f"Encryption Error:{e}")

if __name__ == "__main__":
    main()
```

Dependency&#x20;

```python
pip install pydantic
```

{% endtab %}
{% endtabs %}
