🚀 VaultsPay API v1 is live. See what's new →
Set UpEncryption & Security

Encryption & Security

All API requests/responses must be encrypted using a hybrid encryption model.

UAT Environment

Base URL: https://api.vaultspay.dev

In UAT, you can optionally disable encryption by adding a header:

x-enable-encryption: false

Production Environment

Base URL: https://rest.vaultspay.com

  • Encryption is mandatory
  • Use the RSA public key shared with you for encryption

Encryption Flow

  1. Generate AES key + IV on the client side
  2. Encrypt payload with AES key
  3. Encrypt AES key with RSA public key
  4. Send to backend:
    • AES-encrypted data
    • RSA-encrypted AES key

Response Decryption

  • Use the same AES key used in the request to decrypt the backend response.

Required Headers

HeaderValue Description
x-session-nonceIV used in AES encryption
x-auth-signatureAES key encrypted with RSA public key
x-device-os’SAAS’
x-device-idSaaS app ID (e.g., 17)

Sample Java Code for Encryption/Decryption

public Map<String, String> encrypt(String plainText) {
            KeyGenerator keyGen = KeyGenerator.getInstance("AES");
            keyGen.init(256); // 256-bit AES
 
            // Step 2: Generate AES Key
            SecretKey aesKey = keyGen.generateKey();
 
            // Step 3: Encode key in Base64
            byte[] iv = new byte[12]; // 12 bytes = 96 bits (recommended for GCM)
            new SecureRandom().nextBytes(iv);
 
            String encrypt = encrypt(plainText, aesKey.getEncoded(), iv);
            
            PublicKey publicKey = publicKeyFromBase64("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAne7/zCQBaojWUqVOYrCyzVMubq2cBJhepEKPri+qW6uh1HGCp7XZFXbl2T6XG0XJa4QFFjaQAo+BMAkCUo2JuL5cxyR7CsmaZgdeqtExteQ9rc92G0iMsI4qfSB9Hr3bqrmXqUEIMZJ7tboz51hyW1Mq4m1Y4bzlGwFT0WNyBbUMU/mMbSRd/nU7Py9qoGJJVJRwdNq7E+s8FPMXeWdMQw49yR7IGWayGJ6Fqwy6hnpZesPHAqKmiRCk5DRPMYa6RVk1k/B1PJX6xt/87L9Me+lBM/E5A30qg7f+oCbGZqVHeWBFJjZ6kpr5o9UtqzgdsHMGNln3wZw2C74klrVTBwIDAQAB");
            String base64Iv = Base64.getEncoder().encodeToString(iv);
 
            Map<String, String> result = new HashMap<>();
            result.put("encryptedData", encrypt);
            result.put("iv", base64Iv);
            result.put("encryptedKey", encryptRsa(aesKey.getEncoded(), publicKey));
            return result;
}
 
public String encrypt(String data, byte[] key, byte[] iv) throws Exception {
        // Ensure the key is the correct length (32 bytes for AES-256)
        byte[] truncatedKey = Arrays.copyOf(key, 32);
 
        Cipher cipher = Cipher.getInstance(AES_GCM_NO_PADDING);
        SecretKey secretKey = new SecretKeySpec(truncatedKey, "AES");
        GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
        byte[] encrypted = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(encrypted);
    }
 
    public String encryptRsa(byte[] aesKey, PublicKey publicKey) throws Exception {
        // Step 1: Create cipher instance with matching padding
        Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING");
 
        // Step 2: Configure OAEP params
        OAEPParameterSpec oaepParams = new OAEPParameterSpec(
            "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT
        );
 
        // Step 3: Initialize cipher in encrypt mode
        cipher.init(Cipher.ENCRYPT_MODE, publicKey, oaepParams);
 
        // Step 4: Encrypt the AES key
        byte[] encryptedKey = cipher.doFinal(aesKey);
 
        // Step 5: Return as Base64 string
        return Base64.getEncoder().encodeToString(encryptedKey);
    }
 
public String decrypt(String encryptedText) {
 
        try {
            
            // Decrypt the AES key using the server's RSA private key
            byte[] encryptedAesKey = Base64.getDecoder().decode(aesKey);
            byte[] decryptedAesKey = decryptRsa(encryptedAesKey, public_rsa_key);
            byte[] iv = Base64.getDecoder().decode(iv);
 
            return decrypt(encryptedText,
                decryptedAesKey, iv
            );
        } catch (Exception ecx) {
            log.info("Error in DecryptRequest::decrypt() ", ecx);
            throw new SysException(ErrorCode.DECRYPTION_FAILED);
        }
    }
 
