Chiffrement à l'aide des bibliothèques de cryptographie d'OpenSSL

Linux Gazette n°87 - Février 2003

Vinayak Hegde


Table des matières
1. Motivation pour cet article
2. Quelques informations de base
3. Générer la clé
4. La routine de chiffrement
5. La routine de déchiffrement
6. Le code complet
7. Un exemple d'application - Une messagerie instantanée sécurisée
8. Ressources

1. Motivation pour cet article

Linux est déjà bien ancré dans le monde de l'entreprise. Une des exigences permanentes de ce secteur a été la nécessité d'une meilleure sécurité des données. C'est là où le chiffrement entre en scène, pour masquer à un intrus des données sensibles. Les logiciels « open source » ont la réputation d'être développés de manière sécurisée. Cet article constitue une étape dans cette direction.

La libcrypto d'OpenSSL est réellement une excellente bibliothèque si vous souhaitez utiliser le chiffrement sans vous soucier des détails de mise en œuvre sous-jacente de l'algorithme. Le problème réside dans la documentation, très réduite. Vous pouvez évidemment lire les sources et découvrir de quoi il retourne. De plus, le fait que les noms de fonctions sont assez intuitifs aide dans une certaine mesure. Un autre moyen d'obtenir de l'aide est de contacter les listes de discussion sur le site web d'OpenSSL. Toutefois, les outils en ligne de commande d'OpenSSL sont parfaitement bien documentés et faciles à utiliser. J'expliquerai dans cet article comment utiliser l'algorithme de chiffrement blowfish à l'aide des bibliothèques de cryptographie d'OpenSSL.


2. Quelques informations de base

Aux premières heures de la cryptographie, les algorithmes étaient secrets, au même titre que les clés. La tendance de nos jours a cependant changé. Dorénavant, les algorithmes sont publiquement connus et les clés gardées secrètes. Le meilleur exemple en est l'algorithme RSA (du nom de ses inventeurs : Ron Rivest, Adi Shamir, Len Adleman) amplement connu et mis en œuvre. Les clés publiques sont connues de tout le monde, mais les clés privées sont gardées secrètes. RSA est un algorithme asymétrique car il n'utilise pas la même clé pour le chiffrement et le déchiffrement. De plus, il n'est généralement pas souhaitable d'utiliser RSA en tant que tel pour décrypter de grandes quantités de données, car il consomme beaucoup de temps machine.

Pour chiffrer de grandes quantités de données, des algorithmes moins exigeants en temps machine ont la préférence. Dans cet article, nous utilisons l'algorithme blowfish pour décrypter et dédécrypter des données. C'est un algorithme symétrique, ce qui signifie qu'il utilise la même clé pour le chiffrement et le déchiffrement. Blowfish, conçu par le célèbre cryptographe Bruce Schneier, est un algorithme rapide pour le chiffrement/déchiffrement.


3. Générer la clé

À des fins de démonstration, nous emploierons une clé de 128 bits. Cette dernière est enregistrée sous forme de tableau de caractères dans le programme. Nous génèrerons également un vecteur d'initialisation de 64 bits (IV). Pour notre programme, nous ferons appel au mode CBC (Cipher Block Chaining). En outre, nous ne ferons pas appel aux fonctions de blowfish directement, mais au travers d'une interface de plus haut niveau.

Un vecteur d'initialisation est un bit d'information aléatoire fourni en entrée dans les algorithmes de chiffrement chaînés, en d'autres termes, lorsque chaque étape de chiffrement d'un bloc de données entrées fournit des entrées au chiffrement du bloc suivant (blowfish utilise des blocs de 64 bits pour le chiffrement). Le « IV » fournit le premier bit d'entrée pour chiffrer le premier bloc de données, qui fournit ensuite l'entrée destinée au deuxième bloc, et ainsi de suite. Le bit restant à la fin est ignoré.

