Encrypting transcripts and operator results
Twilio Editions feature
Conversational Intelligence Encryption is available to Twilio Enterprise Edition and Twilio Security Edition customers. Learn more about Editions.
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.
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.
- Twilio generates a random Content Encryption Key (CEK) for each transcript and operator results.
- Twilio encrypts the content with the generated CEK using the AES256-GCM cipher.
- Twilio encrypts the CEK with your public key using the RSAES-OAEP-SHA256-MGF1 cipher.
- You retrieve the CEK and Initial Vector (IV) encryption values for the transcript and operator results.
- You decrypt the CEK using your private key.
- You decrypt the transcript and operator results content using the CEK, along with the IV encryption value.
See Decrypting transcripts and operator results for detailed steps.
Follow these steps to create an RSA key pair with OpenSSL and activate Conversational Intelligence Encryption for an existing service.
-
Generate an RSA private key:
openssl genrsa -out private_key.pem 2048 -
Extract the public key:
openssl rsa -in private_key.pem -pubout -out public_key.pem -
Optional: Convert the private key to PKCS #8 format if your project requires it:
openssl pkcs8 -in private_key.pem -topk8 -nocrypt -out private_key_pkcs8.pem -
Copy the contents of
public_key.pem
to your clipboard:macOSWindowspbcopy < public_key.pem -
Register the public key with Twilio using the Twilio Console or the Public Key Resource API. To register your public key in the Twilio Console, follow these steps:
- Go to Account Management > Credentials.
- Click Create Credentials.
- Enter a name in the Friendly name box.
- Paste your public key into the Public key box.
- 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.
- Turn Conversational Intelligence Encryption on for your service using the Service Resource API or the Twilio Console. To use the Twilio Console, follow these steps:
- Go to Conversational Intelligence > Intelligence Services.
- Click the name of your service.
- Click Settings.
- Select your encryption public key.
- Click Save.
- Fetch the Encrypted Transcript Sentences resource for the transcript or the Encrypted OperatorResult resource for operator results. If the request succeeds, the response contains a
location
property with one URL (transcripts) or alocations
property with an array of URLs (operator results). - Download the URL or URLs in the
location
orlocations
property, recording thex-amz-meta-x-amz-iv
HTTP header and thex-amz-meta-x-amz-key
HTTP header.x-amz-meta-x-amz-iv
is an initialization vector andx-amz-meta-x-amz-key
is the encrypted one-time symmetric key. - Decrypt your file or files using the following script.
1#!/usr/bin/env python32"""3Performs RSA-OAEP decryption of AES key, then AES-GCM decryption of payload.4"""56import base647import os8from pathlib import Path9from typing import Union1011from cryptography.hazmat.primitives import hashes, serialization12from cryptography.hazmat.primitives.asymmetric import padding13from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes14from cryptography.hazmat.backends import default_backend15from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey161718class DecryptScript:19"""20CHANGE THESE VALUES TO YOURS21"""2223ENCRYPTED_KEY_B64 = "THIS_IS_THE_KEY_RETURNED_IN_THE_S3_HEADERS"24IV_B64 = "THIS_IS_THE_IV_RETURNED_IN_THE_S3_HEADERS"25CIPHERTEXT_FILE = "/path/to/encrypted/file"26PRIVATE_KEY_FILE = "/path/to/your/private_key.pem"2728@staticmethod29def main() -> None:30"""Main decryption process"""31try:32# Decode base64 values33encrypted_key = base64.b64decode(DecryptScript.ENCRYPTED_KEY_B64)34iv = base64.b64decode(DecryptScript.IV_B64)3536# Read encrypted file37cipher_plus_tag = Path(DecryptScript.CIPHERTEXT_FILE).read_bytes()3839# Read private key40private_key_string = Path(DecryptScript.PRIVATE_KEY_FILE).read_text()4142# 2. RSA-decrypt (unwrap) the AES data key43rsa_private_key = DecryptScript._build_encryption_key_pair(private_key_string)4445# RSA/ECB/OAEPWithSHA-256AndMGF1Padding equivalent46raw_aes_key = rsa_private_key.decrypt(47encrypted_key,48padding.OAEP(49mgf=padding.MGF1(algorithm=hashes.SHA256()),50algorithm=hashes.SHA256(),51label=None52)53)5455# 3. AES/GCM decrypt the payload56# In AES-GCM, the authentication tag is typically appended to the ciphertext57# We need to separate the ciphertext from the tag (last 16 bytes)58ciphertext = cipher_plus_tag[:-16]59tag = cipher_plus_tag[-16:]6061# Create AES-GCM cipher62cipher = Cipher(63algorithms.AES(raw_aes_key),64modes.GCM(iv, tag),65backend=default_backend()66)67decryptor = cipher.decryptor()6869# Decrypt the payload70plaintext = decryptor.update(ciphertext) + decryptor.finalize()7172# 4. Persist plaintext to disk73out_file = DecryptScript.CIPHERTEXT_FILE + ".json"74DecryptScript._write_file(out_file, plaintext)75print(f"Decrypted data written to: {out_file}")7677except Exception as e:78print(f"Decryption failed: {e}")79raise8081@staticmethod82def _build_encryption_key_pair(private_key_string: str) -> RSAPrivateKey:83"""84Parse private key from PEM string85"""86try:87private_key = serialization.load_pem_private_key(88private_key_string.encode('utf-8'),89password=None,90backend=default_backend()91)92return private_key93except Exception as ex:94raise ValueError(f"Invalid private key: {ex}")9596@staticmethod97def _write_file(path: Union[str, Path], data: bytes) -> None:98"""99Write bytes to a file, creating or replacing it.100"""101Path(path).write_bytes(data)102103104if __name__ == "__main__":105DecryptScript.main()