T.P. 8 : algorithmes et arithmétique 1 L’algorithme d’Euclide étendu Implémenter en Python, l’algorithme d’Euclide étendu vu au cours § 1.4. Par commodité, le voici explicité sur un exemple appliqué à a = 19 et b = 15. k 0 1 2 3 4 5 uk 1 0 1 −3 4 vk 0 1 −1 4 −5 rk 19 15 4 3 1 0 qk 0 1 3 1 3 A chaque étape, si on ne s’intéresse qu’au triplet Lk = [uk , vk , rk ] alors Lk+1 = Lk−1 − qk Lk . On pourra donc coder [u,v,r] dans une liste. ● (M1) comme une liste de Python : dans ce cas, il faut expliquer à Python comment ajouter deux listes et multiplier une liste par un nombre. ● (M2) comme un np.array : l’avantage est que numpy sait ajouter les listes et les multiplier par un nombre. 2 L’algorithme d’exponentiation rapide Le but de cette algorithme est de calculer la puissance N -ième xN d’un nombre x qui peut être un entier ou un flottant, avec un minimum d’opérations. L’intérêt de cet algo. ne se limite pas aux entiers. Si on l’introduit ici, c’est qu’ensuite on l’appliquera aussi dans les anneaux de congruences, où il est encore plus efficace, et qu’on s’en servira pour des problèmes d’arithmétiques... a) L’idée essentielle : Si N = a0 + a1 2 ⋅ ⋅ ⋅ + an 2n , écriture en base deux, avec ai ∈ {0, 1} alors : n xN = xa0 (x2 )a1 . . . (x2 )an . Ensuite deux points de vue possibles, cela dépend si on connait déjà l’écriture en base 2 de N ou pas. b) 1ère méthode (des poids faibles vers les poids forts) En lisant le développement en base deux de droite à gauche : c’est le plus souvent comme cela que l’algorithme est présenté, car dans ce cas, on peut aussi obtenir les chiffres successifs du développement en base 2 à chaque étape par l’algorithme des poids faibles déjà vu au T.P. 5 : pas besoin d’avoir fait le calcul du dév. en base 2 avant ! Pour mettre en oeuvre l’algorithme on utilise trois variables qu’on va appeler res et aux comme résultat et auxiliaire et la variable N qui au départ contient comme valeur l’exposant N et qui va permettre à chaque étape de calculer le chiffre du développement en base 2 de N. De manière pas complètement formelle, à l’étape i : i i−1 ● On stocke dans aux la valeur de x2 , obtenue comme carré de x2 obtenu à l’étape précédente. ● En même temps, on divise à chaque étape N par 2 pour connaı̂tre le i-ème chiffre ai de son écriture en base 2. i.e. on itère N =N//2 . Le ai est le reste de la division euclidienne de N par 2. i ● Enfin toujours à l’étape i , si ai = 1, on multiplie res par x2 c’est-à-dire la valeur de aux à l’étape i, sinon on ne change pas res. 1 Exercice à faire : implémenter cette idée d’algorithme en Python. Rappel l’algo. de dév. en base deux des poids faibles vers les poids forts illustré sur un exple : 24 = 2 × 12 + 0 → a0 = 0, 12 = 2 × 6 + 0 → a1 = 0, 6 = 2 × 3 + 0 → a2 = 0, 3 = 2 × 1 + 1 → a3 = 1, 1 = 2 × 0 + 1 → a4 = 1. c) 2ème méthode (des poids forts vers les poids faibles) En lisant l’écriture en base 2 de gauche à droite : cette méthode évite l’utilisation d’une variable auxiliaire, mais nécessite d’avoir calculé le développement en base 2 de N : ce qui n’est pas un gros problème pratique en Python, mais jouerait sur les questions de coût. L’idée astucieuse, qui est utile dans d’autres contextes (algorithme de Hörner), est d’écrire : xN = ((. . . (xan )2 xan−1 )2 . . . )xa1 )2 xa0 Par exemple avec 24 qui s’écrit 11000 en base 2 : x24 = (((x)2 .x)2 )2 )2 ). On lit donc l’écriture en base 2 de N de gauche à droite en ignorant le premier 1, on part de res := 1 et à chaque étape s’il y a un 1 dans le développement de x en base 2 on remplace res par (res*res)*x, sinon on prend juste le carré i.e. on remplace res par (res*res). Exercice à faire : implémenter cette idée d’algorithme en Python d) Montrer que si N s’écrit avec n + 1 chiffres en base 2, alors cet algorithme permet de calculer xN avec au plus 2n multiplications. e) Comparer la vitesse de vos fonctions avec celle de la fonction pow(x,N) de Python qui utilise ce même algorithme d’exponentiation rapide. 2 T.P. 8 : algorithmes et arithmétique (suite) 3 Le test de Fermat 3.1 Exponentiation modulaire Pour expérimenter le test de Fermat, nous commençons par modifier l’algorithme d’exponentiation rapide pour le faire travailler dans les anneaux de congruences (Z/nZ, +, ×). Ecrire une fonction exp_mod(x,N,m) qui applique l’algorithme d’exponentiaton rapide pour calculer xN , en réduisant à chaque étape le résultat modulo m, autrement dit, en remplaçant le résultat à chaque étape par son reste dans la division euclidienne par m. Remarque : La fonction Python pow fait exactement cela si on lui rentre l’argument facultatif m. (A condition de ne pas prendre celle du module math !) 3.2 Le test de Fermat Le petit théorème de Fermat dit que si n est un nombre premier alors pour tout a ∈ N, an ≡ a [n]. Ce théorème fournit donc une C.N. pour qu’un nombre soit premier. Si on a un a tel que an ≡/ a [n], on est sûr que n n’est pas premier. On dira que n ne passe pas le test de Fermat pour la valeur a en question et donc n’est pas premier. a) Ecrire une fonction test_Fermat qui prend en argument un nombre n et un argument facultatif a, qui sinon admet a=2 comme valeur par défaut, et qui teste si n ≪ passe ≫ le test de Fermat pour tous entiers entre 2 et a, en renvoyant un booléen. b) Déterminer la liste des nombres non premiers entre 1 et 10000 qui passent le test de Fermat pour a = 2 puis a = 3. On utilisera une fonction du type de la fonction premier_mieux du cours, qu’on appellera simplement premier pour tester la primalité du nombre. 3.3 Fabrications d’entiers aléatoires avec de gros facteurs premiers a) Ecrire une fonction alea qui prend comme argument facultatif un nombre n, avec comme valeur par défaut n=500 et qui renvoie un nombre entier impair aléatoire à n chiffres. b) Fabriquer à l’aide de la fonction précédente une liste de 10 nombres aléatoires à 300 chiffres chacun et tester la primalité de chacun avec le test naı̈f de la fonction premier. Pour le premier nombre pour lequel ce test prend beaucoup de temps arrêter la recherche et comparer au test de Fermat pour 2, 3... c) Fabriquer (de manière automatique !) une liste de 10 nombres pour lesquels la fonction premier ne s’arrête pas avant un grand diviseur (à choisir) et appliquer le test de Fermat pour ces nombres pour 2 et 3. d) Tester l’algorithme d’Euclide avec deux nombres à 300 chiffres. Il répond souvent 1. Comparer avec le temps de calcul de la D.F.P. 3.4 Nombre de Carmichael Un nombre de Carmichael est un entier n ≥ 2 non premier qui vérifie la propriété que pour tout entier a ∈ Z : an ≡ a [n]. A l’aide de la fonction précédente, écrire un programme qui donne la liste de tous les nombres de Carmichael inférieurs à 10000. 3 4 Le principe du R.S.A (Appendice au T.P. 8) 4.1 Prérequis mathématiques : la fonction ϕ d’Euler Nous avons démontré en exercice le résultat suivant : Théorème : Si (G, )˙ est un groupe abélien fini à n éléments alors pour tout x ∈ G, on a xn = e. Nous avions vu que ce théorème, appliqué à G = Z/pZ∗ pour p un nombre premier donne immédiatement le petit théorème de Fermat. Nous allons voir ici une généralisation. a) Pour tout n ∈ N∗ , on note ϕ(n) le nombre d’entiers k ∈ [[1, n]] tels que k ∧ n = 1. i) Calculer ϕ(p) si p est premier. ii) Relier ϕ(n) au nombre d’éléments inversibles de l’anneau (Z/nZ, +, ×) (avec dém.). iii) En déduire la propriété suivante, due à Euler : ∀ a ∈ Z, ∀ n ∈ N∗ , a ∧ n = 1 ⇒ aϕ(n) ≡ 1 [n] iv) Quel résultat connu la question précédente généralise-t-elle ? b) i) Montrer que si a ∧ b = 1 alors ϕ(ab) = ϕ(a)ϕ(b) (plus difficile !) ii) Montrer que si pet q sont deux nombres premiers distincts, alors pour tout a premier avec pq, on a a(p−1)(q−1) ≡ 1 [pq]. iii) On garde les hyp. du b). Soit e ∈ N∗ premier avec (p − 1)(q − 1) et soit d ∈ N∗ tel que de ≡ 1 [(p − 1)(q − 1)]. Justifier l’existence de l’entier d et montrer que ade ≡ a [pq]. 4.2 4.2.1 Cryptage de Rivest, Shamir, Adleman (1978) La fabrication de la clé de codage : sa partie publique, sa partie secrète Dans la méthode R.S.A., il y a deux personnes dont les rôles sont bien distincts. Nous parlerons d’un chef et d’un subordonné, le subordonné devant transmettre des messages chiffrés (i.e. protégé par un code secret) au chef. Le chef choisit deux nombres premiers p et q (assez grands, disons de 150 chiffres chacun en base 10). Il calcule le produit n = pq (c’est facile pour un ordinateur : pour des nombres de 100 chiffres, cela ne fait que 10000 opérations, et un ordi en fait 1011 par secondes), puis le nombre (p − 1)(q − 1). Il choisit ensuite un nombre e premier avec (p − 1)(q − 1). Question 1 Pourquoi est-ce facile à faire ? Il rend alors publique la clé R.S.A. i.e. les nombres n et e, mais surtout pas les nombres p et q. Il calcule de son côté, un nombre d tel que de ≡ 1 [(p − 1)(q − 1)]. Question 2 Comment peut-on le faire en pratique ? On notera que cela nécessite de connaı̂tre le nombre (p − 1)(q − 1) = pq − q − p + 1 (ce qui revient à connaı̂tre p et q). Pour cela, si on n’est pas le chef, il suffirait de factoriser n. Mais, tout le principe de la méthode R.S.A. est : il est trop compliqué de décomposer n en facteur premier, même pour un ordinateur ! A ce stade, le chef a une clé secrète qui est (n, p, q, e, d) avec n = p × q et il a rendu publique (pour quiconque veut lui envoyer des messages) une partie de la clé : (n, e). 4.2.2 Comment un subordonné peut envoyer des messages au chef ? Le message est représenté par un nombre. Ce nombre est découpé en paquets de chiffres successifs, pour que chaque paquet définisse un nombre inférieur à l’entier n défini par la clé publique. Pour chaque paquet m, le subordonné calcule m′ = me et envoie m′ au chef. 4.2.3 Comment le chef peut décoder le message reçu ? Le chef est le seul à connaı̂tre d, il calcule m′d modulo n, et il retombe sur le message original m. 4