Les bits aléatoires sont générés depuis le fichier spécial en mode caractère /dev/random qui constitue une précieuse source de nombres aléatoires. Reportez-vous à la page de manuel pour plus d'informations.

int
generate_key ()
{
        int i, j, fd;
        if ((fd = open ("/dev/random", O_RDONLY)) == -1)
                perror ("erreur d'ouverture");

        if ((read (fd, key, 16)) == -1)
                perror ("erreur de lecture de la clé");

        if ((read (fd, iv, 8)) == -1)
                perror ("erreur de lecture de iv");
        
        printf("128 bit key:\n");
        for (i = 0; i < 16; i++)
                printf ("%d \t", key[i]);
        printf ("\n ------ \n");

        printf("Vecteur d'initialisation\n");
        for (i = 0; i < 8; i++)
                printf ("%d \t", iv[i]);

        printf ("\n ------ \n");
        close (fd);
        return 0;
}

4. La routine de chiffrement

La routine de chiffrement admet deux paramètres : les descripteurs de fichiers du fichier d'entrée et du fichier de sortie pour lesquels les données chiffrées vont être enregistrées. Il est toujours judicieux de remplir de zéros vos tampons à l'aide des commandes memset() ou bzero() avant d'utiliser les tampons contenant des données. Ceci est particulièrement important si vous projetez de réutiliser les tampons. Dans le programme ci-dessous, les données fournies en entrée vont être chiffrées en blocs de 1 ko chacun.

Voici les étapes du chiffrement :

  1. Créer un contexte de chiffrement

  2. Initialiser le contexte de chiffrement avec les valeurs « clé » et « IV ».

  3. Appeler EVP_EncryptUpdate pour chiffrer des blocs successifs de 1 ko chacun

  4. Appeler EVP_EncryptFinal pour chiffrer les données restantes (« leftover »)

  5. Pour finir, appeler EVP_CIPHER_CTX_cleanup pour éliminer toutes les informations sensibles de la mémoire

Vous pouvez vous demander ce que sont les données « leftover ». Comme mentioné ci-dessus, blowfish chiffre les informations en blocs de 64 bits chacun. Parfois, il n'est pas possible d'avoir 64 bits pour constituer un bloc. Ceci peut se produire si la taille du tampon dans le programme ci-dessous ou la taille des données du fichier ou des données d'entrée n'est pas un multiple intégral de 8 octets (64 bits). Les données sont sont donc complétées en conséquence, puis le bloc partiel est chiffré à l'aide de EVP_EncryptFinal. La longueur du bloc de données encodé est enregistrée dans la variable tlen et ajoutée ensuite à la longueur finale.

int
encrypt (int infd, int outfd)
{
        unsigned char outbuf[OP_SIZE];
        int olen, tlen, n;
        char inbuff[IP_SIZE];
        EVP_CIPHER_CTX ctx;
        EVP_CIPHER_CTX_init (& ctx);
        EVP_EncryptInit (& ctx, EVP_bf_cbc (), key, iv);

        for (;;)
          {
                  bzero (& inbuff, IP_SIZE);

                  if ((n = read (infd, inbuff, IP_SIZE)) == -1)
                    {
                            perror ("erreur de lecture²");
                            break;
                    }
                  else if (n == 0)
                          break;

                  if (EVP_EncryptUpdate (& ctx, outbuf, & olen, inbuff, n) != 1)
                    {
                            printf ("erreur dans la mise à jour du chiffrement\n");
                            return 0;
                    }

                  if (EVP_EncryptFinal (& ctx, outbuf + olen, & tlen) != 1)
                    {
                            printf ("erreur dans le chiffrement final\n");
                            return 0;
                    }
                  olen += tlen;
                  if ((n = write (outfd, outbuf, olen)) == -1)
                          perror ("erreur d'écriture");
          }
        EVP_CIPHER_CTX_cleanup (& ctx);
        return 1;
}

5. La routine de déchiffrement

