Protéger vos données Twilio Voice grâce au chiffrement et au masquage

Protect Twilio Voice Input with Encryption and Redaction
February 26, 2024
Rédigé par
Révisé par
Paul Kamp
Twilion

Protéger vos données Twilio Voice grâce au chiffrement et au masquage

Faites-vous tout votre possible pour protéger les informations sensibles que vos appelants vous confient ?

À l'heure où les entreprises utilisent de plus en plus d'informations sensibles, sécuriser ces dernières est plus important que jamais. Twilio offre de nombreuses façons de protéger vos données sensibles. Il vous appartient, toutefois d'implémenter les ressources de Twilio de manière responsable.

Dans cet article, vous allez découvrir comment chiffrer et masquer les données collectées à partir de Twilio Programmable Voice, à l'aide du verbe TwiML <Gather>, des fonctions sans serveur Twilio et du mode PCI vocal.

Ce dont vous aurez besoin

Pour suivre ce tutoriel, vous avez besoin des éléments ci-dessous :

Qu'allez-vous construire ?

Vous allez construire une application vocale interactive simple pour gérer l'authentification des appelants. Une fonction permettra d'inviter l'appelant à fournir les informations sensibles suivantes via le verbe TwiML <Gather>.

  1. « Saisissez votre code PIN à 4 chiffres »

  2. « Saisissez les 4 derniers chiffres du numéro de votre carte de paiement »

Ces informations sont chiffrées dès que l'appelant les communique. Dès lors, elles restent chiffrées jusqu'à ce qu'elles atteignent leur destination.

Dans une implémentation concrète, la destination serait probablement votre service back-end de traitement. Dans cet exemple, une autre fonction fera office d'« API factice » pour démontrer le fonctionnement du déchiffrement.

Vous activerez également le mode PCI vocal pour masquer les informations collectées dans les historiques des appels vocaux.

Avant

Avant de commencer, découvrez comment se présenteraient vos journaux sans chiffrement ni masquage.

Le service de fonctions Twilio enregistre toutes les erreurs qu'une fonction génère dans votre débogueur Twilio. Dans cet exemple, une erreur est consignée si certains chiffres spécifiques ne sont pas saisis. Les paramètres de requête sont visibles en texte brut dans l'erreur reçue par le débogueur.

Programmable Voice enregistre également les chiffres collectés en texte brut dans l'historique des appels vocaux :

Ces informations sont disponibles si vous avez accès aux historiques des appels ou au débogueur.

Après

Une fois cette solution implémentée, les données visibles sont moins vulnérables. Les valeurs que votre journal de fonctions affiche sont chiffrées et plus sécurisées :

Le statut *REDACTED* (*MASQUÉ*) apparaît dans votre historique des appels :

Démarrer

Twilio Functions

Pour suivre ces instructions, utilisez l'éditeur de fonctions de la console Twilio.

Les développeurs avancés peuvent également utiliser la CLI sans serveur plus robuste pour créer, déployer et maintenir les fonctions.

Créer un service

Créez et conservez les fonctions dans des services :

  1. Connectez-vous à la console Twilio et accédez à l'onglet Functions (Fonctions).

  2. Créez un service en cliquant sur le bouton Create Service (Créer un service) et en ajoutant un nom. Par exemple : encrypted-gather-sample.

Ajouter une dépendance

Dans cette solution, utilisez la librairie axios pour envoyer une requête à traiter à votre « faux » service back-end (fonction decrypt-gather).

Ajoutez axios en tant que dépendance à votre service.

Créer une variable d'environnement

Cette solution nécessite une clé secrète pour chiffrer et déchiffrer les données sensibles.

Votre chaîne de clé secrète doit comporter au moins 32 octets. Ne divulguez pas cette clé secrète.

Pour créer une clé secrète aléatoire, utilisez la ligne de commande suivante sous Mac/Linux :

xxd -l32 -p /dev/urandom

Vous pouvez également la générer avec Node.js :

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

 

Ajoutez une variable d'environnement dans le service dans lequel vous conservez votre clé.

À des fins de test, vous pouvez utiliser la clé secrète de 32 octets suivante.

a154eb4c759711bc2538a7cc021e9e9f17dd8aa63151c62ca28a82a4a404203d

