Dans ce tutoriel, nous allons nous intéresser au partage de clé secrète et plus particulièrement, à l’algorithme de Shamir1. Ce que l’on veut dire par partage de clé secrète est que nous voulons chiffrer un message m et distribuer des parties à n personnes. Nous voulons ensuite que si k de ces n personnes (avec k < n) combinent leur partie, elles puissent récupérer le message m.
Afin d’aider à la compréhension, nous pouvons imaginer que nous sommes le patron d’une banque et nous voulons protéger le code (utilisons 5887 pour l’exemple) de notre coffre-fort. Nous avons 5 personnes de confiance à qui nous voulons distribuer une partie et si 3 d’entre d’elles combinent leur partie, elles devraient être en mesure de récupérer le code du coffre. Dans cet exemple que nous allons utiliser tout au long de ce tutoriel, m est le code du coffre (5887), n est le nombre de personnes de confiance (5) et k est le nombre de personnes suffisantes pour récupérer le code (3).
Dans le cadre de ce partage de clé, il est important que tout groupe de personnes composé de moins de k personnes ne puisse gagner aucune information sur notre code. Soit nous avons un nombre de clés suffisant et nous pouvons récupérer le code, soit ce n’est pas le cas et nous n’avons aucune information sur le code.
- L'approche naïve et ses défaults
- Les fonctions et les notations
- Fonction polynomiale
- Interpolation de Lagrange
- Génération des fragments
- Récupération du code à partir des fragments
- Place à l'arithmétique modulaire
- Comparaison avec l'approche naïve
L'approche naïve et ses défaults
Avant même de commencer, regardons d’abord à quoi ressemblerait une approche simple et naïve à ce problème et pourquoi nous avons besoin de quelque chose de plus puissant. Après tout, si une approche simple est suffisante, à quoi bon se casser la tête à élaborer un algorithme complexe pour arriver au même résultat ?
L’approche la plus simple est de diviser notre clé en autant de personnes que nécessaire. Par exemple, si nous utilisons notre code 5887, on pourrait le diviser en quatre (5, 8, 8 et 7) et donner chaque chiffre à une personne différente. Ensuite, il suffira à ces personnes à se réunir et elle pourront récupérer le code initial. Cette approche a cependant plusieurs défauts:
- Si l’une des quatre personnes perd son fragment de code ou si elle décède, elle est perdue à jamais et nous ne pourrons jamais reconstruire le code initial.
- Recevoir un fragment nous donne une information importante sur le code initial. Aussi, combiner deux ou trois de ces fragments nous donne de plus en plus de connaissances quant à ce code. Avec trois fragments, nous pouvons même deviner le code initial en ayant une chance sur 10 d’avoir raison. Or, nous voulons vraiment éviter ce scénario ! Nous ne voulons donner des informations sur le code initial que si nous avons tous les fragments nécessaires (ici, quatre fragments).
Bien sûr, le premier défaut peut être plus ou moins amélioré en améliorant un peu notre stratégie mais le deuxième est beaucoup plus difficile à régler. C’est principalement pour cette raison que nous allons devoir faire appel à des algorithmes plus avancés.
Les fonctions et les notations
Fonctions
Avant de se lancer dans le vif du sujet, il nous faut faire un rapide rappel de mathématiques, en commençant par les fonctions.
Wikipedia propose la définition suivante:
En mathématiques, une fonction permet de définir un résultat (le plus souvent numérique) pour chaque valeur d’un ensemble appelé domaine.
Ici, nous allons nous intéresser au domaine des nombres réels . Ainsi, par exemple, ceci est une fonction:
Pour chaque valeur de notre domaine , cette fonction définit un résultat . Par exemple, pour la valeur , le résultat de notre fonction est 4. Et ceci peut être calculé pour tout .
x | f(x) |
---|---|
0 | 0 |
1 | 1 |
2 | 4 |
3 | 9 |
3,5 | 12.15 |
4 | 16 |
10 | 100 |
… | … |
Nous pouvons aussi représenter cette fonction sur un graphe à deux dimensions où l’axe horizontal représentera les valeurs que prend tandis que l’axe vertical représentera les valeurs que prend . Pour noter les points sur un tel graphe, nous utilisons la notation où est la valeur de l’abscisse (la distance horizontale du point à l’origine) et est la valeur de l’ordonnée (la distance verticale du point à l’origine).
Et nous pouvons bien voir que la courbe tracée passe par les points calculés dans le tableau défini plus haut.
Notations
Avant de continuer, il nous faut aussi rapidement introduire deux notations qui seront utilisées au long de ce tutoriel : et . Il s’agit respectivement des signes de somme et de produit. Par exemple, plutôt que d’écrire nous allons plutôt écrire
Et de même pour le produit où nous allons préférer
à .
Pour les programmeurs d’entre vous, vous pouvez voir ces opérations comme des boucles effectuant des additions ou des multiplications.
Un autre symbole que nous utiliserons dans ce tutoriel est qui signifie "pour tout". Ainsi, si nous écrivons , il faut le lire comme "la fonction vaut pour tout dans ".
Fonction polynomiale
Une famille particulière de fonctions que nous allons utiliser dans ce tutoriel est celle des fonctions polynomiales. Voyons ce que Wikipedia peut nous dire à leur propos1:
En mathématiques, une fonction polynomiale (parfois appelée fonction polynôme) est une fonction obtenue en évaluant un polynôme.
Je vous l’accorde, ça ne nous avance pas beaucoup… Pour être plus concret, une fonction polynomiale est une fonction qui peut s’écrire sous la forme
où les valeurs sont appeléss coefficients et sont des valeurs réelles ou complexes.
Pour vous aider à comprendre ce qu’est une fonction polynomiale, voici quelques exemples:
Et nous pouvons vérifier qu’il s’agit bien d’une fonction polynomiale car où .
Et nous pouvons vérifier qu’il s’agit bien d’une fonction polynomiale car où .
Par contre, le fonction suivante n’est pas polynomiale.
En effet, ne peut pas s’écrire sous la forme .
Interpolation de Lagrange
Maintenant que nous savons ce qu’est une fonction et que nous savons trouver tous les points par lesquels elle passe, nous allons nous intéresser à l’opération inverse : comment trouver une fonction polynomiale passant par certains nombres distincts donnés ?
Pour trouver ce polynôme, nous allons appliquer une méthode appelée l’interpolation de Lagrange. Celle-ci se base sur le fait que si nous partons de n+1 points , il est toujours possible de trouver une et une seule fonction polynomiale de degré maximal n qui passe par tous ces points.
Par exemple, si nous partons des points et appliquons l’interpolation de Lagrange, nous serons en mesure de retrouver le polynôme de degré 2 passant par tous ces points (en l’occurrence, ).
Nous allons d’abord donner l’approche générique en termes mathématiques puis l’illustrer avec un exemple. Si vous n’arrivez pas à suivre la partie stricte, jetez un œil à l’exemple, il devrait vous aider à comprendre comment cette méthode fonctionne.
Pour trouver ce polynôme, nous allons d’abord calculer les polynômes de Lagrange qui s’écrivent de la façon suivante:
Pour chacun de ces polynômes, il faut noter les choses suivantes:
- Pour chaque où , . En effet, pour ces valeurs, le numérateur s’annulera pour l’une des fractions présentes dans le produit.
- Pour , . Ceci vient du fait que dans ce cas, le numérateur et le dénominateur sont égaux.
Maintenant, nous pouvons utiliser ces polynômes de Lagrange et les combiner afin de trouver le polynôme de degré maximal n passant par tous les n+1 points:
En utilisant les propriétés des polynômes de Lagrange présentées ci-dessus, nous pouvons aisément conclure que .
Prenons un exemple pour illustrer cette méthode. Nous allons utiliser les 3 points suivants: , et . En utilisant ces points, nous pouvons calculer les polynômes de Lagrange suivants:
Et nous pouvons donc trouver le polynôme passant par les points donnés:
Nous pouvons nous convaincre que ce résultat est correct en vérifiant que :
La dernière chose qu’il nous reste à prouver est que le polynôme trouvé par cette méthode est bien unique.
Pour ce faire, nous allons imaginer l’existence d’un autre polynôme passant également par les points . Pour chacun de ces points, nous avons donc et (où ). Nous pouvons aussi définir une fonction et réaliser que
Cela signifie que est un polynôme de degré valant pour valeurs de X1. Or il n’existe qu’un seul polynôme pour lequel ceci peut être vrai: le polynôme . Or, si , cela signifie que . Ce qui prouve que le polynôme trouvé via la méthode d’interpolation lagrangienne est le seul polynôme de degré maximal passant par les points donnés.
Génération des fragments
Tout d’abord, nous allons nous attarder à la génération des fragments. Pour rappel, les fragments sont les informations qui seront données aux personnes de confiance et qui pourront être combinées pour récupérer le code initial.
Pour ce faire, nous allons construire une fonction polynomiale de degré . Pour rappel, k est le nombre de clés suffisant à fournir lors de l’étape de reconstruction du code.
Cette étape est relativement simple, notre fonction étant polynomiale, elle s’écrit sous la forme
Nous pouvons choisir les termes de façon aléatoire dans 1 et nous utilisons le code m pour . Ainsi, cela nous donne . Avec cette fonction, nous pouvons générer un nombre n de fragments au format où l’on choisira incrémentalement en partant de 1.
Tout ceci peut paraitre très abstrait, utilisons donc notre exemple de base où m = 5887, k = 3 et n = 5. Nous devons donc générer un polynôme de degré 2 sous la forme . Nous pouvons choisir et aléatoirement. Utilisons et . Cela nous donne
Maintenant que nous avons notre fonction polynomiale, il ne nous reste plus qu’à générer nos 5 fragments:
Ces fragments peuvent maintenant être distribués aux 5 personnes de confiance sans qu’aucun de ces fragments individuellement ne dévoile quoi que ce soit quant à la valeur du code secret.
- Si vous vous demandez comment choisir un nombre aléatoirement dans et si c’est même possible, bravo à vous, vous avez détecté un souci dans cette méthode. Nous y reviendrons plus loin dans ce tutoriel.↩
Récupération du code à partir des fragments
Très bien, nous avons 5 fragments, comment pouvons-nous maintenant récupérer le code secret en utilisant 3 de ces fragments ? C’est ici que l’interpolation lagrangienne va rentrer en jeu.
Notre code de déchiffrement va recevoir un nombre k de fragments . À partir de ces fragments, il est possible de retrouver l’unique fonction polynomiale de degré maximal passant par ces k points comme présenté plus tôt dans ce tutoriel. Nous pouvons calculer les polynômes de Lagrange puis les utiliser pour trouver la fonction P(X) passant par ces points. Cette fonction sera de la forme
Or, lors de l’étape de génération des fragments, on a décidé que notre code secret était utilisé pour la valeur . TADA ! Notre code est récupéré et on peut le retourner à nos 3 personnes de confiance.
Une fois encore, réutilisons notre exemple pour mieux visualiser ceci. Nous allons utiliser les fragments (n’importe quelle autre combinaison de 3 fragments fonctionnerait aussi). Calculons maintenant nos polynômes de Lagrange:
Et avec ces polynômes, on peut calculer notre fonction P:
Et notre code secret est bien 5887 !
Place à l'arithmétique modulaire
Cette partie n’est pas nécessaire pour comprendre le cœur de la méthode de Shamir mais elle est indispensable pour que cette méthode soit strictement correcte et que sa sécurité soit garantie. Elle implique cela dit des calculs un peu plus compliqués que ce que nous avons vu jusqu’ici. Si vous n’arrivez pas à suivre cette section, ne vous en faites pas, elle ne bouleverse pas l’idée de base que nous avons utilisée plus haut.
Il est maintenant temps pour nous de revenir sur une note mentionnée plus tôt dans ce tutoriel. Nous avons énoncé que les coefficients étaient choisis aléatoirement dans . Comment faire cela en pratique ? La réponse est très simple : c’est impossible. est un ensemble infini, il nous est donc impossible de choisir des élements de façon uniformément aléatoire. Pour y remédier, nous allons passer à une autre forme d’arithmétique : l’arithmétique modulaire.
Nous allons donc plus choisir nos coefficients dans mais plutôt dans , c’est à dire l’ensemble des nombres entiers modulo p.
C’est quoi un modulo ?
Modulo, c’est l’opération qui nous donne le reste d’une division. Par exemple, car ( est le reste de la division de 17 par 5). Faire de l’arithmétique dans plutôt que dans ou signifie qu’on se limite aux nombres entre 0 et p-1.
Pour continuer à utiliser notre exemple, regardons quelques opérations dans :
Notre ensemble est donc maintenant fini et il est possible de choisir des nombres aléatoirement et de façon uniforme dans cette ensemble.
Il ne nous reste plus qu’à préciser un détail : pour que notre interpolation lagrangienne fonctionne correctement en arithmétique modulaire, il nous faudra choisir un nombre premier. Nous avons déjà abordé pas mal de concepts mathématiques et prouver cette propriété serait assez compliqué. Donc pour ne pas trop dévier du sujet initial, cette affirmation ne sera pas démontrée ici mais vous pouvez essayer le démontrer vous-mêmes.
Qu’est-ce que ça change pour notre algorithme ?
Pour voir comment ceci affecte notre algorithme, prenons un nombre premier , par exemple 6301. Nous pouvons donc recalculer nos clés en utilisant la formule suivante:
Maintenant que nous avons notre fonction polynomiale, il ne nous reste plus qu’à générer nos 5 clés:
Lorsque nous fournirons les clés , l’algorithme de récupération générera les mêmes polynômes de Lagrange mais le calcul du polynôme P changera comme suit:
Et nous voyons que nous sommes bien parvenus à retrouver le bon polynôme en utilisant l’arithmétique modulaire.
Comparaison avec l'approche naïve
Revenons à ce que nous avons mentionné au début de ce tutoriel quant aux défauts d’une approche naïve. Avec l’algorithme de Shamir, nous pouvons générer autant de fragments que l’on souhaite et nous pouvons définir un seuil de fragments nécessaires à la récupération du code. Donc nous ne craignons plus qu’un fragments soit perdu par mégarde : n’importe quelle combinaison de fragments, tant qu’il y en assez, nous permettra de récupérer le code initial.
Regardons maintenant le second et principal défaut de l’approche naïve : cette approche nous donnait de plus en plus d’information au fur et à mesure que l’on combinait les fragments ensemble. Or ce que nous cherchons est une approche de tout ou rien : soit nous n’avons pas assez de fragments et je ne peux rien dire quant au code initial, soit j’en ai assez et je peux récupérer ce code.
Ainsi, pour pouvoir affirmer que notre algorithme est sûr, nous devons montrer que si quelqu’un possède au plus clés, il n’est pas plus proche de récupérer le message secret qu’une personne n’en ayant aucune1.
Heureusement, pour notre algorithme, c’est bel et bien le cas ! En effet, avec clés, le hacker connaitrait points par lesquels notre polynôme passe mais nous savons que notre polynôme est de degré . Or, dans , il existe polynômes passant par ces points, correspondant à autant de distincts (c’est-à-dire à des codes secrets distincts). Ainsi, même avec points, on en est au même point qu’un hacker sans le moindre fragment: chacun des codes dans est équiprobable. Le hacker n’a ainsi gagné aucune information sur le polynôme utilisé pour créer les clés et notre algorithme est bel et bien correct et sûr.
La sécurité de l’algorithme repose également sur le fait que lorsque nous choisissons les paramètres du polynôme lors de la génération des clés, nous choisissons ceux-ci de façon aléatoires dans . En pratique, cette contrainte n’est pas aussi simple à réaliser qu’il n’y parait mais l’étude de la génération de nombres aléatoires est trop vaste et complexe pour être abordée dans le cadre de ce tutoriel.
- Si vous voulez une explication plus en profondeur sur cette définition de la sécurité d’un algorithme, vous pouvez lire cet article de Bermudes.↩
Voilà, grâce à la méthode de Shamir, nous avons maintenant une méthode nous permettant de partager une clé secrète entre plusieurs personnes, sans compromettre la sécurité de notre système. Donc si vous avez un coffre-fort ou une bombe nucléaire en votre possession, vous êtes maintenant en mesure de partager leur code à plusieurs proches de confiance. En espérant qu’ils en fassent bonne utilisation.
Si vous voulez approfondir le sujet, je vous invite à visiter la page Wikipedia. Malheureusement, la page francophone est assez difficile à suivre donc je vous invite plutôt à vous diriger vers la page anglophone qui est plutôt bien expliquée et qui contient même un exemple d’implémentation en Python.
Pour finir, un grand merci à @Lucas-84, @Gabbro et @melepe pour leurs retours durant la beta et la validation de ce tutoriel.