Protege la entrada de Twilio Voice con cifrado y redacción

Protect Twilio Voice Input with Encryption and Redaction
February 26, 2024
Redactado por
Revisado por
Paul Kamp
Twilion

Protege la entrada de Twilio Voice con cifrado y redacción

¿Estás haciendo todo lo posible para proteger la información confidencial que te confían los agentes de llamada?

A medida que las organizaciones aprovechan más información confidencial, proteger esos datos es más importante que nunca. Twilio ofrece numerosas formas de proteger tus datos confidenciales, pero depende de ti implementar los recursos que Twilio proporciona de manera responsable.

En este artículo, aprende a cifrar y anonimizar los datos recopilados de Twilio Programmable Voice, usando <Gather> TwiML con Twilio Serverless Functions y Voice PCI Mode.

Lo que necesitarás

Para realizar este tutorial, necesitarás lo siguiente:

¿Qué estás creando?

Crearás una aplicación móvil de voz interactiva simple para manejar la autenticación de agentes de llamada. Se utilizará una función para solicitar al agente de llamada la siguiente información confidencial a través de <Gather> TwiML.

  1. "Por favor ingresa tu PIN de 4 dígitos"

  2. "Por favor ingresa los últimos 4 dígitos de tu número de tarjeta de pago"

Tan pronto como se reciba esta información del agente de llamada, se la cifrará. A partir de ese momento, los datos permanecerán cifrados hasta que lleguen a su destino.

En una implementación real, el destino probablemente sería tu servicio de actividad en segundo plano para el procesamiento. Sin embargo, aquí, otra función actuará como una “API ficticia” para demostrar cómo se realizaría el descifrado.

También habilitarás Voice PCI Mode para anonimizar la información recopilada en los registros de llamadas de Voice.

El antes

Antes de saltar a la solución, echa un vistazo a cómo se verían tus registros sin cifrado o anonimización.

Twilio Functions registrará cualquier error generado desde una función en tu Twilio Debugger. En esta situación de ejemplo, registrarás un error si no se introducen ciertos dígitos específicos. Puedes ver los parámetros de solicitud en texto sin formato en el error recibido por el Debugger.

Programmable Voice también registrará los dígitos recolectados en texto sin formato en el registro de llamadas de Voice:

Puedes encontrar esta información si tienes acceso a los registros de llamadas o al Debugger.

El después

Los datos visibles después de implementar esta solución son menos vulnerables. Al final, tu registro de funciones mostrará valores más seguros y cifrados:

Y tu registro de llamadas mostrará *REDACTED*:

Comenzar

Twilio Functions

Para seguir estas instrucciones, utiliza el Editor de funciones de la consola de Twilio.

Los desarrolladores avanzados deben considerar usar la CLI sin servidor más robusta para crear, implementar y mantener Functions.

Crea un servicio

Las funciones se crean y contienen dentro de Servicios:

  1. Inicia sesión en la consola de Twilio y navega a la pestaña Functions.

  2. Crea un Servicio haciendo clic en el botón Create Service (Crear servicio) y añade un nombre como encrypted-gather-sample.

Agrega una dependencia

En esta solución, la biblioteca de axios se utiliza para realizar una solicitud a tu servicio de actividad en segundo plano “imaginario” (la función decrypt-gather) para su procesamiento.

Agrega axios como una dependencia a tu Servicio.

Crea una variable de entorno

Esta solución requiere una clave secreta, que se utilizará para cifrar y descifrar los datos confidenciales.

Tu cadena de claves secreta debe tener al menos 32 bytes de longitud. Mantén este secreto en privado.

Para crear un secreto aleatorio, se puede usar la siguiente línea de comandos con Mac/Linux:

xxd -l32 -p /dev/urandom

Como alternativa, este secreto se puede generar a través de Node.js:

crypto.randomBytes(32).toString('hex')

 

Agrega una variable de entorno dentro de tu servicio que almacene tu clave.

Para fines de prueba, se puede utilizar la siguiente clave secreta de 32 bytes.

a154eb4c759711bc2538a7cc021e9e9f17dd8aa63151c62ca28a82a4a404203d

Crea la función de cifrado AES

Primero, crea una función para manejar el cifrado y descifrado de datos usando criptografía de clave simétrica.