public byte[] decryptRsa(byte[] encryptedKey, PrivateKey privateKey) throws Exception {
        // Use the same padding scheme as the client
        Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING");
        OAEPParameterSpec oaepParams = new OAEPParameterSpec(
            "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT
        );
        cipher.init(Cipher.DECRYPT_MODE, privateKey, oaepParams);
        return cipher.doFinal(encryptedKey);
    }
 
public String decrypt(String encryptedData, byte[] key, byte[] iv) throws Exception {
        // Ensure the key is the correct length (32 bytes for AES-256)
        byte[] truncatedKey = Arrays.copyOf(key, 32);
 
        Cipher cipher = Cipher.getInstance(AES_GCM_NO_PADDING);
        SecretKey secretKey = new SecretKeySpec(truncatedKey, "AES");
        GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv);
        cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec);
        byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encryptedData));
        return new String(decrypted, StandardCharsets.UTF_8);
    }

Sample Python Code for Encryption/Decryption

def encrypt(plain_text: str, rsa_public_key):
    # Step 1: Generate AES key (256-bit)
    aes_key = os.urandom(32)  # 256-bit AES key
 
    # Step 2: Generate IV
    iv = os.urandom(12)  # 12 bytes = 96 bits for GCM
 
    # Step 3: Encrypt data with AES GCM
    encrypted_data = encrypt_aes_gcm(plain_text, aes_key, iv)
 
    # Step 4: Encrypt AES key with RSA public key
    public_key_b64 = (
        rsa_public_key
    )
    public_key = load_rsa_public_key(public_key_b64)
    encrypted_key = encrypt_rsa(aes_key, public_key)
 
    # Step 5: Return results
    return {
        "encryptedData": base64.b64encode(encrypted_data).decode("utf-8"),
        "iv": base64.b64encode(iv).decode("utf-8"),
        "encryptedKey": base64.b64encode(encrypted_key).decode("utf-8"),
        "rawAesKey": base64.b64encode(aes_key).decode("utf-8")
    }
 
def encrypt_aes_gcm(data: str, key: bytes, iv: bytes) -> bytes:
    encryptor = Cipher(
        algorithms.AES(key),
        modes.GCM(iv),
        backend=default_backend()
    ).encryptor()
 
    ciphertext = encryptor.update(data.encode("utf-8")) + encryptor.finalize()
    return ciphertext + encryptor.tag  # Append GCM auth tag
 
def encrypt_rsa(aes_key: bytes, public_key) -> bytes:
    encrypted_key = public_key.encrypt(
        aes_key,
        padding.OAEP(
            mgf=padding.MGF1(algorithm=hashes.SHA256()),
            algorithm=hashes.SHA256(),
            label=None,
        )
    )
    return encrypted_key
 
def load_rsa_public_key(b64_key: str):
    der = base64.b64decode(b64_key)
    return serialization.load_der_public_key(der, backend=default_backend())
 
def decrypt_aes_gcm(encrypted_data_b64: str, key: bytes, iv: bytes) -> str:
    # Truncate or pad key to 32 bytes (AES-256)
    truncated_key = key[:32]
 
    cipher = AES.new(truncated_key, AES.MODE_GCM, nonce=iv)
    decrypted = cipher.decrypt(base64.b64decode(encrypted_data_b64))
    return decrypted.decode('utf-8')
 
def decrypt_rsa(encrypted_key_b64: str, private_key_pem: str) -> bytes:
    encrypted_key = base64.b64decode(encrypted_key_b64)
 
    private_key = serialization.load_pem_private_key(
        private_key_pem.encode(),
        password=None,
        backend=default_backend()
    )
 
    decrypted_key = private_key.decrypt(
        encrypted_key,
        padding.OAEP(
            mgf=padding.MGF1(algorithm=hashes.SHA256()),
            algorithm=hashes.SHA256(),
            label=None
        )
    )
    return decrypted_key
 
GCM_TAG_LENGTH = 16  # in bytes (128 bits)
 
def decrypt(encrypted_data_b64: str, iv_b64: str, aes_key: bytes) -> str:
    try:
        # Step 1: Decode Base64 values
        iv = base64.b64decode(iv_b64)
        encrypted_data = base64.b64decode(encrypted_data_b64)
 
        # Step 2: Split encrypted_data into actual ciphertext and tag
        ciphertext = encrypted_data[:-GCM_TAG_LENGTH]
        tag = encrypted_data[-GCM_TAG_LENGTH:]
 
        # Step 3: Create AES-GCM decryptor
        cipher = Cipher(
            algorithms.AES(aes_key),
            modes.GCM(iv, tag),
            backend=default_backend()
        )
        decryptor = cipher.decryptor()
 
        # Step 4: Decrypt
        decrypted_bytes = decryptor.update(ciphertext) + decryptor.finalize()
 
        return decrypted_bytes.decode('utf-8')
 
    except Exception as e:
        print("Decryption failed:", e)
        raise