La routine de déchiffrement suit dans l'ensemble les mêmes étapes que la routine de chiffrement. Le code suivant montre la manière dont le déchiffrement est effectué.

int
decrypt (int infd, int outfd)
{
        unsigned char outbuf[IP_SIZE];
        int olen, tlen, n;
        char inbuff[OP_SIZE];
        EVP_CIPHER_CTX ctx;
        EVP_CIPHER_CTX_init (& ctx);
        EVP_DecryptInit (& ctx, EVP_bf_cbc (), key, iv);

        for (;;)
          {
                  bzero (& inbuff, OP_SIZE);
                  if ((n = read (infd, inbuff, OP_SIZE)) == -1)
                    {
                            perror ("erreur de lecture");
                            break;
                    }
                  else if (n == 0)
                          break;

                  bzero (& outbuf, IP_SIZE);

                  if (EVP_DecryptUpdate (& ctx, outbuf, & olen, inbuff, n) != 1)
                    {
                            printf ("erreur dans la mise à jour du déchiffrement\n");
                            return 0;
                    }

                  if (EVP_DecryptFinal (& ctx, outbuf + olen, & tlen) != 1)
                    {
                            printf ("erreur dans le déchiffrement final\n");
                            return 0;
                    }
                  olen += tlen;
                  if ((n = write (outfd, outbuf, olen)) == -1)
                          perror ("erreur d'écriture");
          }

        EVP_CIPHER_CTX_cleanup (& ctx);
        return 1;
}

6. Le code complet

Un programme interactif minimal mettant en œuvre les routines ci-dessus est disponible en téléchargement ici. La commande pour compiler le programme est la suivante :

# gcc -o blowfish sym_funcs.c -lcrypto

Le programme admet trois fichiers sur la ligne de commande :

  1. Le fichier à chiffrer

  2. Le fichier dans lequel les données chiffrées vont être enregistrées

  3. Le fichier dans lequel les données déchiffrées vont être enregistrées

N'oubliez pas de générer une clé avant de procéder au chiffrement !


7. Un exemple d'application - Une messagerie instantanée sécurisée

Imaginez un logiciel de messagerie instantanée (IM Instant Messenger) qui souhaite communiquer avec un autre logiciel de messagerie instantanée de manière sécurisée. L'approche suivante pourrait être suivie.

  1. Chaque client de l'IM a sa propre clé publique et privée.

  2. Le client de l'IM a les clés publiques de toutes les IMs avec lesquelles il souhaite communiquer.

  3. La clé de session est générée par le client qui initie la connexion. Cette clé de session sert à chiffrer les messages entre les deux clients.

  4. La clé de session est chiffrée et échangée entre deux clients ou plus, à l'aide du chiffrement par clé publique (par exemple, l'algorithme RSA). Ainsi l'authentification est également prise en compte.

  5. L'échange de données chiffrées (à l'aide du chiffrement asymétrique Blowfish) a lieu ensuite entre les différents clients, après cet « établissement de liaison sécurisée » (security handshake).


8. Ressources

Page web d'OpenSSL

L'algorithme Blowfish

Manuel de la cryptographie appliquée

Ma vie a changé depuis que j'ai découvert Linux. Les ordinateurs sont soudain devenus intéressants car je pouvais tenter diverses expériences sur ma machine Linux grâce à la disponibilité du code source. Mes centres d'intérêt se situent principalement dans le domaine de la gestion de réseau, des systèmes embarqués et des langages de programmation. Je travaille actuellement pour Aparna Web Services où nous rendons Linux accessible aux universités et aux entreprises, en configurant des stations de démarrage distantes (clients légers).

Copyright © 2003, Vinayak Hegde.

Copying license http://www.linuxgazette.com/copying.html

Paru dans le n°87 de la Linux Gazette de février 2003.

Traduction française par Sébastien Tricaud .

Relecture de la traduction française par Joëlle Cornavin .