Créer une fonction de chiffrement AES

Commencez par créer une fonction pour gérer le chiffrement et le déchiffrement des données à l'aide de la cryptographie symétrique.

Node.js Crypto

Node.js intègre un module de cryptographie appelé Crypto. Crypto propose plusieurs méthodes utiles, comme createCipheriv() et createDecipheriv(), pour spécifier le type d'algorithme de chiffrement par bloc à utiliser.

Chiffrement par bloc GCM

La technique de protection des données Advanced Encryption Standard, ou AES, utilise des algorithmes pour effectuer le chiffrement. L'AES est compatible avec de nombreux modes d'opération.

Dans cette solution, vous utiliserez le mode GCM (Galois/Counter Mode). Ce mode de chiffrement par bloc en cryptographie symétrique est préféré pour sa vitesse et sa force.

Code

Créez une nouvelle fonction nommée AES avec le code suivant :

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 
}

Définissez sa visibilité sur « Private » (Privée), car elle ne sera utilisée qu'à partir d'une autre fonction du même service.

Créer une fonction encrypted-gather

Créez ensuite la fonction qui effectuera les opérations <Gather> sensibles. Vous la configurerez en tant que webhook vocal du numéro de téléphone entrant dans une étape ultérieure.

Cette fonction permet de chiffrer les chiffres saisis par l'appelant dès leur réception et de les envoyer dans cet état à la fonction de « destination » finale.

Code

Créez une nouvelle fonction nommée encrypted-gather avec le code suivant :

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
}

Définissez sa visibilité sur « Protected » (Protégée), car elle sera appelée depuis Twilio et protégée avec l'en-tête X-Twilio-Signature.

Lors de l'implémentation de cette solution en production, vous devrez remplacer la variable de déchiffrement « dummyApi » par l'URL de votre service back-end.

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

 

Comment fonctionne le chiffrement ?

Commencez par importer les fonctions créées à l'étape précédente avec la ligne suivante :

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

Définissez ensuite votre clé secrète en la récupérant à partir de la variable d'environnement :

const secret_key = context.AES_SECRET

