VoIP: Asterisk et la sécurité

Thu 13 March 2008 by Loïc Cornet

Introduction

Cet article sur Asterisk et la sécurité présente les résultats de l’analyse du protocole IAX2, avec pour objectif la vérification de « l’implémentation » des mécanismes de sécurité annoncés par le draft de l’IETF. Pour réaliser cette étude, les codes sources contenus dans deux fichiers du répertoire channels ont étés examinés en détail :

  • chan_iax2.c
  • iax2.h

Les objectifs étant fixés, entrons dans le vif du sujet. Notons que l’étude porte plus particulièrement sur le chiffrement, sans détailler l’authentification.

Un peu d’observation...

Equipé d’un analyseur réseau, voici ce que l’on obtient en capturant les trames d’une conversation :

Sans chiffrement :

  • 33  Mini packet, source call# 4, timestamp 7420ms,Raw A-law data (GSM)
  • 33  Mini packet, source call# 4, timestamp 7440ms,Raw A-law data (GSM)
  • 33  Mini packet, source call# 4, timestamp 7460ms,Raw A-law data (GSM)
  • 33  Mini packet, source call# 4, timestamp 7480ms,Raw A-law data (GSM)

Avec chiffrement :

  • 62 Mini packet, source call# 2, timestamp 63334ms,unknown (0x00)
  • 62 Mini packet, source call# 2, timestamp 52953ms,unknown (0x00)
  • 62 Mini packet, source call# 2, timestamp 31588ms,unknown (0x00)
  • 62 Mini packet, source call# 2, timestamp 3198ms,unknown (0x00)

Ces captures montrent que l’analyseur n’est plus en mesure de restituer l’information concernant le codec audio utilisé.

La seconde information est le nombre d’octet des trames : 33 octets en non chiffré, 62 octets en chiffré. Nous reviendrons plus tard sur cette différence de taille, qui semble tout de même logique du fait de l’utilisation de la cryptographie (AES-128).

Etude de la structure des trames

Pour commencer, il faut localiser la fonction « mère » permettant de gérer la création et l’envoi des trames IAX2. Cette fonction se nomme iax2_send. Comme cela a été expliqué dans l’article précédent, il existe deux types de trames gérées par IAX2 : les FULL frames et les MINI frames. Dans cet article le focus est fait sur les MINI frames, qui transportent uniquement la voix.

Cependant, il faut savoir que les FULL frames sont chiffrées selon le même principe, et sont utilisées pour les négociations et les remises à zéro du timestamp (ces trames peuvent également transporter de la voix).

Les MINI frames nous intéressent car le draft ne spécifie pas le format chiffré de ces trames. Voici les structures des MINI frames :

Version non chiffrée :

struct ast_iax2_mini_hdr {
    unsigned short callno;
    /* Numéro de l’émetteur de  l’appel (2 Octets) */
    unsigned short ts;
    /* Timestamp (horodatage en ms) (2 Octets) */
    unsigned char data[0];
    /* Données */
} __attribute__ ((__packed__));

Version  chiffrée :

struct ast_iax2_mini_enc_hdr {
    unsigned short callno;
    /* Numéro-identifiant de  l’émetteur de l’appel (2 Octets) */
    unsigned char encdata[0];
    /* Données */

Ce qu’il faut remarquer sur les structures, c’est l’absence du timestamp lorsque les trames sont chiffrées. Or la capture de l’analyseur réseau indique bien la valeur du timestamp:

