Skip to contentSkip to navigationSkip to topbar
Page toolsOn this page

Encrypting transcripts and operator results


(information)

Twilio Editions feature

Conversational Intelligence Encryption is available to Twilio Enterprise Edition and Twilio Security Edition customers. Learn more about Editions(link takes you to an external page).


How Conversational Intelligence Encryption works

how-conversational-intelligence-encryption-works page anchor

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(link takes you to an external page).

(warning)

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

encryption-process page anchor
  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(link takes you to an external page) cipher.
  3. Twilio encrypts the CEK with your public key using the RSAES-OAEP-SHA256-MGF1 cipher.
  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 for detailed steps.


Turn on Conversational Intelligence Encryption

turn-on-conversational-intelligence-encryption page anchor

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:

    openssl genrsa -out private_key.pem 2048
  2. Extract the public key:

    openssl rsa -in private_key.pem -pubout -out public_key.pem
  3. Optional: Convert the private key to PKCS #8(link takes you to an external page) format if your project requires it:

    openssl pkcs8 -in private_key.pem -topk8 -nocrypt -out private_key_pkcs8.pem
  4. Copy the contents of public_key.pem to your clipboard:

    macOSWindows
    pbcopy < public_key.pem
  5. 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:

    1. Go to Account Management > Credentials(link takes you to an external page).
    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)

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.

  1. 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:
    1. Go to Conversational Intelligence > Intelligence Services(link takes you to an external page).
    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 page anchor
  1. 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 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 filesLink to code sample: Decrypt Conversational Intelligence files
    1
    #!/usr/bin/env python3
    2
    """
    3
    Performs RSA-OAEP decryption of AES key, then AES-GCM decryption of payload.
    4
    """
    5
    6
    import base64
    7
    import os
    8
    from pathlib import Path
    9
    from typing import Union
    10
    11
    from cryptography.hazmat.primitives import hashes, serialization
    12
    from cryptography.hazmat.primitives.asymmetric import padding
    13
    from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
    14
    from cryptography.hazmat.backends import default_backend
    15
    from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
    16
    17
    18
    class DecryptScript:
    19
    """
    20
    CHANGE THESE VALUES TO YOURS
    21
    """
    22
    23
    ENCRYPTED_KEY_B64 = "THIS_IS_THE_KEY_RETURNED_IN_THE_S3_HEADERS"
    24
    IV_B64 = "THIS_IS_THE_IV_RETURNED_IN_THE_S3_HEADERS"
    25
    CIPHERTEXT_FILE = "/path/to/encrypted/file"
    26
    PRIVATE_KEY_FILE = "/path/to/your/private_key.pem"
    27
    28
    @staticmethod
    29
    def main() -> None:
    30
    """Main decryption process"""
    31
    try:
    32
    # Decode base64 values
    33
    encrypted_key = base64.b64decode(DecryptScript.ENCRYPTED_KEY_B64)
    34
    iv = base64.b64decode(DecryptScript.IV_B64)
    35
    36
    # Read encrypted file
    37
    cipher_plus_tag = Path(DecryptScript.CIPHERTEXT_FILE).read_bytes()
    38
    39
    # Read private key
    40
    private_key_string = Path(DecryptScript.PRIVATE_KEY_FILE).read_text()
    41
    42
    # 2. RSA-decrypt (unwrap) the AES data key
    43
    rsa_private_key = DecryptScript._build_encryption_key_pair(private_key_string)
    44
    45
    # RSA/ECB/OAEPWithSHA-256AndMGF1Padding equivalent
    46
    raw_aes_key = rsa_private_key.decrypt(
    47
    encrypted_key,
    48
    padding.OAEP(
    49
    mgf=padding.MGF1(algorithm=hashes.SHA256()),
    50
    algorithm=hashes.SHA256(),
    51
    label=None
    52
    )
    53
    )
    54
    55
    # 3. AES/GCM decrypt the payload
    56
    # In AES-GCM, the authentication tag is typically appended to the ciphertext
    57
    # We need to separate the ciphertext from the tag (last 16 bytes)
    58
    ciphertext = cipher_plus_tag[:-16]
    59
    tag = cipher_plus_tag[-16:]
    60
    61
    # Create AES-GCM cipher
    62
    cipher = Cipher(
    63
    algorithms.AES(raw_aes_key),
    64
    modes.GCM(iv, tag),
    65
    backend=default_backend()
    66
    )
    67
    decryptor = cipher.decryptor()
    68
    69
    # Decrypt the payload
    70
    plaintext = decryptor.update(ciphertext) + decryptor.finalize()
    71
    72
    # 4. Persist plaintext to disk
    73
    out_file = DecryptScript.CIPHERTEXT_FILE + ".json"
    74
    DecryptScript._write_file(out_file, plaintext)
    75
    print(f"Decrypted data written to: {out_file}")
    76
    77
    except Exception as e:
    78
    print(f"Decryption failed: {e}")
    79
    raise
    80
    81
    @staticmethod
    82
    def _build_encryption_key_pair(private_key_string: str) -> RSAPrivateKey:
    83
    """
    84
    Parse private key from PEM string
    85
    """
    86
    try:
    87
    private_key = serialization.load_pem_private_key(
    88
    private_key_string.encode('utf-8'),
    89
    password=None,
    90
    backend=default_backend()
    91
    )
    92
    return private_key
    93
    except Exception as ex:
    94
    raise ValueError(f"Invalid private key: {ex}")
    95
    96
    @staticmethod
    97
    def _write_file(path: Union[str, Path], data: bytes) -> None:
    98
    """
    99
    Write bytes to a file, creating or replacing it.
    100
    """
    101
    Path(path).write_bytes(data)
    102
    103
    104
    if __name__ == "__main__":
    105
    DecryptScript.main()