L'important, c'est que toutes les informations sensibles sont protégées par la fonction encrypt. (Le cas échéant, les informations <Gather> sont transmises en tant que paramètre Digit. Vous pouvez les consulter à partir de l'objet événement.)

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

Cette fonction gère le chiffrement des informations collectées.

Créer une fonction decrypt-gather

Enfin, découvrons comment créer une fonction pour montrer comment les données sensibles sont déchiffrées.

Dans un environnement de production, cette requête est généralement adressée à votre service back-end qui traite les informations de l'appelant selon les besoins de votre entreprise.

Dans cette solution, une troisième fonction agira comme le « service back-end » qui traite ces données. Cette fonction recevra les numéros chiffrés et les déchiffrera en vue de leur traitement ultérieur.

Code

Créez une nouvelle fonction nommée decrypt-gather avec le code suivant :

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

Cette fonction aura une visibilité « Public » (Publique) pour imiter un service externe.

Comment le déchiffrement s'exécute-t-il ?

Commencez par réimporter les fonctions AES et définir secret_key en tant que variable.

Appelez ensuite la fonction decrypt sur les informations chiffrées précédemment :

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

Configuration supplémentaire

Webhook du numéro de téléphone

Par souci de simplicité, connectez directement cette fonction à un numéro de téléphone.

Pour configurer le numéro de téléphone :

  1. Dans la console Twilio, accédez à la section Phone Numbers (Numéros de téléphone)

  2. Sélectionnez votre numéro de téléphone, puis faites défiler jusqu'à la section Voice & Fax (Voix et télécopie).

  3. Définissez la fonction encrypted-gather en tant que webhook A call comes in (Appel entrant) sous Voice Configuration (Configuration vocale).

  4. Enregistrez les modifications avec Save (Enregistrer).

Pour déclencher cette fonction à partir de Twilio Studio, consultez cet article de blog pour savoir comment intégrer cette solution en toute sécurité dans Studio.

Activer le mode PCI

Vous avez presque terminé ! Vos fonctions sont désormais protégées. Cependant, Twilio conserve toujours les chiffres collectés en texte brut dans les historiques des appels vocaux.

La capture d'écran de la console Twilio ci-dessous présente un appel entrant utilisant la solution <Gather> chiffrée. Même si Functions sécurisait les données, ce n'était pas le cas de Voice.

La seule et unique façon d'empêcher l'affichage de ces données dans l'historique des appels consiste à utiliser le mode PCI. L'activation du mode PCI sur votre compte masquera toutes les données collectées lors d'une opération <Gather>.

L'activation du mode PCI sur un compte est définitive. Vous ne pourrez plus le désactiver. Le masquage peut rendre l'analyse des problèmes de Voice plus difficile.

Si vous souhaitez sérieusement collecter des informations sensibles en toute sécurité…

  1. Accédez aux paramètres Twilio Voice sur la console Twilio. (Dans le volet de navigation de gauche, cliquez sur Voice > Settings > General [Voice > Paramètres > Général].)

  2. Cliquez sur le bouton Enable PCI Mode (Activer le mode PCI).

  3. Enregistrez les modifications avec Save (Enregistrer).

Passer un appel

Le moment de vérité est arrivé : il est temps de passer un appel test au numéro de téléphone.

Deux possibilités s'offrent à vous.

En entrant les 4 derniers chiffres de votre « carte de crédit » factice 1234 et le code PIN unique 4321, l'appel vous renvoie des informations de compte « fictives ». Dans cet exemple, la réponse de l'API réussit.

En entrant d'autres chiffres, l'appel vous considère comme un utilisateur inconnu et renvoie une réponse 404. Dans cet exemple, la requête échoue et une erreur est consignée dans le débogueur Twilio.

Comment savoir si la configuration est opérationnelle ?

Suivez le chemin d'accès infructueux et consultez votre journal d'erreurs sur la console Twilio.

Si vous avez reçu une réponse d'erreur 404, l'erreur 82005 dans Functions présentant les détails suivants s'affiche dans le journal :

C'est parfait. Sans chiffrement, une réponse infructueuse aurait enregistré ces variables en texte brut. Désormais, les données seront enregistrées dans leur forme sécurisée et chiffrée.

Vous pouvez également consulter votre historique des appels pour confirmer que les chiffres ont bien été remplacés par *REDACTED* (*MASQUÉ*).

Cette configuration est-elle sécurisée ?

En suivant ce tutoriel (y compris les étapes facultatives du mode PCI), vous devriez empêcher les données de connexion d'apparaître en texte brut n'importe où dans l'écosystème de Twilio. De même, personne chez Twilio ne devrait pouvoir déchiffrer vos données sensibles. Votre configuration sera ainsi renforcée par rapport aux paramètres par défaut.

Cependant, comme la clé secrète de chiffrement et de déchiffrement est stockée en tant que variable d'environnement sur le service, les utilisateurs auxquels vous accordez l'accès aux fonctions Twilio pourraient l'extraire et tenter de déchiffrer les valeurs.

Dernière recommandation

Si vous modifiez l'exemple de code fourni, gardez à l'esprit que Functions conserve les avertissements et les erreurs de la console dans les systèmes Twilio internes et dans le débogueur Twilio pendant un certain temps.

N'utilisez aucune des méthodes de journalisation de la console suivantes avec des données sensibles non chiffrées :

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

Conclusion

Dans cette leçon, vous avez appris à protéger les données collectées avec le verbe TwiML <Gather> en utilisant une fonction sans serveur pour le chiffrement et le mode PCI vocal pour le masquage.

Si vous souhaitez collecter des paiements auprès de vos appelants, pensez à la fonction Twilio <Pay> entièrement conforme à la norme PCI.

Pour en savoir plus sur la conformité à la norme PCI chez Twilio, consultez la documentation et la matrice des responsabilités.

Les utilisateurs vous font confiance pour préserver la confidentialité de leurs informations sensibles. Veillez à respecter et à conserver cette confiance en faisant tout votre possible pour sécuriser les données que vous traitez.

Développeuse et formatrice, Bry Schinina met un point d'honneur à ce que les entreprises n'exposent pas d'informations privées. Tech Lead et Sr. Technical Account Manager chez Twilio, elle résout des problèmes complexes et aide les entreprises à exploiter avec succès leur plateforme d'engagement numérique. Vous pouvez la contacter à l'adresse suivante : bschinina [at] twilio.com.