  • 62 Mini packet, source call# 2, timestamp 63334ms, unknown (0x00)

Pour éclaircir la situation, il est intéressant de se plonger dans le code.

Etude du code : c’est parti !

Au sein de la fonction iax2_send, la partie traitant les minis trames attire notre attention :

...
} else {
    /*Mini-frames have no sequence number */
    fr->oseqno = -1;
    fr->iseqno = -1;
    /* Mini frame will do */
    mh = (struct ast_iax2_mini_hdr *)(fr-> af.data - sizeof(struct
    ast_iax2_mini_hdr));
    mh->callno = htons(fr->callno);
    mh->ts = htons(fr->ts & 0xFFFF);
    fr->datalen = fr->af.datalen + sizeof(struct ast_iax2_mini_hdr);
    fr->data =mh;
    fr->retries = -1;
    if (pvt->transferring == TRANSFER_MEDIAPASS)
    fr->transfer = 1;
    if (ast_test_flag(pvt, IAX_ENCRYPTED)) {
    if (ast_test_flag(pvt, IAX_KEYPOPULATED)) {
    encrypt_frame(&pvt->ecx, (struct ast_iax2_full_hdr *)mh, pvt->semirand, &fr->datalen);
} else {
    ast_log(LOG_WARNING, "Supposed to send packet encrypted, but no key?\n");
}
res = send_packet(fr);
...

Explications :

mh signifie MiniHeader, ce qui correspond à l’entête des minis trames.

fr représente la Frame, ou la mimi trame dans notre cas.

Dans un premier temps, le numéro d’appel (identifiant unique), ainsi que le timestamp sont placés dans l’en-tête mh. Ceci expliquerait la présence du timestamp au niveau de l’analyseur.

Dans un deuxième temps, la tramefr est construite. L’en-têtemh, est placé avant les données issues du codec (contenues dans data).

Les drapeaux IAX_ENCRYPTED et IAX_KEYPOPULATED sont testés, puis la routine encrypt_frame est appelée. Au retour de cette dernière, la trame est émise :

res = send_packet(fr);

Note : le codec utilisé (GSM) renvoie des blocs binaires de 33 octets, représentant 20 ms de voix encodée puis compressée.

Le processus est donc le suivant : après avoir affecté les valeurs correspondantes au numéro d’appel et au timestamp, les données sont récupérées (bloc de 33 octets) et placées à la suite. On teste le drapeau de chiffrement (IAX_ENCRYPTED). S’il est présent on appelle la fonction encrypt_frame.

A ce stade, le format de la trame est le suivant :

image1_article_voip.jpg

A présent, intéressons nous à la fonction encrypt_frame, qui se charge du chiffrement. Les arguments passés à cette fonction sont :

&pvt->ecx :clé de chiffrement ;
(struct ast_iax2_full_hdr *) mh: trame précédemment construite ;
pvt->semirand: 32 octets aléatoires ;
&fr->datalen: longueur des données.

Comme pour la fonction précédente, nous nous penchons uniquement sur la fin du code, qui concerne les minis trames chiffrées :

else {
    struct ast_iax2_mini_enc_hdr *efh = (struct
    ast_iax2_mini_enc_hdr *)fh;
    if (option_debug && iaxdebug)
        ast_log(LOG_DEBUG, "Encoding mini frame with length %d\n", *datalen);
    padding  = 16 - ((*datalen -
    sizeof(struct ast_iax2_mini_enc_hdr)) % 16);
    padding  = 16 + (padding & 0xf);
    memcpy( workspace, poo, padding);
    memcpy( workspace + padding, efh->encdata, *datalen -
    sizeof(struct ast_iax2_mini_enc_hdr));
    workspace [15] &= 0xf0;
    workspace [15] \|= (padding & 0x0f);
    datalen += padding ;
    memcpy_encrypt (efh->encdata, workspace, *datalen -
    sizeof(struct ast_iax2_mini_enc_hdr), ecx);
    if (*datalen >= 32 + sizeof(struct ast_iax2_mini_enc_hdr))
        memcpy(poo, workspace + *datalen - 32, 32);
}
return 0;}

Cette fonction est intéressante : elle introduit du padding en bourrage.

Il faut suivre le calcul du padding avec la valeur initiale datalen = 37 octets. Pourquoi 37 octets alors que le codec nous en renvoie 33 ? Car dans la fonction iax2_send, il faut noter la ligne :

fr->datalen = fr->af.datalen + sizeof(struct ast_iax2_mini_hdr);

qui ajoute + 4 octets à la variable datalen. Ces quatre octets correspondent à la structure de la trame iax2_mini, qui comporte les 2 champs d’en-tête (identifiant de l’appel et le timestamp) de taille égale à deux octets chacun (la partie data étant vide dans la description de la structure, on ne compte que ces deux variables).

Suivons le calcul avec la valeur datalen = 37 octets :

padding  = 16 - ((datalen - sizeof(struct ast_iax2_mini_enc_hdr)) % 16)
padding  = 13

Le calcul se poursuit avec une seconde instruction :

padding = 16 + (padding & 0xf);
padding = 29

Le padding est donc de 29 octets, voyons maintenant quel usage il en est fait.

1: memcpy(workspace, poo, padding);
2: memcpy(workspace + padding, efh->encdata, *datalen - sizeof(struct ast_iax2_mini_enc_hdr));

1 : Lorsque la fonction encrypt_frame est appelée par iax2_send, nous avions remarqué que 32 octets aléatoires étaient passés en argument.

Ces 32 octets sont stockés dans la variable poo. L’opération effectuée copie en mémoire (dans l’espace de travail nommé workspace) le contenu de poo sur une longueur correspondant à padding. Comme padding est égal à 29, nous avons au début de l’espace de travail 29 octets aléatoires.

2 : À la suite des 29 premiers octets, sont ajoutées les données (voix encodée), sur une longueur de datalen – 2 octets (taille de la structure iax2_enc_hdr), soit 37-2 = 35 octets.

Voici une représentation de l’espace de travail en mémoire (workspace)

29 octets aléatoires
35 octets de données

Lorsque l’espace de travail est constitué, la valeur de datalen est mise à jour en lui ajoutant padding calculé précédemment :

datalen = datalen + padding = 37 + 29 = 66 octets

Cette valeur de 66 octets n’est pas anodine.

C’est la valeur que l’on retrouve sur la console du serveur Asterisk lorsque le mode debug est activé :

DEBUG[]: Decoding miniwith length 66

Nous concluons que la taille des paquets voix IAX2 en communication est de 66 octets. Cela se retrouve en ajoutant les valeurs obtenues dans l’analyseur :

62 octets de payload + 2 octet de timestamp + 2 octets d’identifiant d’appel = 66 octets.

Seulement, d’après la structuredes minis trames, il n’y a pas de timestamp ! Nous avons donc un déficit de 2 octets si l’on ajoute les 62 de payload + les 2 d’identifiant d’appel. Poursuivons tout de même l’analyse de la suite du code.

Une fois la valeur datalen mise à jour, la fonction memcpy_encrypt est appelée, en passant les paramètres suivants :

efh->encdata : zone mémoire de destination des données chiffrées

workspace : emplacement des données en attente de chiffrement

datalen - sizeof(struct ast_iax2_mini_enc_hdr) : taille des données à chiffrer ecx : clé de chiffrement

La taille des données à chiffrer est :

(datalen – sizeof(struct_mini_enc)) = 64 octets.

Chose intéressante : au fur et à mesure que l’on avance, on se rend compte que la taille des trames est bien de 66 octets, et que la routine de chiffrement reçoit 64 octets de données à chiffrer ! Et comme 66 – 64 = 2, il ne nous reste plus que 2 octets d’en-tête. Alors, dans ce cas, les 2 octets restants correspondent au numéro d’identifiant de l’appel. Mais où est passé le timestamp pourtant bien affiché par l’analyseur ? La solution est peut être dans la fonction appelée : memcpy_encrypt. Voici le code :

int x;
if (len % 16)
    ast_log(LOG_WARNING, "len should be multiple of 16, not %d!\n", len);
for (x=0;x<len;x++)
    dst[x] = src[x] ^ 0xff;
unsigned char curblock[16] = { 0 };
int x;
while(len > 0) {
    for (x=0;x<16;x++)
        curblock[x] ^= src[x];
    aes_encrypt(curblock, dst, ecx);
    memcpy(curblock, dst, sizeof(curblock));
    dst += 16;
    src += 16;
    len -= 16;
}}

