Wie Sie Input in Twilio Voice mit Verschlüsselung und Unkenntlichmachung schützen

Protect Twilio Voice Input with Encryption and Redaction
February 23, 2024
Autor:in:
Prüfer:in:
Paul Kamp
Twilion

Wie Sie Input in Twilio Voice mit Verschlüsselung und Unkenntlichmachung schützen

Tun Sie wirklich alles, was Ihnen möglich ist, um die sensiblen Informationen zu schützen, die Ihnen Ihre Anrufer:innen anvertrauen?

Da Unternehmen immer mehr vertrauliche Informationen nutzen, ist die Sicherung dieser Daten wichtiger denn je. Twilio bietet Ihnen zahlreiche Möglichkeiten, Ihre sensiblen Daten zu schützen – aber es liegt bei Ihnen, die von Twilio bereitgestellten Ressourcen verantwortungsbewusst zu implementieren.

In diesem Artikel erfahren Sie, wie Sie von Twilio Programmable Voice erfasste Daten mithilfe von <Gather> TwiML mit Twilio Serverless Functions  und dem Voice PCI-Modus verschlüsseln und unkenntlich machen.

Was Sie benötigen

Für dieses Tutorial benötigen Sie Folgendes:

Was werden Sie entwickeln?

Sie entwickeln eine einfache interaktive Sprachanwendung für die Anruferauthentifizierung. Mithilfe einer Funktion werden Anrufer:innen über <Gather> TwiML zur Eingabe der folgenden vertraulichen Informationen aufgefordert.

  1. „Geben Sie bitte Ihre 4-stellige PIN ein.“

  2. „Geben Sie bitte die letzten 4 Ziffern Ihrer Kartennummer ein.“

Sobald diese Informationen von Anrufer:innen eingehen, werden sie verschlüsselt. Von diesem Moment an bleiben die Daten verschlüsselt, bis sie ihr Ziel erreichen.

In einer realen Implementierung wäre das Ziel wahrscheinlich Ihr Back-End-Service für die Verarbeitung. An dieser Stelle fungiert jedoch eine andere Funktion als „Dummy-API“, um zu zeigen, wie die Entschlüsselung durchgeführt würde.

Sie aktivieren zudem den Voice PCI-Modus, um erfasste Informationen in Sprachanrufprotokollen unkenntlich zu machen.

Vorher

Bevor Sie mit der Lösung starten, sollten Sie sich ansehen, wie Ihre Protokolle ohne Verschlüsselung oder Unkenntlichmachung aussehen würden.

Twilio Functions protokolliert alle Fehler, die von einer Funktion generiert werden, in Ihren Twilio Debugger. In diesem Beispielszenario wird ein Fehler protokolliert, wenn bestimmte Ziffern nicht eingegeben werden. In dem vom Debugger empfangenen Fehler können Sie die Anfrageparameter im Klartext sehen.

Auch Programmable Voice protokolliert die erfassten Ziffern im Sprachanrufprotokollim Klartext:

Diese Informationen finden Sie, wenn Sie Zugriff auf Anrufprotokolle oder den Debugger haben.

Nachher

Nach der Implementierung dieser Lösung sind die sichtbaren Daten weniger anfällig. Am Ende zeigt Ihr Funktionsprotokoll sicherere, verschlüsselte Werte an:

Und in Ihrem Anrufprotokoll wird *REDACTED* (*UNKENNTLICH*) angezeigt:

Erste Schritte

Twilio Functions

Um diese Anweisungen zu befolgen, verwenden Sie den Funktions-Editor der Twilio-Konsole.

Und in Ihrem Anrufprotokoll wird *REDACTED* (*UNKENNTLICH*) angezeigt:

Erfahrene Entwickler:innen sollten die robustere serverlose CLI (Command Line Interface, Kommandozeile) in Betracht ziehen, um Funktionen zu erstellen, bereitzustellen und zu verwalten.

Dienst erstellen

