CPN

How-to: Encrypt Files for RFI Transmission

This guide explains how to set up encryption and decryption between an OFI and a BFI during the RFI process so that a file can be passed securely. This encryption varies from the method used to encrypt JSON communications between OFI and BFI, but shares some features. For compactness, files are encrypted using AES, and then the key is encrypted using JWE. Both are then transmitted to the BFI for a two-stage decryption process.

In the CPN system, the OFI encrypts a payload with a randomly generated AES key. This key is then encrypted with the BFI's public key using JSON Web Encryption. The encrypted AES key and encrypted file payload are transmitted to the BFI. The BFI decrypts the AES key using their private JWK and uses it to decrypt the file contents.

The following sections describe the steps necessary to encrypt a file sent from an OFI to a BFI through the RFI endpoint.

Using your chosen implementation language, generate a random 128-bit AES key for AES-128-GCM encryption.

Java
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;


/**
 * @return A SecretKey for AES encryption.
 * @throws GeneralSecurityException if the AES algorithm is not available.
 */
public static SecretKey generateAesKey() throws GeneralSecurityException {
  KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
  keyGenerator.init(128, new SecureRandom());
  return keyGenerator.generateKey();
}

Using your chosen implementation language, generate a 12-byte IV.

Java
import java.security.SecureRandom;

/**
 * @return A 12-byte array containing the IV.
 */
public static byte[] generateIv() {
  byte[] iv = new byte[12];
  new SecureRandom().nextBytes(iv);
  return iv;
}

Encrypt the file contents using AES-128-GCM using the key and IV from the previous steps.

Java
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import java.security.GeneralSecurityException;


/**
 * @param plaintextPayload The raw data to encrypt.
 * @param aesKey The AES key to use for encryption.
 * @param iv The 12-byte Initialization Vector.
 * @return The encrypted data, including the GCM authentication tag.
 * @throws GeneralSecurityException if a cryptographic error occurs.
 */
public static byte[] encryptPayload(byte[] plaintextPayload, SecretKey aesKey, byte[] iv) throws GeneralSecurityException {
  Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
  GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, iv);
  cipher.init(Cipher.ENCRYPT_MODE, aesKey, gcmParameterSpec);
  return cipher.doFinal(plaintextPayload);
}

Using the JWK data from the quote response, encrypt the AES key that was used to encrypt the file contents with the following parameters using JWE:

  • Algorithm: ECDH-ES+A128KW
  • Encryption method: A128GCM
Java
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jose.EncryptionMethod;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWEAlgorithm;
import com.nimbusds.jose.JWEEncrypter;
import com.nimbusds.jose.JWEHeader;
import com.nimbusds.jose.JWEObject;
import com.nimbusds.jose.Payload;
import com.nimbusds.jose.crypto.ECDHEncrypter;
import java.util.Base64;
import javax.crypto.SecretKey;
import java.security.interfaces.ECPublicKey;

   /**
    * @param aesKey The AES key to wrap.
    * @param bfiPublicKey The recipient's Elliptic Curve public key.
    * @return A compact, serialized JWE string representing the encrypted key.
    * @throws JOSEException if an error occurs during JWE creation or encryption.
    */
   public static String wrapAesKey(SecretKey aesKey, ECPublicKey bfiPublicKey) throws JOSEException {
  // Create the JWEHeader using ECDH_ES+AS128KW and AES-128-GCM
        JWEHeader header = new JWEHeader(
            JWEAlgorithm.ECDH_ES_A128KW,
            EncryptionMethod.A128GCM
        );

    // Base64 encode the AES key and wrap in JSON string
       String base64AesKey = Base64.getEncoder().encodeToString(aesKey.getEncoded());
       ObjectMapper objectMapper = new ObjectMapper();
       String jsonPayload = objectMapper.writeValueAsString(base64AesKey);

       Payload jwePayload = new Payload(jsonPayload);
       JWEObject jweObject = new JWEObject(header, jwePayload);
       JWEEncrypter encrypter = new ECDHEncrypter(bfiPublicKey);
       jweObject.encrypt(encrypter);
       return jweObject.serialize();
   }