Prenons les opérations effectuées dans l’ordre :

  • [xxx] Teste si la longueur est un multiple de 16. La longueur transmise est de 64 octets, et 64 mod 16 = 0 : le traitement se poursuit.
  • [xxx] Ensuite, tant que la longueur est supérieure à zéro, il y a un découpage en blocs de 16 octets, qui subissent un XOR (OU exclusif logique) avec le bloc précédent.
  • lorsque le XOR est effectué, la fonction aes_encrypt est appelée pour effectuer le chiffrement proprement dit, en passant en paramètres le bloc en cours curblock, l’adresse mémoire de destination dst, et la clé de chiffrement ecx (de 128 bits).

Cela ne résout pas notre problème de taille, mais cela nous permet de voir que le mode opératoire utilisé pour le chiffrement AES est le CBC (Cipher-Block Chaining). Pour s’en convaincre, il suffit de reprendre les deux lignes surlignées en bleu : on chaîne en effectuant le XOR sur le bloc précédent, puis on appelle la fonction aes_encrypt, qui effectue le chiffrement selon le mode ECB (Electronic CodeBook).

Padding et AES CBC

Revenons sur l’utilité du padding. Une première explication découle du mode opératoire pour le chiffrement en AES.

En effet, le chiffrement se fait sur des blocs de 16 octets. Or, nous avons à l’origine 33 octets de voix encodée.