Crypto de Node.js

Node.js ofrece un módulo de criptografía integrado llamado Crypto. Crypto proporciona varios métodos útiles, como createCipheriv() y createDecipheriv(), que nos permiten especificar qué tipo de algoritmo de cifrado de bloques emplear.

Cifrado de bloque de GCM

Advanced Encryption Standard, conocido como AES, es una técnica para proteger los datos utilizando algoritmos de cifrado. AES se puede lograr a través de una variedad de modos de operaciones.

En esta solución, usarás GCM, Galois/Counter Mode, un cifrado de bloque criptográfico de clave simétrica que se prefiere por su velocidad y fuerza.

Código

Crea una nueva función llamada AES con el siguiente código.

const crypto = require("crypto")

const ALGORITHM = {
    BLOCK_CIPHER: "aes-256-gcm",
    AUTH_TAG_BYTE_SIZE: 16, 
    IV_BYTE_SIZE: 12,  
}

exports.encrypt = (plainText, key) => {
    const nonce = crypto.randomBytes(ALGORITHM.IV_BYTE_SIZE)
    const cipher = crypto.createCipheriv(
        ALGORITHM.BLOCK_CIPHER, 
        Buffer.from(key, 'hex'), 
        nonce, 
        {
            authTagLength: ALGORITHM.AUTH_TAG_BYTE_SIZE
        }
    )

    const cipherText = Buffer.concat([
        nonce,
        cipher.update(plainText),
        cipher.final(),
        cipher.getAuthTag()
    ])

    return cipherText.toString('hex')
}

exports.decrypt = (cipherText, key) => {
    cipherText = Buffer.from(cipherText, 'hex')

    const authTag = cipherText.slice(-16)
    const nonce = cipherText.slice(0, 12)
    const encryptedMessage = cipherText.slice(12, -16)

    const decipher = crypto.createDecipheriv(
        ALGORITHM.BLOCK_CIPHER, 
        Buffer.from(key), 
        nonce, 
        {
            authTagLength: ALGORITHM.AUTH_TAG_BYTE_SIZE
        }
    )

    decipher.setAuthTag(authTag)
    const decrypted = decipher.update(encryptedMessage, '', 'utf8') + decipher.final('utf8')      
    return decrypted 
}

Esta función debe ajustarse a una visibilidad de "Privado", ya que solo se utilizará desde dentro de otra función en el mismo servicio.

Crea la función encrypted-gather

A continuación, crea la función que realizará las operaciones sensibles de <Gather>. Esta función se configurará como el webhook de voz de número de teléfono entrante en un paso posterior.

A partir de esta función, los dígitos ingresados por el agente de llamada se cifrarán tan pronto como se reciban y se enviarán en su estado cifrado a la función final, “destino”.

Código

Crea una nueva función llamada encrypted-gather con el siguiente código:

const axios = require('axios')
const AES = require(Runtime.getFunctions()['AES'].path)

exports.handler = async function (context, event, callback) {
    const twiml = new Twilio.twiml.VoiceResponse()

    const secret_key = context.AES_SECRET

    const functionUrl = `https://${context.DOMAIN_NAME}/encrypted-gather`
    const dummyApi = `https://${context.DOMAIN_NAME}/decrypt-gather`

    const step = event.step || "getLast4CC"

    switch (step) {
        case ("getLast4CC"):
            gatherLast4Card(twiml, functionUrl);
            break
        case ("getPin"):
            let encryptedCardDigits = AES.encrypt(event.Digits, secret_key)
            gatherPin(twiml, encryptedCardDigits, functionUrl)
            break
        case ("processData"):
            let encryptedPinDigits = AES.encrypt(event.Digits, secret_key)
            await processGatheredData(twiml, event.encryptedCardDigits, encryptedPinDigits, dummyApi)
            break
    }

    return callback(null, twiml)
}

const gatherLast4Card = (twiml, functionUrl) => {
    const gather = twiml.gather({
        action: `${functionUrl}?step=getPin`,
        method: 'POST',
        input: 'dtmf',
        timeout: 10,
        numDigits: 4,
    });
    gather.say('Please enter last 4 digits of your payment card number.');

    return gather
}