After performing the encryption steps from the previous steps, you should have three components:

  • The AES-encrypted file content
  • The JWE string containing the encrypted AES key
  • The base64-encoded 12-byte IV

Use these components to create a multipart/form-data request to the upload RFI file endpoint. A 200 response from the API indicates that the encryption was performed correctly and the BFI can decrypt the file's contents.

Java
import okhttp3.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.io.IOException;
import java.util.Base64;
import java.util.UUID;

/**
* Upload an encrypted RFI file using multipart/form-data.
*
* @param paymentId The payment UUID.
* @param rfiId The RFI UUID.
* @param encryptedFile The encrypted file to upload.
* @param fileName The original file name.
* @param fileType The file type (e.g., "PDF", "JPEG").
* @param fileKey The file key (e.g., "ID_DOCUMENT", "PROOF_OF_ADDRESS_DOCUMENT").
* @param encryptedAesKey The JWE-wrapped AES key.
* @param iv The Base64-encoded initialization vector.
* @param accessToken The Bearer token for authentication.
* @throws IOException if the HTTP request fails.
*/
public static void uploadRfiFile(UUID paymentId, UUID rfiId, File encryptedFile,
                               String fileName, String fileType, String fileKey,
                               String encryptedAesKey, byte[] iv, String accessToken) throws IOException {

   OkHttpClient client = new OkHttpClient();
   ObjectMapper objectMapper = new ObjectMapper();

   // Create data objects
   FileMetadata fileMetadata = new FileMetadata(fileName, fileType, fileKey);
   FileEncryption encryption = new FileEncryption(encryptedAesKey, Base64.getEncoder().encodeToString(iv));

   // Build multipart request
   RequestBody requestBody = new MultipartBody.Builder()
       .setType(MultipartBody.FORM)
       .addFormDataPart("fileMetadata", objectMapper.writeValueAsString(fileMetadata))
       .addFormDataPart("encryption", objectMapper.writeValueAsString(encryption))
       .addFormDataPart("encryptedFile", fileName,
           RequestBody.create(encryptedFile, MediaType.parse("application/octet-stream")))
       .build();

   Request request = new Request.Builder()
       .url(String.format("https://api.circle.com/v1/payments/%s/rfis/%s/files", paymentId, rfiId))
       .header("Authorization", "Bearer " + accessToken)
       .post(requestBody)
       .build();

   try (Response response = client.newCall(request).execute()) {
       if (!response.isSuccessful()) {
           throw new IOException("Upload failed: " + response.code());
       }
   }
}

public static class FileMetadata {
   public final String fileName;
   public final String fileType;
   public final String fileKey;

   public FileMetadata(String fileName, String fileType, String fileKey) {
       this.fileName = fileName;
       this.fileType = fileType;
       this.fileKey = fileKey;
   }
}

public static class FileEncryption {
   public final String encryptedAesKey;
   public final String iv;

   public FileEncryption(String encryptedAesKey, String iv) {
       this.encryptedAesKey = encryptedAesKey;
       this.iv = iv;
   }
}

An example request body is shown below:

Text
------WebKitFormBoundary
Content-Disposition: form-data; name="fileMetadata"
Content-Type: application/json

{
  "fileName": "example.pdf",
  "fileType": "application/pdf",
  "fileKey": "PROOF_OF_ADDRESS"
}
------WebKitFormBoundary
Content-Disposition: form-data; name="encryption"
Content-Type: application/json

{
  "encryptedAesKey": "<base64-encoded-encrypted-aes-key>",
  "iv": "<base64-encoded-iv-for-file-encryption>",
}
------WebKitFormBoundary
Content-Disposition: form-data; name="encryptedFile"; filename="encrypted_data.bin"
Content-Type: application/octet-stream

[AES ENCRYPTED BINARY FILE DATA]
Did this page help you?
© 2023-2025 Circle Technology Services, LLC. All rights reserved.