Ensuite, nous avons vu que la fonction encrypt_frame injecte 35 octets après les 29 de padding. Et comme 29 + 35 = 64, cela permet d’être aligné sur un multiple de 16 octets, qui est la taille des blocs qui passent dans la routine de chiffrement. L’utilité du padding parait plus claire.

Mais le calcul du padding, qui s’effectue en deux instructions, attire notre attention sur la deuxième étape du calcul:

padding = 16 + (padding & 0xf);

Sachant que le padding sert à s’aligner sur un multiple de 16 octets, pourquoi ajouter 16 octets d’office ?

Il faut savoir que la méthode de chiffrement AES CBC peut avoir un vecteur d’initialisation (qui n’est pas indispensable). Alors pourquoi les 16 octets ne serviraient-ils pas de vecteur d’initialisation (IV) ?

Recoupons les informations que nous avons déjà :

  • la taille du padding minimum est de 16 octets ;
  • la fonction mère iax2_send passe 32 octets aléatoires, dont 29 sont recopiés en mémoire (par la fonction encrypt_frame), juste avant le bloc de données.

Cela signifie que le padding, en plus de servir de bourrage pour l’alignement sur un multiple de 16 octets, sert d’IV grâce aux valeurs aléatoires.

Lorsque le premier bloc de 16 octets arrive, il s’agit des 16 premiers octets aléatoires (au minimum), qui sont à priori l’IV.

Lorsque le XOR s’effectue pour la première fois, il s’applique sur le même bloc, le résultat est donc le premier bloc chiffré. Ce dernier est ensuite passé dans la fonction aes_encrypt, qui elle va chiffrer le bloc seul (sans chaînage avec un précédent, puisqu’il n’y en a pas). Étant donné que c’est le premier bloc qui sera ensuite chaîné avec le suivant, cela confirme que les 16 premiers octets sont l’IV. Les 13 octets suivants qui font partie des 29 de padding, sont donc purement du bourrage, qui permet d’aligner le reste des données sur un multiple de 16 (13+35=48 ; et 48%16=0).

Arrêtons-nous un instant sur le vecteur d’initialisation, stocké dans la variable poo :

if (*datalen >= 32 + sizeof(struct[…]enc_hdr))

memcpy(poo, workspace + *datalen - 32, 32);

Ce bloc d’instruction est exécuté juste après l’appel de la fonction aes_encrypt. Nous remarquons que la variable poo est modifiée, à condition que la taille des données soit supérieure ou égale a 34 (32 +2 ) octets. Cette condition est à chaque fois vérifiée, car datalen = 66 octets. L’opération qui est effectuée récupère les 32 derniers octets stockés dans l’espace de travail, qui correspondent à de la voix. Cette méthode permet de modifier le vecteur d’initialisation pour chaque chiffrement de paquets, et ce tout au long de la communication.

Résumons : la taille d’une trame non chiffrée est 37 octets (33 de données + 4 d’en-tête), et la taille d’une trame chiffrée est de 66 octets. Cela nous donne un écart de 29 octets, qui correspond exactement au padding calculé.

Il faut maintenant résoudre le problème des deux octets de différence. Pour trouver la solution, il suffit de bien regarder la première capture de trame, et notamment les valeurs des timestamps :

Non chiffré :         Chiffré :

timestamp7400ms              timestamp45655ms

timestamp7420ms              timestamp 63334ms

timestamp7440ms              timestamp 52953ms

timestamp7460ms              timestamp 31588ms

timestamp7480ms              timestamp 3198ms

timestamp7500ms              timestamp 19488ms

timestamp7520ms              timestamp 31942ms

Dans le premier cas [xxx], les valeurs du timestamp se suivent avec un écart de 20 ms (le codec génère des paquets de 33 Octets toutes les 20ms).

Dans le deuxième cas, les valeurs du timestamp sont complètements incohérentes [xxx].

Si les valeurs du timestamp sont incohérentes, c’est qu’elles ne sont pas en claires, mais chiffrées ! Cela signifie que l’en-tête n’est pas de 4 octets mais uniquement de 2, ne comportant que le numéro d’appel.

Mais alors, où est passé le timestamp ? Remontons au niveau de la fonction encrypt_frame, avant l’appel de la routine memcpy_encrypt : les données sont envoyées dans le workspace, avec une longueur de 35 octets.