Funktionen werden in Services erstellt und sind darin enthalten:

  1. Melden Sie sich bei der Twilio-Konsole an und navigieren Sie zur Registerkarte Functions (Funktionen).

  2. Erstellen Sie einen Service, indem Sie auf die Schaltfläche Create Service (Service erstellen) klicken und einen Namen hinzufügen, z. B. encrypted-gather-sample.

Abhängigkeit hinzufügen

In dieser Lösung wird die axios-Bibliothek verwendet, um eine Anfrage an Ihren „vorgeblichen“ Back-End-Service (die Funktion decrypt-gather) zur Verarbeitung zu stellen.

Fügen Sie axios als Abhängigkeit zu Ihrem Service hinzu.

Umgebungsvariable erstellen

Für diese Lösung ist ein Secret Key (Geheimschlüssel) erforderlich, mit dem die vertraulichen Daten verschlüsselt und entschlüsselt werden.

Die Zeichenfolge für den Geheimschlüssel muss mindestens 32 Byte lang sein. Behalten Sie diesen Geheimschlüssel für sich.

Um einen zufälligen Geheimschlüssel zu erstellen, kann mit Mac/Linux die folgende Befehlszeile verwendet werden:

xxd -l32 -p /dev/urandom

Alternativ kann dieser Geheimschlüssel über Node.js generiert werden:

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

 

Fügen Sie in Ihrem Service, in dem Ihr Schlüssel gespeichert ist, eine Umgebungsvariable hinzu.

Für Testzwecke kann der folgende 32-Byte-Geheimschlüssel verwendet werden.

a154eb4c759711bc2538a7cc021e9e9f17dd8aa63151c62ca28a82a4a404203d

AES-Verschlüsselungsfunktion erstellen

Erstellen Sie zunächst mithilfe eines symmetrischen Verschlüsselungsverfahrens eine Funktion zur Verschlüsselung und Entschlüsselung von Daten.

Crypto in Node.js

Node.js bietet ein integriertes Kryptografie-Modul namens Crypto. Crypto umfasst mehrere nützliche Methoden wie createCipheriv() und createDecipheriv(), mit denen Sie angeben können, welche Art von Blockchiffren-Algorithmus verwendet werden soll.

GCM-Blockchiffre

Der unter der Abkürzung AES bekannte Advanced Encryption Standard ist eine Technik zum Schutz von Daten mithilfe von Verschlüsselungsalgorithmen. Der AES kann durch eine Vielzahl von Betriebsmodi erreicht werden.

In dieser Lösung verwenden Sie GCM, Galois/Counter Mode, ein symmetrisches Blockchiffre-Verfahren, das wegen seiner Geschwindigkeit und Stärke beliebt ist.

Code

Erstellen Sie eine neue Funktion namens AES mit dem folgenden Code.

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 }

Die Sichtbarkeit dieser Funktion sollte auf „Private“ (Privat) gesetzt werden, da sie nur über eine andere Funktion im selben Service verwendet wird.

Die Funktion encrypted-gather erstellen

Erstellen Sie anschließend die Funktion, die die sensiblen <Gather>-Vorgänge ausführen wird. Diese Funktion wird in einem späteren Schritt als Voice WebHook für eingehende Telefonnummern konfiguriert.

Über diese Funktion werden von Anrufer:innen eingegebene Ziffern verschlüsselt, sobald sie eingehen, und in ihrem verschlüsselten Zustand an die endgültige Zielfunktion („destination“) gesendet.

Code

Erstellen Sie eine neue Funktion namens encrypted-gather mit dem folgenden Code:

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 }

Diese Funktion sollte auf „Protected“ (Geschützt) gesetzt werden, da sie über Twilio aufgerufen wird. Sie kann mit dem Header X-Twilio-Signature gesichert werden.

Wenn Sie diese Lösung in der Produktion implementieren, müssen Sie die Entschlüsselungsvariable „dummyApi“ in der URL Ihres Back-End-Service ändern.

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

Wie erfolgt die Verschlüsselung?

Oben importieren Sie die Funktionen, die Sie im vorigen Schritt erstellt haben, mit der folgenden Zeile:

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

Dann definieren Sie Ihren Geheimschlüssel, indem Sie ihn aus der Umgebungsvariablen abrufen:

const secret_key = context.AES_SECRET

Und am wichtigsten ist, dass alle vertraulichen Informationen mit der Verschlüsselungsfunktion encrypt umhüllt werden. (In diesem Fall werden <Gather>-Informationen als Digit-Parameter übergeben und können über das Ereignisobjekt aufgerufen werden.)

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

Damit wird die Verschlüsselung der erfassten Informationen verarbeitet.

Die Funktion decrypt-gather erstellen

Zum Schluss erstellen wir eine Funktion, um zu zeigen, wie die sensiblen Daten wieder entschlüsselt werden können.

In einer Produktionsumgebung wäre dies wahrscheinlich eine Anforderung an Ihren Back-End-Service, der die Anruferinformationen auf der Grundlage Ihrer Geschäftsanforderungen verarbeitet.

In dieser Lösung fungiert eine dritte Funktion als „Back-End-Service“, der diese Daten verarbeitet. Diese Funktion empfängt die verschlüsselten Ziffern und entschlüsselt sie zur weiteren Verarbeitung.

Code

Erstellen Sie eine neue Funktion mit dem Namen decrypt-gather mit folgendem Code:

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) 
}

Die Sichtbarkeit dieser Funktion muss „Public“ (Öffentlich) sein, da sie vorgibt, ein externer Dienst zu sein.

Wie erfolgt die Entschlüsselung?

Oben importieren Sie erneut die AES-Funktionen und definieren den secret_key als Variable.

Dann rufen Sie die Entschlüsselung (decrypt) der zuvor verschlüsselten Informationen auf:

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

Zusätzliche Konfiguration

Telefonnummern-WebHook

Verknüpfen Sie diese Funktion der Einfachheit halber direkt mit einer Telefonnummer.

So konfigurieren Sie die Telefonnummer:

  1. Navigieren Sie in der Twilio-Konsole zum Abschnitt Phone Numbers (Telefonnummern).

  2. Wählen Sie Ihre Telefonnummer aus und blättern Sie dann zum Abschnitt Voice & Fax (Sprache und Fax).

  3. Stellen Sie unter Voice Configuration (Sprachkonfiguration) die Funktion encrypted-gather als WebHook A call comes in (Ein Anruf geht ein) ein.

  4. Speichern Sie die Änderungen.

Wenn Sie dies von Twilio Studio auslösen möchten, lesen Sie diesen Blogbeitrag, um mehr darüber zu erfahren, wie Sie diese Lösung sicher in Studio integrieren können.

PCI-Modus aktivieren

Fast fertig! Sie haben die Funktionen gesichert, aber es gibt noch einen weiteren Bereich, in dem Twilio erfasste Ziffern im Klartext speichert – Sprachanrufprotokolle.

Unten sehen Sie einen Screenshot von der Twilio-Konsole für einen eingehenden Anruf mit der implementierten verschlüsselten <Gather>-Lösung. Auch wenn Functions die Daten gesichert hat, ist das für Voice nicht der Fall.

Es gibt nur eine Möglichkeit, zu verhindern, dass diese Daten im Anrufprotokoll angezeigt werden: den PCI-Modus. Wenn Sie den PCI-Modus für Ihr Konto aktivieren, werden alle Daten, die von einem beliebigen <Gather>-Vorgang erfasst wurden, unkenntlich gemacht.

Das Aktivieren des PCI-Modus für ein Konto ist eine Einbahnstraße. Ist der Modus einmal aktiviert, lässt er sich nicht mehr deaktivieren. Durch Unkenntlichmachung kann die Fehlerbehebung bei Problemen mit Voice schwieriger werden.

Wenn Sie vertrauliche Informationen wirklich sicher erfassen möchten ...

  1. Navigieren Sie in der Twilio-Konsole zu den Twilio Voice Settings (Spracheinstellungen). (Klicken Sie im linken Navigationsbereich auf Voice > Settings > General (Voice > Einstellungen > Allgemein).)

  2. Klicken Sie auf die Schaltfläche Enable PCI Mode (PCI-Modus aktivieren).

  3. Speichern Sie die Änderungen.

