26 novembre 2020 â Cryptographie, Go, Diffie-Hellman
Le protocole TLS (anciennement SSL) permet de sĂ©curiser les communications sur Internet â authentification, contrĂŽle dâintĂ©gritĂ© et confidentialitĂ© des Ă©changes.
Ce protocole dĂ©finit notamment un handshake au cours duquel client et serveur sâaccordent sur le choix de la suite cryptographique Ă utiliser pour sĂ©curiser la communication ultĂ©rieure et Ă©changent des clefs cryptographiques.
La page SSL Labs permet de scanner un serveur et de déterminer les versions de TLS et des suites cryptographiques (cipher suites) supportées.
Voici un extrait du résultat pour lipsum.dev :
On constate en particulier que le format des suites cryptographiques a changé avec TLS 1.3.
Dans la RFC 8446 qui définit ce protocole il est indiqué :
Static RSA and Diffie-Hellman cipher suites have been removed; all public-key based key exchange mechanisms now provide forward secrecy.
Nous proposons ici de présenter quelques notions mathématiques qui permettent la mise en oeuvre de cette confidentialité persistante (forward secrecy).
En particulier, TLS 1.3 repose sur des variantes dâun mĂ©canisme dâĂ©change de clefs imaginĂ© en 1976 et utilisant des concepts mathĂ©matiques issus de la thĂ©orie des groupes.
Une des caractĂ©ristiques des mathĂ©matiques est lâabstraction.
Cette abstraction consiste parfois à observer différents ensembles, les opérations associées et en déduire des structures communes.
Lâune des structures algĂ©briques les plus classiques est celle de groupe, qui intervient dans de nombreuses branches des mathĂ©matiques mais aussi en physique thĂ©orique, cristallographie, etc.
Un groupe est un ensemble muni dâune opĂ©ration (notĂ©e ici ) qui permet dâeffectuer des calculs sur cet ensemble en associant Ă deux Ă©lĂ©ments un troisiĂšme Ă©lĂ©ment de .
Pour que cette opération définisse une structure de groupe, on exige :
Lâexponentiation en base consiste Ă appliquer lâopĂ©ration de façon itĂ©rĂ©e Ă un Ă©lĂ©ment dâun groupe .
On définit ainsi puis par récurrence pour .
Pour calculer , on peut naĂŻvement effectuer des multiplications successives pour calculer la suite .
Si lâon procĂšde ainsi, le nombre de multiplications Ă effectuer est .
Il existe une approche beaucoup plus efficace qui consiste Ă multiplier les rĂ©sultats intermĂ©diaires par eux-mĂȘmes.
Par exemple, pour calculer , on peut remarquer que (notation binaire) puis :
Ce calcul fait intervenir multiplications au lieu des de la mĂ©thode naĂŻve. Pour un exposant , ce nombre de multiplications est de lâordre de .
Notons que cet algorithme dâexponentiation rapide ne fait aucune hypothĂšse sur le groupe dont il est question ni sur la loi de composition .
đ»  En voici une implĂ©mentation (en Go), dans le cas classique de la multiplication des entiers :
// Computes a^n in O(log n) stepsfunc Exponentiate(a int, n int) int {res := 1for n > 0 {if n%2 == 1 {res *= an = (n - 1) / 2} else {n /= 2}a *= a}return res}
Pour un groupe et un Ă©lĂ©ment de ce groupe, le problĂšme du logarithme en base consiste Ă rĂ©soudre lâĂ©quation dâinconnue .
Suivant les cas, il existera zĂ©ro, une ou plusieurs solutions Ă cette Ă©quation â parfois une infinitĂ©.
Un groupe cyclique est un cas particulier de groupe qui a les propriétés additionnelles suivantes :
On a alors et nécessairement (je vous laisse le prouver) .
Le logarithme (discret) en base sur un groupe cyclique Ă Ă©lĂ©ments et gĂ©nĂ©rĂ© par peut ĂȘtre dĂ©fini comme une fonction qui vĂ©rifie pour tout .
Cette relation ne donne a priori ni formule explicite de calcul ni algorithme autre quâune mĂ©thode par force brute consistant Ă tester successivement avec .
On effectue alors en moyenne de lâordre de opĂ©rations dâexponentation.
Il est cependant possible de le calculer de façon plus efficace :
Ce paragraphe visant à présenter cet algorithme est facultatif, vous pourrez librement y revenir ensuite.
Cet algorithme proposĂ© en 1971 permet dâobtenir en un nombre dâopĂ©rations de lâordre de .
Notons un groupe cyclique Ă Ă©lĂ©ments et lâarrondi supĂ©rieur de (notĂ© ).
Définissons alors :
Lâapplication dĂ©finie par est surjective.
Je vous laisse vous en convaincre en exercice, en considérant pour quelconque la division euclidienne de par .
équivaut à résoudre soit
On est donc amené à chercher une collision entre les séquences et , qui contiennent chacun éléments.
On sait résoudre ce problÚme en opérations (par exemple avec une table de hachage).
Les indices obtenus vérifient alors ce qui résoud le problÚme du logarithme discret.
En 1976 est publié un article intitulé New Directions in Cryptography.
Cet article dĂ©finit lâun des premiers mĂ©canismes cryptographiques dâĂ©change de clef, qui porte le nom dâĂ©change de clĂ©s Diffie-Hellman.
Ce mĂ©canisme permet Ă deux personnes, que nous appellerons Amandine et Bastien, dâĂ©tablir une clef secrĂšte S que eux seuls connaissent en Ă©changeant sur un canal non sĂ©curisĂ©.
Nous verrons plus loin comment ce mécanisme est utilisé en pratique dans le protocole TLS.
Ă lâissue de cet Ă©change, Amandine et Bastien ont tous deux connaissances du secret partagĂ© .
Les clefs et circulant sur des canaux non sĂ©curisĂ©s, elles peuvent ĂȘtre utilisĂ©es par une tierce personne pour tenter dâobtenir le secret .
Si cette personne est capable de calculer lâun des logarithmes discrets ou , elle peut alors obtenir lâun des exposants secrets ou et recalculer .
La rĂ©solution du logarithme discret est donc un vecteur dâattaque potentiel contre le mĂ©canisme de Diffie-Hellman.
Bien que cela ne soit pas prouvĂ© dans le cas gĂ©nĂ©ral, on considĂšre dâailleurs que Diffie-Hellman est aussi sĂ»r que le problĂšme du logarithme discret.
La difficulté de calcul du logarithme sera donc déterminante dans le choix du groupe cyclique utilisé en pratique.
â ïž Les implĂ©mentations naĂŻves prĂ©sentĂ©es dans cet article ne doivent Ă©videmment pas ĂȘtre utilisĂ©es en production, leur but est purement didactique.
Nous proposons ci-dessous une implémentation avec différents groupes cycliques.
Cette implĂ©mentation est Ă©crite en Go, dont nous utiliserons le module crypto, mais il nâest pas nĂ©cessaire dâĂȘtre familier avec ce langage pour lire la suite.
CyclicGroup
Lâinterface ci-dessous dĂ©finit ce quâon attendra dâune implĂ©mentation dâun groupe cyclique.
On y retrouve les notions suivantes :
Identity
, Compose
, Inverse
qui correspondent aux axiomes dâun groupe (Ă©lĂ©ment neutre, loi de composition interne et inversion)Order
, Generator
qui correspondent au cas dâun groupe cycliquetype CyclicGroup interface {Identity() interface{}Compose(a, b interface{}) interface{}Inverse(a interface{}) interface{}Exponentiate(a interface{}, exp *big.Int) interface{}Generator() interface{}Order() *big.IntString() string}
đĄ On a utilisĂ© des empty interface (qui peuvent contenir nâimporte quelle valeur) pour typer les Ă©lĂ©ments du groupe, dont nous ne savons rien a priori. Dans dâautres langages, on pourrait utiliser des types gĂ©nĂ©riques (inexistant en Go).
Rappelons quelques définitions vues dans un article précédent.
On note lâensemble des nombres entiers modulo ( dĂ©signe un entier positif quelconque).
Compter dans revient à compter avec les nombres en considérant tous les calculs comme effectués modulo .
Par exemple, modulo , on a et la table de multiplication de est la suivante :
x | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
2 | 0 | 2 | 4 | 6 | 1 | 3 | 5 |
3 | 0 | 3 | 6 | 2 | 5 | 1 | 4 |
4 | 0 | 4 | 1 | 5 | 2 | 6 | 3 |
5 | 0 | 5 | 3 | 1 | 6 | 4 | 2 |
6 | 0 | 6 | 5 | 4 | 3 | 2 | 1 |
On sait que lorsque est un nombre premier les Ă©lĂ©ments de diffĂ©rents de sont tous inversibles pour la multiplication : est un corps que lâon notera aussi .
On remarque également sur ce particulier que permet de générer les éléments non nuls de  :
, , , , , .
De fait, on montre quâil existe toujours au moins un nombre dans qui permet de gĂ©nĂ©rer tous les Ă©lĂ©ments non nuls par exponentiation, comme câest le cas de dans .
Un tel nombre est appelé une racine primitive modulo .
DĂšs lors, lâensemble des Ă©lĂ©ments non nuls de muni de la multiplication est un groupe cyclique, dâordre .
MultGroup
Voici ci-dessous une implémentation trÚs simple des groupes multiplicatifs modulo .
Notons que lâon utilise le type big.Int pour manipuler des entiers de taille arbitraire.
type MultGroup struct {P *big.IntGen *big.Int}func (group *MultGroup) Order() *big.Int {// P - 1return new(big.Int).Add(group.P, big.NewInt(-1))}func (group *MultGroup) Identity() interface{} {return big.NewInt(1)}func (group *MultGroup) Compose(a, b interface{}) interface{} {// a * b % Preturn new(big.Int).Mod(new(big.Int).Mul(a.(*big.Int), b.(*big.Int)),group.P,)}func (group *MultGroup) Inverse(a interface{}) interface{} {return new(big.Int).ModInverse(a.(*big.Int), group.P)}func (group *MultGroup) Exponentiate(a interface{}, exp *big.Int) interface{} {return new(big.Int).Exp(a.(*big.Int), exp, group.P)}func (group *MultGroup) Generator() interface{} {return group.Gen}
đĄ On utilise lâexponentiation modulaire des entiers implĂ©mentĂ©e nativement en Go, plus efficace que lâexponentiation rapide gĂ©nĂ©rique prĂ©sentĂ©e prĂ©cĂ©demment du fait dâoptimisations spĂ©cifiques au calcul modulaire.
Je vous propose une implĂ©mentation naĂŻve (cliquez sur le bouton Run â¶ïž pour lâexĂ©cuter) de Diffie-Hellman en Go.
Le premier résultat utilise le groupe multiplicatif du corps  :
---Diffie-Hellman: F_1291799 multiplicative groupGroup order 1291798Generator 17---Amandine generated secret key:1266948Amandine transmitted (public) key:91778Bastien generated secret key:81716Bastien transmitted (public) key:1089774Amandine computed shared secret:489232Bastien computed shared secret:489232
Ăvidemment, ce groupe dâordre nâest pas Ă utiliser en pratique.
Je laisse dâailleurs aux lecteurs et lectrices le soin dâimplĂ©menter lâalgorithme baby-step giant-step dĂ©crit plus haut pour casser le secret ci-dessusâŠ
Notons que les groupes multiplicatifs associĂ©s aux corps finis sont le choix dâorigine pour lâĂ©change de Diffie-Hellman et quâils sont encore utilisĂ©s aujourdâhui, avec des groupes dont le paramĂštre est sur 2048 bits ou plus.
LâĂ©quation dâinconnues dans le corps (avec ) dĂ©finit une courbe elliptique sur .
Le sujet est bien trop vaste pour une vraie présentation dans le cadre de cet article, remarquons simplement ceci :
đĄ Parmi les courbes qui ont ces propriĂ©tĂ©s, les courbes elliptiques ont de plus un genre Ă©gal Ă . Le genre dĂ©signe ici une notion de gĂ©omĂ©trie algĂ©brique qui coĂŻncide dans le cas des courbes complexes avec une notion topologique assez visuelle : les courbes elliptiques sur ont la mĂȘme topologie quâun donut.
Ă toute courbe elliptique peut ĂȘtre associĂ©e une loi de groupe.
Cette loi est commutative et associe à deux points et de la courbe un troisiÚme point noté .
LâidĂ©e gĂ©nĂ©rale est quâen considĂ©rant deux points situĂ©s sur cette courbe, on peut en produire un troisiĂšme par un calcul algĂ©brique.
Sans rentrer dans les dĂ©veloppements, deux points et dâune courbe plane dĂ©finissent une droite â lâunique droite qui passe par et (si , on considĂšre la droite tangente Ă la courbe en ce point).
Cette droite admet une Ă©quation de la forme oĂč .
La recherche de tous les points dâintersections de cette droite avec une courbe elliptique amĂšne Ă rĂ©soudre une Ă©quation du troisiĂšme degrĂ© en .
Par exemple pour la courbe définie plus haut :
Sous certaines hypothĂšses supplĂ©mentaires (notamment pour prendre en compte le cas ci-dessusâŠ), on en dĂ©duit un mĂ©canisme permettant dâassocier aux points et un troisiĂšme point .
On montre alors que lâopĂ©ration ainsi introduite dĂ©finit une loi de groupe commutatif.
Notons que malgrĂ© le vocabulaire de la gĂ©omĂ©trie plane utilisĂ© ici (droite, tangente), cette construction est purement algĂ©brique et possible sur nâimporte quel corps de base â en particulier les corps finis.
Le package elliptic de Go introduit une interface Curve
(reproduite ci-dessous)
décrivant une courbe elliptique ainsi que différentes implémentations.
type Curve interface {// Params returns the parameters for the curve.Params() *CurveParams// IsOnCurve reports whether the given (x,y) lies on the curve.IsOnCurve(x, y *big.Int) bool// Add returns the sum of (x1,y1) and (x2,y2)Add(x1, y1, x2, y2 *big.Int) (x, y *big.Int)// Double returns 2*(x,y)Double(x1, y1 *big.Int) (x, y *big.Int)// ScalarMult returns k*(Bx,By) where k is a number in big-endian form.ScalarMult(x1, y1 *big.Int, k []byte) (x, y *big.Int)// ScalarBaseMult returns k*G, where G is the base point of the group// and k is an integer in big-endian form.ScalarBaseMult(k []byte) (x, y *big.Int)}
Ce package implémente notamment les courbes P-256 et P-384 recommandées par le NIST et définies sur des corps avec premier (sur et bits respectivement).
Ces courbes sont trĂšs utilisĂ©es en pratique, bien quâelles soient lâobjet de soupçons de backdoor par la NSAâŠ
Ci-dessous, voici ce que donne notre implémentation de Diffie-Hellman avec la courbe P-256 (exécuter en ligne) :
---Diffie Hellman: Elliptic Curve P-256Field F_115792089210356248762697446949407573530086143415290314195533631308867097853951Group order 115792089210356248762697446949407573529996955224135760342422259061068512044369---Amandine generated secret key:7342462952232507609821760396776010434242597936110644963427068432176148435477Amandine transmitted (public) key:4298064935869640021519201203011003733572235803818690789383769808073692516886Bastien generated secret key:110170395102730437573897103177357816686836430099641375606421793187578417842509Bastien transmitted (public) key:6169708491757107974945465047131128230446890469460906853603235544851377683418Amandine computed shared secret:85470343014045070211971750616744066840724631640773885304746048731416023347568Bastien computed shared secret:85470343014045070211971750616744066840724631640773885304746048731416023347568
đĄ On remarque que lâordre du groupe gĂ©nĂ©rĂ© est relativement proche de celui du corps fini utilisĂ© :
sur leurs chiffres, les premiers coincident.
Câest une consĂ©quence dâun thĂ©orĂšme connuâŠ
Avant TLS 1.3, il Ă©tait possible dâutiliser le mĂȘme couple de clefs cryptographiques pour lâauthentification et lâĂ©change des clefs de session.
Ce mĂ©canisme est qualifiĂ© de statique (par exemple static RSA, lorsque lâalgorithme sous-jacent est RSA).
Avec TLS 1.3, des clefs asymĂ©triques Ă usage unique (clefs Ă©phĂ©mĂšres) doivent ĂȘtre utilisĂ©es pour chaque session.
Ceci permet de garantir une confidentialitĂ© persistante : si une clef privĂ©e est compromise dans le futur, elle ne pourra pas ĂȘtre utilisĂ©e pour dĂ©chiffrer a posteriori des communications chiffrĂ©es qui auraient Ă©tĂ© conservĂ©es.
Le mĂ©canisme de Diffie-Hellman permet de satisfaire cette exigence, en partageant une clef secrĂšte â la clĂ© « pre-master » â basĂ©e sur des clefs secrĂštes (client) et (serveur) Ă©phĂ©mĂšres.
Les lecteurs et lectrices souhaitant en savoir plus sur le fonctionnement de TLS pourront lire cette présentation par Cloudflare.
Le livre An Introduction to Mathematical Cryptography, utilisĂ© comme source dâinspiration pour cet article, est une rĂ©fĂ©rence pour approfondir les notions thĂ©oriques.
Maths et applications, avec les mains et avec du code đ»
Suivre sur Twitter