const gatherPin = (twiml, encryptedCardDigits, functionUrl) => {
    const gather = twiml.gather({
        action: `${functionUrl}?step=processData&encryptedCardDigits=${encryptedCardDigits}`,
        method: 'POST',
        input: 'dtmf',
        timeout: 10,
        numDigits: 4,
    });
    gather.say('Please enter your unique 4 digit identification number');

    return gather
}

const processGatheredData = async (twiml, encryptedCardDigits, encryptedPinDigits, dummy_url) => {
    // make request to "dummy" api endpoint - example decrypt function
    try {
        const apiResponse = await axios({
            method: 'post',
            url: dummy_url,
            data: {
                encryptedCardDigits, encryptedPinDigits
            }
        })

        twiml.say(`Thank you. Your account number is ${apiResponse.data.account} and your balance is ${apiResponse.data.balance}`)
    }
    catch (e) {
        twiml.say(`We were not able to locate you in our system. Goodbye.`)
    }

    return twiml
}

Esta función debe establecerse como "Protegida", ya que será llamada desde dentro de Twilio y puede ser asegurada con el encabezado X-Twilio-Signature.

Cuando implementes esta solución en producción, deberás cambiar la variable de descifrado “dummyApi” a la URL de tu servicio de actividad en segundo plano.

const dummyApi = `https://${context.DOMAIN_NAME}/decrypt-gather`

¿Cómo se cifra?

En la parte superior, importas las funciones creadas en el paso anterior con la siguiente línea:

const AES = require(Runtime.getFunctions()['AES'].path)

Entonces, defines tu secreto obteniéndolo de la variable de entorno:

const secret_key = context.AES_SECRET

Y, lo más importante, cualquier información confidencial se incluye con la función de cifrado. (En este caso, la información de <Gather> se pasa como el parámetro Digit y se puede acceder desde el objeto de evento.)

let encryptedCardDigits = AES.encrypt(event.Digits, secret_key)

Esto maneja el cifrado de la información recopilada.

Crea la función decrypt-gather

Finalmente, vamos a crear una función para demostrar cómo descifrar los datos confidenciales.

En un entorno de producción, esto probablemente sería una solicitud a tu servicio de actividad en segundo plano que procesa la información del agente de llamada en función de las necesidades de tu negocio.

En esta solución, una tercera función actuará como el “servicio de actividad en segundo plano” que procesa estos datos. Esta función recibirá los dígitos cifrados y los descifrará para su posterior procesamiento.

Código

Crea una nueva función llamada decrypt-gather con el siguiente código:

const AES = require(Runtime.getFunctions()['AES'].path) 

exports.handler = function(context, event, callback) { 
const response = new Twilio.Response() 
const secret_key = context.AES_SECRET 

const last4card = AES.decrypt(event.encryptedCardDigits, secret_key) 
const pin = AES.decrypt(event.encryptedPinDigits, secret_key) 

//hard-coded values used for testing purposes 
if (last4card === "1234" && pin === "4321") { 
response.setBody(JSON.stringify({ 
account: "AC12345678", 
balance: "12.55"
 })) 
} else { 
response.setStatusCode(404) 
response.setBody("No data found") 
} 

return callback(null, response) 
}

La visibilidad de esta función será "Pública", ya que pretende ser un servicio externo.

¿Cómo se descifra?

En la parte superior, vuelves a importar las funciones de AES y defines secret_key como una variable.

Luego llamas a decrypt en la información que fue encriptada anteriormente:

const last4card = AES.decrypt(event.encryptedCardDigits, secret_key)

Configuración adicional

Webhook de número de teléfono

Para simplificar, conecta esta función directamente a un número de teléfono.

Para configurar el número de teléfono:

  1. Desde Twilio Console (Consola de Twilio), ve a la sección Phone Numbers (Números de teléfono)

  2. Selecciona tu número de teléfono y, a continuación, desplázate hasta la sección Voice & Fax (Voz y fax)

  3. Establece la función encrypted-gather como el webhook A call comes in (Una llamada ingresa) en Voice Configuration (Configuración de voz)

  4. Guarda los cambios

Si esperas activar esto desde Twilio Studio, echa un vistazo a esta entrada de blog para obtener más información sobre cómo incorporar esta solución de forma segura con Studio.

Activar el modo PCI