Telefonieren

Jetzt ist es der Moment der Wahrheit gekommen – Zeit für einen Testanruf an die Telefonnummer.

Von hier aus gibt es zwei Möglichkeiten.

Wenn Sie 1234 als die letzten 4 Ziffern Ihrer „Kreditkarte“ und 4321 als eindeutige PIN eingeben, werden während des Anrufs einige „Dummy“-Kontoinformationen zurückgegeben. Das ist ein Beispiel für eine erfolgreiche API-Antwort.

Wenn Sie andere Ziffern eingeben, verhält es sich so, als wären Sie als Nutzer:in nicht bekannt und eine 404-Antwort wird zurückgegeben. Das ist ein Beispiel für eine nicht erfolgreiche Anforderung, bei der ein Fehler an den Twilio Debugger gesendet wird.

Woher weiß ich, dass es funktioniert hat?

Folgen Sie dem nicht erfolgreichen Pfad und sehen Sie sich Ihr Fehlerprotokoll in der Twilio-Konsole an.

Für die Fehlerantwort 404 finden Sie einen 82005-Fehler von Functions mit den folgenden Details:

Das ist gut. Ohne die Verschlüsselung hätte eine nicht erfolgreiche Antwort diese Variablen im Klartext protokolliert. Jetzt aber werden die Daten in ihrer sichereren, verschlüsselten Form protokolliert.

Sie können auch Ihr Anrufprotokoll überprüfen, um zu bestätigen, dass auch dort die Ziffern *REDACTED* (*UNKENNTLICH*) angezeigt werden.

Ist das sicher?

Wenn Sie dieses Tutorial (einschließlich der optionalen Schritte für den PCI-Modus) befolgen, wird verhindert, dass Daten innerhalb des Twilio-Ökosystems im Klartext protokolliert werden, und es wird verhindert, dass jede:r bei Twilio Ihre sensiblen Daten entschlüsseln kann. Das stellt eine Verbesserung gegenüber dem Standard dar.

Allerdings wird der für die Verschlüsselung und Entschlüsselung verwendete Geheimschlüssel als Umgebungsvariable im Service gespeichert, d. h. Nutzer:innen, denen Sie Zugriff auf Twilio Functions gewähren, könnten den Schlüssel extrahieren und die Werte entschlüsseln.

Abschließende Empfehlung

Wenn Sie am bereitgestellten Beispielcode Änderungen vornehmen, beachten Sie bitte, dass Functions Konsolenwarnungen und Fehler in internen Twilio-Systemen und im Twilio Debugger für einige Zeit vorhält.

Verwenden Sie für vertrauliche, unverschlüsselte Daten keine der folgenden Methoden zur Konsolenprotokollierung:

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

Fazit

In dieser Lektion haben Sie gelernt, wie Sie von <Gather> TwiML erfasste Daten durch Verschlüsselung über eine serverlose Funktion und durch Unkenntlichmachung über den Voice-PCI-Modus schützen können.

Wenn Sie von Ihren Anrufer:innen Zahlungen entgegennehmen möchten, sollten Sie die vollständig PCI-konforme Twilio <Pay>-Funktion in Betracht ziehen.

Weitere Informationen zur PCI-Compliance bei Twilio finden Sie in der Dokumentation und der Verantwortungsmatrix.

Nutzer:innen vertrauen darauf, dass Sie ihre sensiblen Informationen vertraulich behandeln. Achten Sie darauf, dass Sie dieses Vertrauen respektieren und bewahren, indem Sie alles tun, was Ihnen möglich ist, um die von Ihnen verarbeiteten Daten zu schützen.

Bry Schinina ist Entwicklerin und Pädagogin. Ihr ist besonders wichtig, dass Unternehmen keine privaten Informationen preisgeben. Sie arbeitet als Tech Lead und Senior Technical Account Manager bei Twilio. Hier löst sie komplexe Probleme und verhilft Unternehmen zum Erfolg mit ihren Plattformen für digitale Interaktionen. Sie ist unter bschinina [at] twilio.com zu erreichen.