# Encrypting transcripts and operator results

> \[!NOTE]
>
> Conversational Intelligence Encryption is available to Twilio Enterprise Edition and Twilio Security Edition customers. Learn more about [Editions](https://www.twilio.com/en-us/editions).

## How Conversational Intelligence Encryption works

By default, Twilio encrypts all transcripts and operator results at rest. To encrypt your transcripts and operator results with your public key, activate Conversational Intelligence Encryption. With this feature turned on, Twilio encrypts transcripts and operator results with your key as soon as they're generated. The data remains encrypted, so only the corresponding private key holder can decrypt it. The feature uses [hybrid encryption](https://en.wikipedia.org/wiki/Hybrid_cryptosystem).

> \[!WARNING]
>
> Keep your private key secure. Without it, you can't decrypt files that are encrypted with the corresponding public key. Twilio Support can't decrypt content protected by Conversational Intelligence Encryption.
>
> Once public key encryption is enabled, you will be responsible for fulfilling data subject rights requests under applicable law because Twilio will no longer have access to personal information included in the encrypted transcripts and operator results.

### Encryption process

1. Twilio generates a random Content Encryption Key (CEK) for each transcript and operator results.
2. Twilio encrypts the content with the generated CEK using the [AES256-GCM](https://en.wikipedia.org/wiki/Galois/Counter_Mode) cipher.
3. Twilio encrypts the CEK with your public key using the RSAES-OAEP-SHA256-MGF1 cipher.

### Decryption process

1. You retrieve the CEK and Initial Vector (IV) encryption values for the transcript and operator results.
2. You decrypt the CEK using your private key.
3. You decrypt the transcript and operator results content using the CEK, along with the IV encryption value.

See [Decrypting transcripts and operator results](#decrypt) for detailed steps.

## Turn on Conversational Intelligence Encryption

Follow these steps to create an RSA key pair with OpenSSL and activate Conversational Intelligence Encryption for an existing service.

1. Generate an RSA private key:
   ```bash
   openssl genrsa -out private_key.pem 2048
   ```
2. Extract the public key:
   ```bash
   openssl rsa -in private_key.pem -pubout -out public_key.pem
   ```
3. Optional: Convert the private key to [PKCS #8](https://en.wikipedia.org/wiki/PKCS_8) format if your project requires it:
   ```bash
   openssl pkcs8 -in private_key.pem -topk8 -nocrypt -out private_key_pkcs8.pem
   ```
4. Copy the contents of `public_key.pem` to your clipboard:

   ## macOS

   ```bash
     pbcopy < public_key.pem
   ```

   ## Windows

   ```bash
     clip < public_key.pem
   ```
5. Register the public key with Twilio using the Twilio Console or the [Public Key Resource API](/docs/iam/credentialpublickey-resource). To register your public key in the Twilio Console, follow these steps:
   1. Go to **Account Management** > [**Credentials**](https://console.twilio.com/us1/account/keys-credentials/credentials).
   2. Click **Create Credentials**.
   3. Enter a name in the **Friendly name** box.
   4. Paste your public key into the **Public key** box.
   5. Click **Create credentials**.

> \[!WARNING]
>
> If data use (also called data logging) is on for your service, Twilio stores a copy of the transcript and operator results that is not encrypted with the public key. We use this copy to improve the quality of speech recognition and language understanding services.

6. Turn Conversational Intelligence Encryption on for your service using the [Service Resource API](/docs/conversational-intelligence/api/service-resource#update-a-service) or the Twilio Console. To use the Twilio Console, follow these steps:
   1. Go to **Conversational Intelligence** > [**Intelligence Services**](https://console.twilio.com/us1/develop/conversational-intelligence/services).
   2. Click the name of your service.
   3. Click **Settings**.
   4. Select your encryption public key.
   5. Click **Save**.

## Decrypting transcripts and operator results \[#decrypt]

1. Fetch the [Encrypted Transcript Sentences resource](/docs/conversational-intelligence/api/transcript-sentence-subresource#retrieve-encrypted-transcript-sentences) for the transcript or the [Encrypted OperatorResult resource](/docs/conversational-intelligence/api/transcript-operator-results-subresource#retrieve-encrypted-operatorresults) for operator results. If the request succeeds, the response contains a `location` property with one URL (transcripts) or a `locations` property with an array of URLs (operator results).
2. Download the URL or URLs in the `location` or `locations` property, recording the
   `x-amz-meta-x-amz-iv` HTTP header and the `x-amz-meta-x-amz-key` HTTP header. `x-amz-meta-x-amz-iv` is an initialization vector and `x-amz-meta-x-amz-key` is the encrypted one-time symmetric key.
3. Decrypt your file or files using the following script.

   Decrypt Conversational Intelligence files
   ```py
   #!/usr/bin/env python3
   """
   Performs RSA-OAEP decryption of AES key, then AES-GCM decryption of payload.
   """

   import base64
   import os
   from pathlib import Path
   from typing import Union

   from cryptography.hazmat.primitives import hashes, serialization
   from cryptography.hazmat.primitives.asymmetric import padding
   from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
   from cryptography.hazmat.backends import default_backend
   from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey


   class DecryptScript:
       """
       CHANGE THESE VALUES TO YOURS
       """

       ENCRYPTED_KEY_B64 = "THIS_IS_THE_KEY_RETURNED_IN_THE_S3_HEADERS"
       IV_B64 = "THIS_IS_THE_IV_RETURNED_IN_THE_S3_HEADERS"
       CIPHERTEXT_FILE = "/path/to/encrypted/file"
       PRIVATE_KEY_FILE = "/path/to/your/private_key.pem"

       @staticmethod
       def main() -> None:
           """Main decryption process"""
           try:
               # Decode base64 values
               encrypted_key = base64.b64decode(DecryptScript.ENCRYPTED_KEY_B64)
               iv = base64.b64decode(DecryptScript.IV_B64)

               # Read encrypted file
               cipher_plus_tag = Path(DecryptScript.CIPHERTEXT_FILE).read_bytes()

               # Read private key
               private_key_string = Path(DecryptScript.PRIVATE_KEY_FILE).read_text()

               # 2. RSA-decrypt (unwrap) the AES data key
               rsa_private_key = DecryptScript._build_encryption_key_pair(private_key_string)

               # RSA/ECB/OAEPWithSHA-256AndMGF1Padding equivalent
               raw_aes_key = rsa_private_key.decrypt(
                   encrypted_key,
                   padding.OAEP(
                       mgf=padding.MGF1(algorithm=hashes.SHA256()),
                       algorithm=hashes.SHA256(),
                       label=None
                   )
               )

               # 3. AES/GCM decrypt the payload
               # In AES-GCM, the authentication tag is typically appended to the ciphertext
               # We need to separate the ciphertext from the tag (last 16 bytes)
               ciphertext = cipher_plus_tag[:-16]
               tag = cipher_plus_tag[-16:]

               # Create AES-GCM cipher
               cipher = Cipher(
                   algorithms.AES(raw_aes_key),
                   modes.GCM(iv, tag),
                   backend=default_backend()
               )
               decryptor = cipher.decryptor()

               # Decrypt the payload
               plaintext = decryptor.update(ciphertext) + decryptor.finalize()

               # 4. Persist plaintext to disk
               out_file = DecryptScript.CIPHERTEXT_FILE + ".json"
               DecryptScript._write_file(out_file, plaintext)
               print(f"Decrypted data written to: {out_file}")

           except Exception as e:
               print(f"Decryption failed: {e}")
               raise

       @staticmethod
       def _build_encryption_key_pair(private_key_string: str) -> RSAPrivateKey:
           """
           Parse private key from PEM string
           """
           try:
               private_key = serialization.load_pem_private_key(
                   private_key_string.encode('utf-8'),
                   password=None,
                   backend=default_backend()
               )
               return private_key
           except Exception as ex:
               raise ValueError(f"Invalid private key: {ex}")

       @staticmethod
       def _write_file(path: Union[str, Path], data: bytes) -> None:
           """
           Write bytes to a file, creating or replacing it.
           """
           Path(path).write_bytes(data)


   if __name__ == "__main__":
       DecryptScript.main()
   ```
   ```java
   import java.io.StringReader;
   import java.nio.file.Files;
   import java.nio.file.Path;
   import java.security.KeyPair;
   import java.security.PrivateKey;
   import java.security.spec.MGF1ParameterSpec;
   import java.util.Base64;
   import javax.crypto.Cipher;
   import javax.crypto.SecretKey;
   import javax.crypto.spec.GCMParameterSpec;
   import javax.crypto.spec.OAEPParameterSpec;
   import javax.crypto.spec.PSource;
   import javax.crypto.spec.SecretKeySpec;

   import org.bouncycastle.openssl.PEMKeyPair;
   import org.bouncycastle.openssl.PEMParser;
   import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;

   public class DecryptScript {

       /*
        * CHANGE THESE VALUES TO YOURS
        */

       private static final String ENCRYPTED_KEY_B64 = "THIS_IS_THE_KEY_RETURNED_IN_THE_S3_HEADERS";
       private static final String IV_B64 = "THIS_IS_THE_IV_RETURNED_IN_THE_S3_HEADERS";
       private static final String CIPHERTEXT_FILE = "/path/to/encrypted/file";
       private static final String PRIVATE_KEY_FILE = "/path/to/private/key.pem";

       public static void main(final String[] args) throws Exception {
           // Load BC
           Security.addProvider(new BouncyCastleProvider());

           final byte[] encryptedKey = Base64.getDecoder().decode(ENCRYPTED_KEY_B64);
           final byte[] iv = Base64.getDecoder().decode(IV_B64);
           final byte[] cipherPlusTag = Files.readAllBytes(Path.of(CIPHERTEXT_FILE));

           final String privateKeyString = Files.readString(Path.of(PRIVATE_KEY_FILE));

           /* 2. RSA-decrypt (unwrap) the AES data key */
           final PrivateKey rsaPriv = buildEncryptionKeyPair(privateKeyString);

           final Cipher rsa = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
           final OAEPParameterSpec oaep = new OAEPParameterSpec(
               "SHA-256",
               "MGF1",
               MGF1ParameterSpec.SHA256,
               PSource.PSpecified.DEFAULT);
           rsa.init(Cipher.DECRYPT_MODE, rsaPriv, oaep);

           final byte[] rawAesKey = rsa.doFinal(encryptedKey);
           final SecretKey aesKey = new SecretKeySpec(rawAesKey, "AES");

           /* 3. AES/GCM decrypt the payload */
           final Cipher gcm = Cipher.getInstance("AES/GCM/NoPadding");
           final GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv);
           gcm.init(Cipher.DECRYPT_MODE, aesKey, gcmSpec);

           final byte[] plaintext = gcm.doFinal(cipherPlusTag);

           /* 4. Persist plaintext to disk */
           final String outFile = CIPHERTEXT_FILE + ".json";
           writeFile(outFile, plaintext);
           System.out.println("Decrypted data written to: " + outFile);
       }

       private static PrivateKey buildEncryptionKeyPair(final String privateKey) {
           try (PEMParser pemParser = new PEMParser(new StringReader(privateKey))) {
               JcaPEMKeyConverter converter = (new JcaPEMKeyConverter()).setProvider("BC");
               PrivateKeyInfo privateKeyInfo = (PrivateKeyInfo) pemParser.readObject();
               return converter.getPrivateKey(privateKeyInfo);
           } catch (Exception e) {
               throw new IllegalArgumentException("invalid private key", e);
           }
       }

       /**
        * Write bytes to a file, creating or replacing it.
        */
       private static void writeFile(final String path, final byte[] data) throws Exception {
           Files.write(Path.of(path), data);
       }
   }
   ```

## Additional resources

* [CredentialPublicKey Resource](/docs/iam/credentialpublickey-resource)