Cela correspond aux 33 octets provenant du codec GSM, plus les 2 octets du timestamp. Mais alors, comment se fait-il que l’analyseur affiche une valeur d’horodatage ?

Simplement car l’en-tête ne possède aucun indicateur qui permette de déterminer si la trame est chiffrée ou non. L’analyseur récupère donc les deux premiers octets qui suivent le numéro d’appel, les interprètes comme il peut puis retourne la valeur qu’il a lue.

Déchiffrement & construction des clés

A présent, le mécanisme de génération et de chiffrement des trames est correctement analysé. Poursuivons avec le mécanisme de déchiffrement. La première étape est effectuée par la fonction decrypt_frame.

Cette fonction détermine si la clé de déchiffrement a déjà été générée ou non.

Ensuite, la fonction decode_frame effectue le déchiffrement de la trame, en appelant la fonction memcpy_decrypt (qui elle-même appelle la routine aes_decrypt. (cf. schéma ci-dessous pour visualiser les appels successifs).

Cette suite d’appels permet le déchiffrement du payload, et restitue ainsi des données reçues.

image2_article_voip.jpg

Analyse de la fonction decrypt _frame :

static int decrypt_frame(int callno, struct ast_iax2_full_hdr *fh, struct ast_frame *f, int *datalen)
{
    int res=-1;
    (1) if (!ast_test_flag(iaxs[callno], IAX_KEYPOPULATED)) {
        /* […] */
        (2) tmppw = ast_strdupa(iaxs[callno]->secret);
        stringp = tmppw;
        while ((tmppw = strsep(&stringp, ";"))) {
        (3) **MD5Init** (&md5);
        (4) **MD5Update** (&md5, (unsigned char *)iaxs[callno]->challenge,
        strlen(iaxs[callno]->challenge));
        (5) **MD5Update** (&md5, (unsigned char *)tmppw, strlen(tmppw));
        (6) **MD5Final** (digest, &md5);
        (7) **build_enc_keys** (digest, &iaxs[callno]->ecx, &iaxs[callno]->dcx);
        (8) res = **decode_frame** (&iaxs[callno]->dcx, fh, f, datalen);
        if (!res) {
        (9) ast_set_flag(iaxs[callno],IAX_KEYPOPULATED);
        break;}}
    } else
    res = decode_frame(&iaxs[callno]->dcx, fh, f, datalen);
    (10) return res;
}

La première étape est de vérifier que la clé a été construite. Pour ce faire, le premier test recherche si le flag IAX_KEYPOPULATED (drapeau, ou indicateur) est activé ou non. Dans le cas où ce drapeau ne serait pas activé, la fonction construit la clé de déchiffrement et teste le résultat. Nous verrons comment par la suite.

Ce flag est stocké dans une structure relative à la communication, identifiée par son numéro d’appel (callno). Ce test est fait à l’indicateur(1) dans le code ci-dessus.

  1. le mot de passe est récupéré puis stocké;

(3) initialisation et calcul d’un condensé MD5, avec des constantes prédéfinies ;

(4) \"mise à jour\" du condensat MD5 en incluant le challenge envoyé à l’initialisation de la communication (challenge utilisé pour l’authentification auprès du serveur);

(5) nouvelle mise à jour en incluant le mot de passe utilisateur (stocké en (2));

(6) calcul du condensat MD5 final, en utilisant du bourrage si nécessaire (et est donc basé sur le challenge et le mot de passe) ;

(7) génération des clés (fonction buil_enc_keys), en utilisant le condensat MD5 précédemment calculé [xxx];

  1. effectue le déchiffrement de la trame avec la clé calculée ;

(9) si le test est positif, on positionne l’indicateur IAX_KEYPOPULATED (fonction ast_set_flag);

  1. la trame déchiffrée est retournée

Remarque : lors du premier test de Flag (IAX_KEYPOPULATED), s’il est activé, on déchiffre la trame puis le résultat est retourné.

L’étude de cette fonction nous a permis de voir comment étaient générées les clés de chiffrement et de déchiffrement (fonction build_enc_keys).

Il faut remarquer que la transmission des clés n’est pas nécessaire, car leur construction repose sur :

  • le challenge qui est commun aux deux serveurs ;
  • le mot de passe de l’utilisateur TRUNK, lui aussi commun et connu en local par les deux entités.

Ce point est plutôt positif, car l’envoi des clés de chiffrement / déchiffrement, ou en général la communication du secret partagé, est souvent le talon d’Achille de tout système de chiffrement symétrique. Nous ne rentrerons pas dans le détail des sous-fonctions de déchiffrement : elles sont symétriques aux fonctions appelées pour le chiffrement.

Conclusion

Maintenant que nous avons étudié tout le processus de chiffrement et de déchiffrement utilisé par le protocole IAX2, nous pouvons faire le bilan et conclure sur sa fiabilité.

Nous savons que le chiffrement repose sur l’algorithme AES, avec une clé de 128 bits. Il à été établi que le mode chaîné (CBC) est utilisé, et qu’un vecteur d’initialisation est également mis en œuvre. Nous avons vu dans le code que ce vecteur était modifié tout au long d’une communication, et que les 32 derniers octets de voix étaient utilisés à cet effet. En considérant que la voix est un signal aléatoire, l’utiliser comme vecteur d’initialisation paraît une bonne idée.

De plus, ce vecteur ne circule pas en clair, mais est chiffré comme le reste des données. Il est important de le préciser car dans de nombreux cas où est utilisé l’AES en mode CBC, le vecteur est très souvent transmis en clair, ce qui peut permettre des attaques connues sur les IV.

Cela dit, le mécanisme permettant le renouvellement de l’IV interpelle. Comme les 32 derniers octets du paquet n, représentent les 32 premiers octets des données du paquet n+1, nous connaissons donc la partie claire et sa version chiffrée deux paquets consécutifs.

Cela nous permettrait peut être de monter une attaque de type « clair connu », qui par analyse linéaire, pourrait nous permettre de déchiffrer le reste des données. Cependant, à ce jour aucune attaque de ce type n’a été recensée pour l’AES. Seul un autre type d’attaque est envisageable, l’attaque par temps de réponse (au niveau processeur), mais cela est uniquement réalisable en laboratoire à ce jour.

Un autre point positif est l’utilisation et la génération des clés utilisées pour le chiffrement/déchiffrement des données.

Nous avons vu le mécanisme qui consiste à générer les clés à partir du mot de passe (qui est connu des deux entités communicantes) et du numéro d’appel qui est unique. Le fait que les clés soient générées en local et ne soient pas diffusées est un atout majeur. Car comme nous le savons, en matière de chiffrement symétrique, le point sensible réside dans la mise en commun de la clé.

Cela dit, un attaquant ayant la main sur un des serveurs pourrait récupérer le mot de passe, et en interceptant l’identifiant de l’appel pourrait générer des clés et déchiffrer la communication. Cependant, l’attaquant ayant la main sur un serveur, pourrait intercepter la communication de manière beaucoup plus simple …

Nous arrivons donc au principal point faible d’une communication en IAX2 : elle s’effectue point à point. Si un des serveurs est corrompu, la confidentialité des communications peut être compromise. De plus, au niveau de l’authentification, le choix MD5 est couramment utilisé mais présente un certain danger. En effet il est simple de compromettre l’authentification et d’usurper une quelconque identité, par le biais d’attaques connues.

Une solution consiste donc à appliquer une stratégie de sécurité rigoureuse concernant l’accès aux serveurs. Concernant le mécanisme d’authentification, il pourrait reposer sur RSA afin de garantir une meilleure sécurité.

Tout en gardant à l’esprit les capacités offertes par la version sécurisée de SIP (SIPS, SRTP), nous pouvons dire que le protocole IAX2 trouve aisément sa place au sein d’une architecture classique. Les avantages sont maintenant clairement identifiés : il permet d’obtenir un flux chiffré et offre une authentification forte via RSA, le tout en n’utilisant qu’un seul port UDP.

De plus, l’utilisation du codec GSM permet d’avoir des paquets chiffrés de taille raisonnable par rapport à SIPS par exemple, et offre une bonne qualité audio. Il parait intéressant de se pencher sur le chiffrement de la voix au niveau du codec, et non pas sur toutes les trames VoIP.

Ceci présente l’avantage de pouvoir chiffrer les communications avant même l’encapsulation dans un quelconque protocole, et ainsi d’avoir un flux chiffré de bout en bout bien réel, sans passage en clair au niveau des passerelles VoIP. Une autre idée envisageable est le développement d’un softphone IAX2 supportant l’authentification RSA, ainsi que le chiffrement AES 128 CBC.