¡Ya casi terminamos! Aseguraste las funciones, pero todavía hay un área más donde Twilio retiene los dígitos reunidos en texto sin formato: registros de llamadas de Voice.

A continuación, se muestra una captura de pantalla de la Consola de Twilio para una llamada entrante con la solución <Gather> cifrada implementada. A pesar de que Functions aseguró los datos, Voice no lo ha hecho.

Solo hay una manera de evitar que estos datos se muestren en el registro de llamadas, y eso es con el modo PCI. La habilitación del modo PCI en tu cuenta anonimizará todos los datos capturados de cualquier operación de <Gather>.

 

La habilitación del modo PCI en una cuenta no tiene retorno. Una vez que esté activada, no podrás desactivarla. La anonimización puede hacer que la solución de problemas de Voice sea más difícil.

Si realmente quieres capturar información confidencial de forma segura…

  1. Dirígete a la Configuración de Voice de Twilio en la Consola de Twilio. (En el panel de navegación izquierdo, haz clic en Voice > Settings > General [Voice > Configuración > General].)

  2. Haz clic en el botón Enable PCI Mode (Habilitar el modo PCI).

  3. Guarda los cambios.

Haz una llamada

Ahora es el momento de la verdad, es hora de realizar una llamada de prueba al número de teléfono.

A partir de aquí, hay dos caminos.

Si ingresas 1234 como los últimos 4 dígitos de tu “tarjeta de crédito” y 4321 como el PIN único, escucharás información de cuenta “ficticia” devuelta en la llamada. Este es un ejemplo de una respuesta de API exitosa.

Si ingresas otros dígitos, se comportará como si no fuera un usuario conocido y devolverá una respuesta 404. Este es un ejemplo de una solicitud fallida, que registrará un error en el Debugger de Twilio.

¿Cómo sé que funcionó?

Sigue el camino fallido y echa un vistazo a tu registro de errores en la Consola de Twilio.

Para la respuesta de error 404, encontrarás un error 82005 de Functions con los siguientes detalles:

 

Esto es bueno. Sin el cifrado, una respuesta fallida habría registrado esas variables en texto sin formato. Sin embargo, ahora los datos se registrarán en su forma más segura y encriptada.

También puedes comprobar tu registro de llamadas para confirmar que los dígitos muestran *REDACTED* allí también.

¿Es esto seguro?

Seguir este tutorial (incluidos los pasos opcionales del modo PCI) evitaría que los datos se registraran en texto sin formato en cualquier lugar del ecosistema de Twilio, y evitaría que cualquier persona en Twilio pudiera descifrar tus datos confidenciales, lo que haría que esto fuera una mejora con respecto al valor predeterminado.

Sin embargo, la clave secreta utilizada para el cifrado y descifrado se almacena como una variable de entorno en el servicio, lo que significa que los usuarios a quienes se les otorga acceso a Twilio Functions podrían extraer la clave y potencialmente realizar el esfuerzo de descifrar los valores.

Recomendación final

Si estás realizando modificaciones en el código de muestra proporcionado, ten en cuenta que en Functions se conservan las advertencias y errores de la consola dentro de los sistemas internos de Twilio y en Twilio Debugger durante algún tiempo.

No uses ninguno de los siguientes métodos de registro de consola con datos confidenciales y no cifrados:

 

console.log() 
console.warn() 
console.error()

Conclusión

En esta lección, aprendiste cómo proteger los datos recopilados de <Gather> TwiML con cifrado a través de una función sin servidor y anonimización a través del modo PCI de Voice.

Si deseas cobrar pagos de tus agentes de llamada, considera la función Twilio <Pay> totalmente compatible con PCI.

Para obtener más información sobre el cumplimiento de PCI en Twilio, consulta la documentación y la matriz de responsabilidad.

Los usuarios confían en ti para mantener privada su información confidencial. Asegúrate de respetar y conservar esa confianza haciendo todo lo posible para proteger los datos que procesas.

Bry Schinina es una desarrolladora y educadora que aprecia profundamente que las empresas no expongan información privada. Trabaja como líder y gerente técnica sénior de cuentas en Twilio, resolviendo problemas complejos y ayudando a las organizaciones a tener éxito con su plataforma de participación digital. Puedes comunicarte con ella en bschinina [@] twilio.com.