Algèbre, arithmétique (Mat309)Bernard.Parisse@univ-grenoble-alpes.fr2019 |
Table des matières
- 1 Motivations: cryptographie, codes correcteurs
- 2 Arithmétique des entiers et polynômes.
- 3 Euclide
- 4 L’anneau et le corps
- 5 Algèbre linéaire
- A Programmes
Index
|
|
1 Motivations: cryptographie, codes correcteurs
La transmission d’informations par les réseaux sous forme numérique pose deux problèmatiques importantes
- la confidentialité ou l’authentification des données échangées, qu’il s’agisse de donner un numéro de carte bancaire ou de signer numériquement sa déclaration d’impots. C’est le domaine de la cryptographie.
- la détection et si possible la correction d’erreurs de transmission. La correction est souhaitable si on veut éviter de demander de renvoyer un message, en particulier s’il faut du temps pour transmettre un message (par exemple une donnée provenant d’une sonde martienne située en moyenne à 76 millions de km de la Terre met 4 minutes 20 secondes à nous parvenir, une réémission demande alors presque 10 minutes).
Ces deux problématiques ont des solutions à la frontière entre informatique et mathématiques. Dans ce cours intitulé Algèbre et arithmétique, on présente plusieurs structures algébriques (groupes, anneaux, corps, espaces vectoriels) ainsi que des algorithmes d’arithmétique dans ces structures, et on étudiera leur efficacité, qui assure la confidentialité des données transmises ou permet de détecter et corriger rapidement des erreurs de transmission (pas trop nombreuses).
1.1 Cryptographie
Historiquement, les militaires
Romains pratiquaient déjà la cryptographie, avec le code dit
de César qui consistait à décaler de plusieurs positions les
lettres constituant un message dans l’alphabet, de manière
cyclique. Par exemple si on décale
de 4 positions, un A devient un E, un B devient un F, etc et un Z
devient un D. Ce procédé est facile à implémenter sur
une chaine de caractère numérisée (on additionne la clef
de cryptage modulo 26) mais il est trop simple,
car le nombre de possibilités de cryptage est trop limité (voir
feuille d’exercices 1).
Plusieurs variations permettent de rendre ce système plus robuste.
Par exemple on peut choisir une permutation quelconque des 26 lettres
de l’alphabet. Cette méthode est toutefois sujette à une attaque
statistique, toutes les lettres de l’alphabet n’ont pas
la même fréquence dans un message (c’est pour cela qu’elles
ne valent pas le même nombre de points au scrabble!).
Une autre méthode utilisée pendant la seconde guerre mondiale
par les services de renseignements utilise une clef variable,
on fait toujours une addition modulo 26, mais la clef change à
chaque lettre, on utilise par exemple la position d’une série de
lettre prise dans un livre, popularisé par The Key to Rebecca
(Le Code Rebecca) de K. Follett
basée sur l’histoire de l’espion nazi Johannes Eppler et
l’opération Salaam. Ce type de code est incassable sans
connaissance de la clef.
Une méthode plus élaborée était mise en oeuvre par la
machine Enigma utilisée par les militaires nazis toujours pendant
la seconde guerre mondiale, code qui a été cassé
par Alan Turing et son équipe à Bletchley Park, popularisé
par le film The Imitation Game et qui marque les débuts
de l’informatique moderne (machine de Turing universelle).
On peut modéliser ces systèmes cryptographiques par la donnée
d’une bijection d’un ensemble d’entiers et de sa bijection réciproque
. Par exemple pour le cryptage de César, l’ensemble d’entiers est
(A=0, B=1, etc.), est le décalage fixe et
On peut imaginer des variations par exemple le codage dit linéaire
qui est bijectif si est premier avec 26.
Ces méthodes de cryptage sont dites à clef secrète (ou symétrique), car
la connaissance des paramètres de permet de déterminer facilement
. Il est nécessaire d’échanger la clef (par exemple
par une rencontre physique) avant de pouvoir échanger
des données.
L’essor des communications a nécessité
l’invention de mécanismes permettant de sécuriser l’échange
de données sans échanger au préalable des clefs.
La méthode RSA (inventée en 1977 par Ronald Rivest,
Adi Shamir et Leonard Adleman) très populaire aujourd’hui
utilise le calcul de puissances
d’entiers modulo le produit de deux grands nombres premiers, nous allons
maintenant la présenter rapidement et nous y reviendrons plus
en détails plus loin lorsque nous aurons tous les outils nécessaires
pour l’analyser.
On se donne donc , produit de deux grands nombres premiers et
, est public, alors que et sont secrets. La sécurité
repose sur le fait que multiplier de grands entiers est rapide (moins
d’une seconde pour 1 millions de digits) alors que factoriser un
entier (avec les algorithmes connus actuellement) prend beaucoup
de temps dès qu’on dépasse la centaine de chiffres et
est actuellement impossible au-delà d’environ 200 chiffres.
Exemple avec et pas trop grands :
On se donne également et deux entiers qui vérifient la
relation
est public, alors que est secrète.
Exemple de paire de clefs :
Une donnée à sécuriser est représentée par un entier ,
la fonction de codage est
ses paramètres sont publics donc n’importe qui peut coder une donnée.
On montrera que la fonction de décodage est
Seul le détenteur de peut décoder. On a donc la possibilité
d’échanger des données de manière sécurisée sans avoir
besoin d’échanger des clefs auparavant.
Illustration : on code les caractères ASCII du mot Bonjour puis on décode
(attention, dans la réalité il ne faut surtout pas coder caractère
par caractère de cette manière)
Le fait que les fonctions et soient réciproques l’une de
l’autre est une conséquence du :
Petit théorème de Fermat : soit
est un nombre premier, et un entier, alors est
divisible par .
On a , donc si n’est pas un multiple de , on en déduit que
est divisible par puisque est premier.
Donc est divisible par car
(prendre ).
En multipliant par , on conclut que pour tout entier,
est divisible par , donc est divisible
par (prendre dans (1)).
Pour les mêmes raisons est divisible
par , donc il est divisible par puisque et sont des
nombres premiers distincts, CQFD.
Le petit théorème de Fermat peut se montrer par récurrence sur . Rappelons d’abord la formule du binôme : En effet quand on développe le produit, il y a autant de termes que de choix de dans une liste de éléments, sans tenir compte de l’ordre. Si on tient compte de l’ordre, choisir éléments parmi laisse possibilités ( choix pour le premier élément, pour le second, etc.). Il faut ensuite diviser par les ordres possibles entre les éléments choisis, i.e. .
Pour , est bien divisible par . Pour montrer l’hérédité, on développe avec la formule du binôme, tous les termes de la somme d’indices à sont divisibles par , donc est divisible par , l’hypothèse de récurrence nous donne divisible par , on additionne et on conclut.
Remarque : si on développe en appliquant
la formule du binôme des deux cotés, on obtient
une formule donnant les coefficients binomiaux de la ligne
en fonction de ceux de la ligne
qui permet de calculer les coefficients binomiaux par un algorithme
itératif simple.
Exercice : Implémenter cet algorithme, et vérifier pour
quelques valeurs que
tous les coefficients sauf le premier et le dernier
d’une ligne d’indice premier sont divisibles par
La sécurité du cryptage est liée au temps de calcul de la factorisation de l’entier , produit de deux grands nombres premiers et relativement au temps de calcul du produit de ces deux nombres premiers et . Encore faut-il s’assurer qu’on peut coder et décoder en un temps raisonnable, c’est à dire calculer efficacement (ou c’est le même algorithme). Pour cela on utilise l’algorithme dit de la puissance modulaire (ou exponentiation modulaire) rapide. Il repose sur l’observation suivante :
- si est nul, alors
- si est pair, alors
- si est impair, alors
Attention, pour rendre cet algoritme récursif efficace,
il faut veiller à faire les calculs
intermédiaires modulo . On effectue alors des multiplications
d’entiers plus petits que , et le nombre de multiplications
est de l’ordre du nombre de bits de au lieu de , c’est ce qui
fait toute la différence (un millier d’opérations contre
), comme on le voit sur le calcul ci-dessous (avec beaucoup
plus petit que pour que l’algorithme lent se termine)
Dans la suite de ce cours, nous allons “industrialiser” ces preuves et méthodes, en étudiant l’arithmétique des entiers, des entiers modulo un entier, on parlera aussi de polynômes, de groupes commutatifs, d’anneaux euclidiens, de corps finis premiers, et un peu de temps de calcul.
1.2 Codes
La théorie de l’information et du codage est beaucoup plus récente que la cryptographie (dont l’intérêt militaire est évident), elle date des années 50 (Shannon, Hamming). Il ne s’agissait pas à l’époque de télécommunications mais de pallier à la fiabilité des cartes perforées.
L’idée est d’ajouter de la redondance, par exemple un code très simple consiste à répéter 3 fois chaque bit. Si en réception les 3 bits sont identiques, on considère que la valeur commune est la bonne, sinon il y a eu une erreur, et on peut la corriger sous l’hypothèse qu’il y a eu au plus une erreur, en votant à la majorité. Mais ce système est peu efficace si la probabilité d’erreur sur 1 bit est faible, car il utilise 3 fois plus de bande passante qu’il n’est nécessaire pour transmettre l’information sur un canal 100% fiable.
On verra vers la fin du cours des méthodes permettant de détecter
et corriger des erreurs avec une efficacité meilleure, ces
méthodes ont besoin de structure qui sera apportée par les espaces
vectoriels dont les vecteurs
ont des coordonnées dans
au lieu de ou (on se limite à
dans le cadre de ce cours). On parle
de codes linéaires.
Par exemple, le code de Hamming binaire 7,4 encode un quartet (i.e. un
4-uplet de bits) par
un 7-uplet , en multipliant modulo 2 par la matrice le vecteur :
On observe que les 4 premières lignes de forment la matrice
identité ce qui permet facilement de reconstituer si a
été transmis correctement x=y[0:3]
Pour détecter une erreur, on vérifie que est dans le noyau de
la matrice
S’il n’y a pas eu d’erreur détectable, alors (modulo 2).
S’il y a une erreur au plus, alors est à une permutation près
l’écriture en base 2 du
numéro de composante de à modifier.
Par exemple, on simule une erreur de transmission en changeant le bit d’indice :
Ici s mod 0
a pour effet d’enlever les %
pour avoir une
liste de 0 et de 1, que l’on convertit en entier et à qui on
applique
la permutation inverse de , on retrouve bien l’indice de l’erreur
(vous pouvez le vérifier en changeant la valeur de ).
2 Arithmétique des entiers et polynômes.
2.1 Petits et grands entiers
Les microprocesseurs sont capables d’effectuer des
opérations arithmétiques de base (toujours +,-
, pratiquement
toujours *
et la division euclidienne) sur des petits entiers compris dans l’intervalle
(entier signé) ou (entier non
signé), pour , , et (parfois aussi pour
). En cas de dépassement, les calculs sont faits modulo
.
Les logiciels de calcul doivent pouvoir travailler avec des entiers de
taille plus grande, il faut les stocker en mémoire et implémenter
les opérations arithmétiques de base en se ramenant à des petits
entiers.
Pour faire cela, on va écrire des entiers dans des bases adéquates.
2.1.1 Ecriture en base
Il s’agit de généraliser l’écriture des entiers qui nous est familière en base 10 à une base quelconque, les ordinateurs préférant la base 2 ou une puissance de 2. Ainsi écrire 1602 signifie que La taille d’un nombre est le nombre de chiffres qu’il contient, ainsi 1602 s’écrit avec 4 chiffres (en base 10). Les nombres à 4 chiffres sont compris entre et , plus généralement, un nombre compris entre et sécrit avec chiffres. Si on change pour une base quelconque, on a alors :
On a
floor(
)
où floor
est la partie entière (plus grand entier
inférieur ou égal). (Attention, le nombre de chiffres de est ).
On remarque que le nombre de digits pour écrire en base
est floor(
)
, en effet d’une part :
et d’autre part :
puis on prend les log.
Pour montrer l’unicité de l’écriture, on suppose qu’il y a deux écritures distinctes de ce type, et on regarde le coefficient d’indice maximal qui est différent (appelons le ), on a alors quitte à changer de signe, on peut supposer que le membre de gauche est strictement positif, on a alors Mais le membre de droite se majore par absurde.
L’existence se prouve par l’algorithme suivant qui utilise la division euclidienne (on a utilisé irem(N,b)
au lieu de N % b
pour calculer le reste de division euclidienne pour
des raisons de compatibilité avec LATEX)
def ecriturebase(N,b): L=[] while N>0: L.append(irem(N, b)) N=N//b return L
onload
i.e. tant que , on calcule le reste de par , qui donne
le coefficient de poids le plus faible de l’écriture de en base
, on ajoute lécriture en base du quotient de par .
L’algorithme s’arrête au bout de partie entière de
itérations.
Réciproquement, on vérifie que l’écriture obtenue
convient en développant
On observe au passage que l’écriture de sous la forme ci-dessus
nécessite additions et multiplications, et est donc plus
efficace que le calcul sous la forme développée 2.
C’est la méthode dite de Hörner.
Exemple : en base . Pour écrire en base 2, on calcule le
reste de 12 par 2 donc , le quotient de 12 par 2 vaut 6, on
divise par 2, reste 0 donc , quotient 3, on divise par 2, reste
, quotient 1, on divise par 2, reste quotient 0 on
s’arrête, donc 12=0b1100
.
Réciproquement on a bien
Exercice : la commande b:=convert(N,base,b)
de Xcas effectue la conversion
d’un entier en la liste des coefficients de son écriture en base
, la réciproque étant convert(L,base,b)
ou
horner(L,b)
. Implémenter des fonctions équivalentes
dans votre langage de programmation préféré.
Exercice : comment passe-t-on simplement de la représentation d’un nombre en base 2 à un nombre en base 16 et réciproquement ?
2.1.2 Lien avec les polynômes de avec .
A tout entier naturel , on peut associer un polynôme à coefficients entiers qui est son écriture en base , les coefficients du polynôme sont dans l’intervalle . Réciproquement, si les coefficients d’un polynôme sont des entiers compris entre alors ils correspondent à l’écriture d’un entier en base .
On va voir que les opérations arithmétiques de base sur les grands entiers reviennent à effectuer des opérations de base sur les polynômes, avec une petite difficulté supplémentaire, il faut tenir compte de retenues. Les algorithmes naifs pour additionner et multiplier deux polynômes correspondent précisément aux algorithmes enseignés à l’école primaire pour effectuer l’addition ou la multiplication de deux entiers.
On travaille sur ordinateur avec une base qui est une puissance de
2, par exemple on pourrait prendre et dans ce cas les entiers
s’écrivent avec des chiffres qui sont des octets. Ainsi
\nAa
représente
En pratique, sur des processeurs 32 bits, on utilise
les chiffres sont des double-mots (dword), sur des processeurs 64 bits
les chiffres sont des quadruple mots (qword).
2.2 Opérations arithmétiques de base
2.2.1 Addition, soustraction
Si et sont deux polynômes, alors Si et sont deux entiers écrits en base , alors il faut donc additionner les digits de l’écriture en base comme pour additionner deux polynômes. On a , si , il n’y a rien à faire, sinon on remplace par et on ajoute 1 (retenue) à qui appartient à , etc. Le nombre d’opérations à effectuer est de l’ordre du maximum du nombre de bits/chiffres/digits de et .
2.2.2 Multiplication
Si et , alors on se ramène à la multiplication de deux polynômes : Si , par exemple ou , sera très souvent supérieur à , il y aura souvent des retenues! On peut regrouper les termes ayant le même exposant, en utilisant des décalages pour tenir compte de (c’est l’algorithme de multiplication posée en base 10) ou additionner au fur et à mesure au coefficient actuel de (exercice de la feuille de TD). En base 2, si on pose la multiplication comme en base 10, il est conseillé d’effectuer les additions une par une, sinon la prise en compte des retenues est délicate.
Le nombre de multiplications et d’additions est de l’ordre de (on peut montrer que c’est aussi le cas en tenant compte des retenues, car si une retenue se propage de positions dans la boucle intérieure en , elle ne pourra pas se propager de plus de 2 positions pour les itérations suivantes de la boucle intérieure).
Taille du résultat : si on multiplie un nombre à 3 chiffres par un nombre à 2 chiffres, le résultat aura 4 ou 5 chiffres, penser par exemple à ou . Dans le cas général, donc le produit a pour taille ou bits/chiffres/digits.
Si et sont proches, par exemple égaux, le résultat a pour taille alors que l’algorithme présenté nécessite opérations. On peut donc espérer l’existence d’algorithmes plus efficaces (un peu comme les algorithmes de tri...). Nous allons en présenter un dans le cadre plus simple de la multiplication des polynômes (où on n’a pas à gérer les retenues).
2.2.3 Diviser pour régner: multiplication de Karatsuba.
Soient deux polynômes de degrés strictement inférieur à . On suppose que le cout d’une opération arithmétique dans l’ensemble des coefficients vaut 1 et on néglige les autres opérations (on suppose par exemple que l’ensemble des coefficients est fini). On écrit avec de degrés strictement inférieur à , on a alors : Il y a 4 produits de polynômes de degrés , mais au prix d’additions intermédiaires, on peut se ramener à 3 produits, en effet donc pour calculer le facteur de il suffit de soustraire à les produits et que l’on doit calculer par ailleurs. Au premier ordre, le temps nécessaire pour multiplier les 2 polynômes de degré est multiplié par 3, au lieu de 4.
Plus précisément, soit le temps nécessaire pour calculer le produit de 2
polynômes par cette méthode, on a alors
où représente le nombre d’additions ou de soustractions
pour former , , soustraire et , et tenir compte
du fait que les termes de degré de se combinent
avec ceux de degré de et les termes de degré
de avec ceux de degré de .
On en déduit
cette récurrence se résoud facilement par la commande
ce qui donne .
Asymptotiquement, ce qui est bien
meilleur que la multiplication naive en , mais pour de
petites valeurs de , la multiplication naive est plus rapide
comme l’illustre le graphe ci-dessous (en rouge la multiplication
naive, en bleu Karatsuba complet) :
On utilise Karatsuba (récursivement) uniquement pour des valeurs de
suffisamment grandes (théoriquement lorsque , le surcout dû
aux additions est plus petit que la multiplication économisée,
soit soit , en pratique plutôt pour de l’ordre de quelques
dizaines selon les implémentations, car nous n’avons tenu compte
que des opérations arithmétiques).
Exercice : vérifier que la multiplication des entiers longs en langage Python se comporte pour grand comme la multiplication de Karatsuba.
Les logiciels de calcul intensif utilisent la plupart du temps des algorithmes plus efficaces lorsque est encore plus grand, qui se rapprochent de . C’est en particulier le cas de tous les logiciels de calcul formel qui utilisent la libraire GMP écrite en langage C (Xcas, Sagemath, Maple, Mathematica, ...). Ces algorithmes utilisent la transformation de Fourier rapide (FFT) en calcul exact. Voir par exemple (en anglais) ce lien.
2.3 Opérations de base sur les petits entiers.
Les petits entiers sont représentés en base , les opérations de base sont directement faites par le microprocesseur en se ramenant aux calculs en base 2:
-
addition : 0+0=0=1+1 (avec retenue),
0+1=1=1+0 qui se traduisent enou exclusif
logique hors retenues. Les opérations bit à bit
se parallélisent facilement, conduisant à des temps
d’éxecution de 1 cycle.
01001111 + 01101011 ---------- 10111010
- multiplication : 0*0=0*1=1*0=0, 1*1=1 qui se traduit en et logique. Les opérations de multiplication se parallélisent aussi, conduisant à des temps d’exécution de un ou quelques cycles sur la plupart des processeurs actuels (mais ce n’était pas vrai il y a une vingtaine d’années).
- l’algorithme de division euclidienne en base 2
peut se faire avec l’algorithme de la potence, il consiste
à chercher les bits de l’écriture en base 2 du quotient en
commençant par le bit de poids fort.
La situation
est simplifiée par rapport à un calcul en base 10
parce que le bit du quotient à déterminer
ne peut être que 0 ou 1, il suffit de comparer avec le reste
en cours. Le temps d’exécution est significativement plus
élevé que celui d’une multiplication (plusieurs dizaines de cycle
si ou ), car n’est pas connu quand on calcule
, alors que et sont connus quand on calcule .
110000 | 101 -101 |--------- ---- | 1001 010 | 100 | 1000 | - 101 | ----- | 011 |
2.4 Opérations de base sur les grands entiers
Les grands entiers naturels sont représentés dans une base qui est une puissance de 2, , les coefficients de l’écriture étant de petits entiers, ils sont stockés en mémoire à des adresses consécutives. On utilise le plus souvent la taille des régistres du microprocesseur, parfois une valeur légèrement inférieure pour simplifier la gestion des retenues.
Les implémentations de la multiplication font intervenir des algorithmes plus efficaces (comme Karatsuba), ainsi que des découpages par blocs visant à minimiser les opérations de lecture/écriture en mémoire au profit de l’utilisation de régistres et de l’optimisation du stockage dans la mémoire cache (L1). Avec les processeurs modernes, le cout du calcul d’un produit de 2 grands entiers par la méthode naive est dominé par les accès mémoire en RAM et non par les opérations arithmétiques sur les coefficients.
Expérience avec un langage compilé, ici C++, pour la multiplication de deux polynômes par la méthode naïve.
#include <iostream> #include <vector> using namespace std; template<class T> ostream & operator << (ostream & os,const vector<T> & v){ if (v.empty()) return os << "[ ]"; const T * ptr=&v.front(),*ptrend=ptr+v.size(); os << "["; for (;ptr!=ptrend;++ptr){ os << *ptr << " "; } return os << "]"; } template<class T> void mul1(vector<T> & A,const vector<T> & M,const vector<T> & N){ A=vector<T>(M.size()+N.size()-1); for (size_t i=0;i<M.size();++i){ T Mi(M[i]); for (size_t j=0;j<N.size();++j){ A[i+j] += Mi*N[j]; // 1 lecture et 1 ecriture } } } template<class T> void mul2(vector<T> & A,const vector<T> & M,const vector<T> & N){ A=vector<T>(M.size()+N.size()-1); size_t i; for (i=0;i+3<M.size();i+=4){ T M0=M[i], M1=M[i+1], M2=M[i+2], M3=M[i+3]; T * Aptr=&A[i]; size_t j=0; for (j=0;j+3<N.size();Aptr+=4,j+=4){ T N0=N[j]; T N1=N[j+1]; T N2=N[j+2]; T N3=N[j+3]; *Aptr += M0*N0; Aptr[1] += M1*N0+M0*N1; Aptr[2] += M2*N0+M1*N1+M0*N2; Aptr[3] += M3*N0+M2*N1+M1*N2+M0*N3; Aptr[4] += M3*N1+M2*N2+M1*N3; Aptr[5] += M3*N2+M2*N3; Aptr[6] += M3*N3; } for (;j<N.size();++Aptr,++j){ T Nj=N[j]; *Aptr += M0*Nj; Aptr[1] += M1*Nj; Aptr[2] += M2*Nj; Aptr[3] += M3*Nj; } } // fin du produit for (;i<M.size();++i){ T Mi(M[i]); for (size_t j=0;j<N.size();++j){ A[i+j]+=Mi*N[j]; } } } int main(int argc,char ** argv){ if (argc<2){ cout << "Syntaxe " << argv[0] << " <entier> [methode 1 ou 2]" << endl; return 1; } int n=atoi(argv[1]); int algo=argc==3?atoi(argv[2]):1; vector<int> A,M(n),N(n); // on remplit M et N for (int i=0;i<n;++i){ M[i]=i+2; N[i]=i+1; } cout << M << "*" << N << endl; if (algo==1) mul1(A,M,N); else mul2(A,M,N); cout << A << endl; return 0; }
Compilation (sans utiliser les optimisations SSE)
avec c++ -O2 -mno-sse prog1.cc
.
Appel de l’algorithme non optimisé, puis optimisé, puis vérification par exemple par
time ./a.out 100000 1 >& log1
time ./a.out 100000 2 >& log2
diff -q log1 log2
On gagne presque un facteur 2 en temps.
La fonction mul1
effectue une lecture et une incrémentation
par produit de coefficients, donc lecture et incrémentation
où et désignent le nombre de coefficients des polynômes
et . La fonction mul2
stocke dans des régistres
4 coefficients de et de pour faire leur produit, elle effectue moins de la moitié
d’opérations de lecture/écriture en RAM (asymptotiquement 4
lectures et 7 incrémentations pour 16 produits de coefficients donc
lectures et incrémentations). Le
nombre d’opérations arithmétiques sur les coefficients est
identique.
2.5 Points à retenir absolument
- L’algorithme pour écrire un nombre en base , et le calcul inverse.
- Le nombre de chiffres de est proportionnel au log de .
- Addition et soustraction se font avec l’algorithme vu à l’école primaire, le cout est proportionnel au max du nombre de chiffres de et
- Le produit de deux entiers a pour taille la somme des tailles à un près, l’algorithme école primaire a un cout proportionnel au produit des tailles. Il existe des algorithmes plus rapides.
3 Euclide
Dans cette section, on se concentre sur tout ce qui tourne autour de la division euclidienne.
3.1 Division euclidienne.
On a vu la définition de la division euclidienne pour les entiers naturels . Comment calcule-t-on la représentation dans une base (que l’on notera ) de et ? Si alors et . Si , le calcul de et en base peut se faire par l’algorithme dit de la potence.
On suppose donc . Posons On observe que l’écriture de en base n’a pas de digit d’indice supérieur strict à , car . La valeur du digit d’indice de (digit éventuellement nul) est le plus grand entier tel que . Comme cela revient à chercher le plus grand digit tel que on regarde donc les digits de les plus significatifs et on calcule le quotient euclidien par . Si , on effectue alors la soustraction Pour déterminer le digit , on recommence la procédure ci-dessus, avec les au plus digits de d’indices supérieurs ou égaux à .
Le cout de l’algorithme de la potence est proportionnel au produit , le cas le pire correspond à proche de qui donne un cout proportionnel à (dans ce cas, on peut accélérer par un algorithme diviser pour régner de type Karatsuba).
Pour les entiers relatifs, d’autres
conventions de signe sont possibles pour et . Ainsi, la
division euclidienne en langage C renvoie tronqué vers 0.
Si , a % b
sera du signe de . Pour obtenir
le reste dans lorsque (convention
de signe de reste positif), il faudra ajouter à et
diminuer de 1.
Optimisation : sur les microprocesseurs modernes, on évite si possible
l’utilisation de tests pour optimiser le temps de calcul. En C, si on
divise par , et si est de signe quelconque, tous deux des entiers 31 bits,
alors unsigned(a)>>31
vaut 0 si et 1 sinon, on peut donc écrire :
inline int reste(int a,int b){ return a % b + (unsigned(a)>>31)*b; }
On peut aussi adopter la convention dite du reste symétrique, c’est-á-dire que . En général, on peut supposer que , ce qui donne le programme suivant :
def rsym(a,b): r = irem(a, b) if r<0: r += b if r<=b//2: return r return r-b
onload
Optimisation :
int rsym(int r,int m){ r %= m; r += (unsigned(r)>>31)*m; // rend r positif return r-(unsigned((m>>1)-r)>>31)*m; }
Le reste de division euclidienne (%
) est ici
l’opération la plus couteuse, il existe heureusement
parfois des méthodes qui permettent de l’éviter, par
exemple on verra plus bas l’algorithme de PGCD binaire
ou lorsqu’on fait des calculs modulo un entier fixé.
Pour les polynômes en une variable, si et sont deux polynômes à coefficients dans un corps , il existe un unique polynôme et un unique polynôme tels que : L’unicité vient du fait que est incompatible avec les degrés si . L’existence se prouve par récurrence sur la différence de degré entre et , si elle est négative , si elle est nulle est le quotient des coefficients dominants de de degré et de de degré , sinon, on pose , ce qui annule le coefficient dominant de et on est ramené à une différence de degré diminuée de 1 (au moins).
Remarque 1 : si et sont à coefficients entiers et si est unitaire, alors et sont à coefficients entiers. Sinon, on peut définir la pseudo-division de par en multipliant par le coefficient dominant de à la puissance la différence des degrés + 1.
Remarque 2 : pour les polynômes à plusieurs variables, il n’existe malheureusement pas de division euclidienne.
3.2 Anneau euclidien
L’ensemble des entiers relatifs est muni des opérations
+
et *
qui en font un anneau commutatif unitaire intègre.
On rappelle (ou on définit) que
-
un groupe possède une loi associative, avec un élément
neutre, tel que tout élément possède un symétrique. Si la
loi est commutative, on parle de groupe commutatif et on note
habituellement la loi
+
. - un anneau dispose de deux lois,
+
et*
, telles que est un groupe commutatif pour+
, et que la loi*
est associative et distributive par rapport à+
. - si la loi
*
est commutative, on parle d’anneau commutatif - si la loi
*
admet un élément neutre (1), on parle d’anneau unitaire. - si la relation entraine ou , on parle d’anneau intègre. On dit aussi qu’il n’y a pas de diviseurs de zéro.
L’ensemble des entiers relatifs est donc un anneau commutatif unitaire intègre, mais il a une propriété supplémentaire, il possède une opération de division euclidienne pour et non nul, il existe et (uniques) tels que avec . Cette propriété est également vraie pour les polynômes à coefficients dans un corps avec une condition de degré avec degree<degree. Plus généralement, on parle de stasthme pour désigner l’équivalent du degré pour un polynôme ou de la valeur absolue de l’entier dans .
3.3 Divisibilité
Ceci s’applique aussi bien aux entiers qu’aux polynômes. On observe que l’entier 0 est divisible par tous les entiers non nuls, et le polynôme nul par tous les polynômes non nuls. Si et sont des entiers naturels non nuls et si est divisible par alors . En effet avec puisque . Si et sont deux polynômes non nuls et si est divisible par alors le degré de est supérieur ou égal au degré de .
Observation : 1 n’est divisible que par 1 et -1, les seuls entiers ayant un inverse pour la multiplication dans sont 1 et -1. Pour les polynômes à coefficients dans un corps , le polynôme 1 est divisible par les polynômes constants non nuls (ceci est une conséquence du degré d’un produit qui est égal à la somme des degrés des facteurs), les polynômes constants sont les seuls polynômes ayant un inverse pour la multiplication dans les polynômes. Ces éléments, appelés inversibles de l’anneau, forment un groupe, ils jouent un role important, en effet si divise , i.e. alors pour tout élément inversible , divise puisque . Dans le cas des entiers, cela signifie qu’on peut négliger le signe pour étudier la divisibilité, autrement dit on peut se limiter aux entiers naturels. Dans le cas des polynômes, on peut multiplier par n’importe quel coefficient non nul, autrement dit on peut se limiter aux polynômes unitaires (i.e. dont le coefficient dominant est 1).
La relation de divisibilité dans l’ensemble des entiers naturels non nuls vérifie les propriétés suivantes :
- réflexivité : divise
- transitivité : si est divisible par et par alors est divisible par , en effet et alors .
- antisymétrie : si est divisble par et par , alors et donc et donc et .
Ces propriétés sont aussi vérifiées par la relation sur les entiers (ou les réels). Une relation réflexive (), antisymétrique ( et entraine ) et transitive ( et entraine ) est appelée relation d’ordre. Il s’agit ici d’une relation d’ordre partiel, il y a des éléments qui ne peuvent être comparés, par exemple 2 et 3. Lorsque deux éléments sont toujours comparables, on parle de relation d’ordre total (par exemple sur les entiers ou les réels ou l’ordre lexicographique sur les chaines de caractères ou les listes).
La divisibilité sur les polynômes unitaires est aussi une relation d’ordre partiel.
On verra plus loin un autre type de relation, les relations d’équivalence, où on remplace l’antisymétrie par la symétrie ( entraine ).
3.4 Le PGCD
Soient et deux entiers. On se ramène au cas des entiers naturels en enlevant les signes.
Si l’un des deux éléments est nul, disons , alors la réponse est . Sinon, comme 1 divise et de , l’ensemble des diviseurs communs à et est non vide, et son plus grand élément est inférieur ou égal au minimum de et de , le PGCD existe donc bien.
On verra dans la suite que le PGCD intervient souvent en arithmétique. On peut déjà citer une de ses applications qui est la réduction des fractions sous forme irréductible.
Pour le calculer, on observe que le PGCD de et de est le
même que celui de et de , le reste de la division euclidienne de
par . En effet si est un diviseur commun à et ,
alors et donc . Réciproquement
si est un diviseur commun à et , alors
et donc . Cet algorithme, appelé
algorithme d’Euclide,
se traduit en un programme récursif (on a utilisé irem(a,b)
pour le reste euclidien au lieu de a % b
pour raison
de compatibilité avec LATEX) :
def pgcdr(a,b): if b==0: return a return pgcdr(b,irem(a,b))
onload
A chaque appel récursif, la valeur du reste diminue au moins de 1 puisque .
Au bout d’un nombre fini d’appels, et on a le PGCD.
Si on utilise la valeur absolue du reste symétrique au lieu du reste euclidien
positif, le reste perd (au moins) un bit dans son écriture en base 2 à chaque
itération, le nombre d’itérations est au plus égal au minimum
du nombre de bits de et .
Notons la suite des restes de l’algorithme d’Euclide (avec le dernier reste), on a avec un cout pour cette division proportionnel à où est le nombre de digits de l’écriture de . Or à une unité près , on a donc un cout total pour l’algorithme d’Euclide proportionnel à Donc le cout est en si le nombre de bits de et est plus petit que , en majorant le nombre d’étapes par avec la version restes symétriques d’Euclide. C’est encore vrai à une constante près sans prendre les restes symétrique mais un peu plus long à justifier, on peut par exemple utiliser le bonus de l’exercice sur Fibonacci de la feuille de TD et un équivalent de pour grand. On peut aussi voir que si le reste de la division de par est supérieur à , alors à l’étape suivante, le quotient vaudra 1, donc le reste suivant sera , donc le nombre d’étapes d’Euclide classique est au plus 2 fois le nombre de bits de l’écriture en base 2 de .
Exercice : traduire l’algorithme d’Euclide en un programme non récursif.
Solution avec reste symétrique, cf. A.2
Le même algorithme fonctionne pour les polynômes, cette fois on cherche un polynôme de degré le plus grand possible qui divise et . L’algorithme ci-dessus s’adapte en remplaçant la division euclidienne des entiers par celle des polynômes, et le nombre d’appels récursifs est fini car le degré du reste qui diminue de 1 au moins à chaque itération. On obtient alors un PGCD, les autres PGCD sont des multiples non nuls de ce PGCD.
Il existe une version adaptée à l’écriture en base 2 des entiers sur machine, appelée PGCD binaire, qui évite de devoir faire des divisions euclidiennes (les divisions par 2 sont des décalages)
- si ou est nul on renvoie l’autre,
- si et sont pairs pgcd(,)=2 pgcd(,), (ne peut se produire qu’au début)
- si est pair, impair, pgcd(,)=pgcd(,),
- le cas symétrique (ne peut se produire qu’au début)
- si les 2 sont impairs pgcd(,)=pgcd(,min(,))
Chaque itération nécessite au plus le nombre de bits de l’écriture en base 2 de et , et il y a au plus itérations (car on diminue de 1 le nombre de bits d’au moins un des 2 opérandes). Cet algorithme a donc un coût d’au plus , mais la constante devant est significativement plus petite que celle de l’algorithme d’Euclide car on évite les divisions qui sont des opérations arithmétiques de base couteuses. Sa justification repose sur des propriétés du PGCD qui se déduisent eux-mêmes de l’identité de Bézout.
3.5 L’identité de Bachet-Bézout
On va construire deux entiers relatifs et tels que On considère la matrice qu’il faut interpréter ligne par ligne comme 1er coefficient 2ème coefficient=3ième coefficient. L’algorithme de construction de et est une version arithmétique de l’algorithme du pivot de Gauss, où on souhaite créer un zéro dans la dernière colonne de la matrice. Mais seules les manipulations de lignes du type avec entier sont admises. Le mieux que l’on puisse faire est donc de créer avec quotient euclidien de par . Il faudra donc plusieurs manipulations de lignes avant d’avoir un 0, et la dernière colonne sera la liste des restes successsifs de l’algorithme d’Euclide pour calculer le PGCD de et . La ligne précédent la ligne avec un 0 en dernière colonne se termine par le PGCD (dernier reste non nul) et les deux premières colonnes donnent les coefficients de Bézout. Exemple avec et
Algorithme :
- Initialiser deux listes et à et .
- Tant que (comme les indices
commencent à 0, est le dernier nombre de la liste ),
faire
- quotient euclidien de par ,
- prend la valeur ,
- Renvoyer
Exemple : exécutez la commande ci-dessous qui calcule
l’identité de Bézout pour 125 et 45 en affichant les étapes de
l’algorithme dans la partie basse de la page HTML
Explication : A chaque itération, l’élément l[2]
d’indice 2 de la liste (ou ) prend comme valeur l’un des restes
successifs de l’algorithme d’Euclide,
et on a un invariant de boucle a*l[0]+b*l[1]=l[2]
qui donne l’identité de Bézout lorsque l[2]
est le
dernier reste non nul.
Cet invariant se prouve facilement par récurrence.
Regardons maintenant la taille de et de . On peut supposer que quitte à échanger et et on ignore les cas triviaux où et/ou valent 1.
Preuve (abrégée) : On considére les trois suites (coefficients de la colonne d’indice 0), (colonne d’indice 1) et (colonne d’indice 2), ces suites vérifient la même relation de récurrence : où est le quotient de la division de par . On montre par récurrence au cran après Bézout, on a donc la suite est majorée par et On en déduit puisque .
On montre que le cout de l’algorithme est proportionnel à , comme pour l’algorithme d’Euclide.
Cet algorithme fonctionne à l’identique avec les polynômes à coefficients dans un corps, en utilisant la division euclidienne des polynômes.
L’identité de Bézout a de nombreuses conséquences en arithmétique. En voici une première :
En effet, si divise et , il divise le PGCD de et .
3.6 Nombres premiers entre eux
Deux entiers sont dits premiers entre eux si leur PGCD vaut 1. Par exemple 22 et 15 sont premiers entre eux. Plus généralement, si et ont comme PGCD , alors et sont premiers entre eux, car leur PGCD multiplié par divise et .
Si et sont premiers entre eux et si divise alors divise .
Preuve : par hypothèse, il existe un entier tel que et par Bézout, deux entiers tels que , on élimine entre ces deux équations ce qui donne er est bien multiple de .
En effet, il existe des entiers tels que Donc
Ces résultats restent vrais avec des polynômes à coefficients dans un corps (où premiers entre eux signifie que le PGCD est constant).
Application : On en tire une méthode de résolution de l’équation d’inconnues et . Soit le PGCD de et . Alors est divisible par donc l’équation n’a pas de solution si n’est pas divisible par . Si est divisible par , alors une solution particulière est . Les autres solutions vérifient Comme et sont premiers entre eux, divise donc
Application : décomposition d’une fraction
avec et premiers entre eux (entiers ou polynômes).
Il existe alors et tels que , donc et tels
que et
Exemple 1 : , on a
donc et
Exemple 2 : Les polynômes et sont premiers entre eux, on a l’identité de Bézout donc Ce qui permet de calculer une primitive de .
3.7 Les restes chinois
L’identité de Bézout a une application très importante en calcul formel, elle permet de reconstruire un entier dont on connait le reste de la division par des entiers premiers entre eux :
Soit et deux entiers naturels premiers entre eux, alors pour tout et entiers relatifs, il existe un entier tel que est divisible par et est divisible par .
Il existe une infinité de solutions, deux solutions et distinctes diffèrent par un multiple de . Il y a donc existence et unicité dans n’importe quel intervalle contenant entiers.
Remarque : lorsqu’on reconstruit un entier naturel plus petit que , on choisit l’unique valeur de telle que (c’est le reste de la division par de n’importe quelle solution), lorsqu’on reconstruit un entier relatif de valeur absolue plus petite que , on choisit tel que (on prend le reste symétrique).
Preuve : Si on a deux solutions et alors est divisible par et qui sont premiers entre eux, donc est divisible par . Cherchons une solution, cela revient à chercher et tels que On a donc Or et sont premiers entre eux, donc par Bézout, il existe et tels que il suffit de multiplier par pour trouver un couple de valeurs qui convient.
On sait que le couple n’est pas unique, si est une autre solution, alors donc donc divise et divise avec le même quotient. Pour déterminer efficacement une valeur de qui convient, on calculera tel que par l’algorithme de Bézout, on multipliera par et on prendra pour le reste de par .
Exemple : trouver tel que le reste de par 13 est 11 et le reste de par 7 est 3. On veut donc On a l’identité de Bézout : on multiplie par -8 donc on peut prendre (), on préfère prendre le reste par 13 donc , (et ).
Remarque : en calcul formel, on prend un produit de
nombres premiers distincts, et un nombre premier distinct
des précédents, on calcule la valeur de modulo chaque nombre
premier et dès qu’on a assez de nombres premiers
(i.e. si ) on en déduit .
On parle d’algorithme modulaire.
Par exemple, on peut calculer le déterminant d’une matrice à
coefficients entiers de cette manière.
Cette méthode s’applique aussi aux polynômes, mais elle est moins utile, car l’interpolation polynomiale de Lagrange est plus efficace lorsque les nombres premiers sont remplacés par des
3.8 Nombres premiers, factorisation
Exemple : 17 est premier, mais pas 18. 1 n’est pas premier.
Un nombre non premier admet un diviseur différent de 1 et lui-même donc une factorisation avec et . On peut supposer que , pour savoir si un nombre est premier on peut donc tester que tous les entiers plus petits ou égaux à ne divisent pas .
On a la
Si premier divise alors divise ou divise .
Si ne divise pas , alors la liste des diviseurs de ne
contient pas , donc la liste des diviseurs communs à et
est réduite à 1 donc et sont premiers entre eux.
Si divise , soit divise , soit est premier avec
donc par le lemme de Gauss il divise .
On en déduit :
Sinon, divise donc divise l’un des facteurs donc est égal à puisque est premier.
Preuve de l’existence par récurrence. Pour , on a . Pour , si est premier, alors , sinon il admet un plus petit diviseur strictement supérieur à 1, on a avec premier et à qui on applique l’hypothèse de récurrence.
Exercice : traduire cette preuve en un algorithme.
Unicité: supposons . Donc divise . Comme est premier, il doit être égal à un des . On divise par ce facteur commun et on recommence.
Remarques :
- La liste des diviseurs d’un entier se déduit de sa factorisation. En effet, un diviseur de a comme liste de facteurs premiers un sous-ensemble de la liste des facteurs premiers de (en raison de la proposition 10) avec des multiplicités inférieures ou égales.
- Le PGCD de deux entiers se déduit de la factorisation de ces entiers. En raison de ce qui précède, on reprend les facteurs premiers communs de et avec comme multiplicité la plus petite des deux. Ceci a surtout un intérêt théorique, car factoriser deux entiers est beaucoup plus couteux que de leur appliquer l’algorithme d’Euclide.
Des résultats analogues s’appliquent pour les polynômes à coefficients dans un corps: on parle de facteur irréductible (analogue de nombre premier), i.e. polynome divisible seulement par des polynomes constants et des polynomes multiples non nuls d’eux-mêmes. Par exemple tous les polynômes de degré 1 sont irréductibles. Un polynôme s’écrit de manière unique à permutation près et à inversibles près comme produit de facteurs irréductibles.
3.9 Prolongement : idéaux
Un idéal est un sous-ensemble d’un anneau qui est stable pour l’addition (la somme de deux éléments de l’idéal est encore dans l’idéal), et stable par multiplication par tout élément de l’anneau. Par exemple si on veut résoudre un système de plusieurs équations en une inconnue, l’ensemble des polynômes en une variable qui s’annulent en un point est un idéal.
Dans les anneaux euclidiens, tous les idéaux sont principaux, i.e. engendrés par un élément, on prend un plus petit entier en valeur absolue pour les entiers ou un plus petit polynôme en degré pour les polynômes, la division euclidienne par cet élément doit renvoyer 0 comme reste sinon on aurait un élément dans l’idéal plus petit. Dans l’exemple ci-dessus, on peut se ramener à une équation polynomiale en une variable (c’est un PGCD des polynômes des équations).
Dans les anneaux non euclidiens, comme par exemple les polynômes à plusieurs variables, les idéaux ne sont pas forcément engendrés par un seul élément, ce qui rend la résolution de systèmes polynomiaux à plusieurs inconnues beaucoup plus délicate qu’en une inconnue. Il est en général difficile de vérifier qu’un élément de l’anneau appartient ou non à l’idéal (alors que c’est juste une division dans le cas à une variable). Heureusement, il existe des familles particulières qui engendrent l’idéal pour lesquelles la vérification se fait par une généralisation de la division euclidienne. La recherche efficace de telles familles, appelées bases de Groebner, est un domaine de recherche actif!
4 L’anneau et le corps
4.1 Définition
4.1.1 Congruences
On dit que deux entiers et sont congrus modulo si est divisible par . On vérifie immédiatement les propriétés suivantes qui caractérisent une relation d’équivalence :
- (réflexivité) est toujours congru à modulo
- (symétrie) si est congru à modulo alors est congru à modulo
- (transitivité) si est congru à modulo et si est congru à modulo , alors est congru à modulo
L’ensemble des entiers congrus à modulo est appelé classe de modulo et est notée . Par exemple la classe de 3 modulo 11, Les propriétés ci-dessus montrent que si et seulement si et sont congrus modulo (ou encore si et seulement si ). Un élément de de est appelé représentant de la classe. Deux classes distinctes ont une intersection vide.
On peut définir les opérations de somme et de produit sur les classes, car ces opérations sont compatibles avec la divisibilié par , ainsi et le résultat ne dépend du choix du représentant dans la classe de et de . En effet si et sont deux autres représentants, on a et donc
Comme il y a reste possibles lorsqu’on divise un entier par , il y a classes distinctes, l’ensemble de ces classes est appelé et on vient de voir qu’il possède une structure d’anneau. Nous allons dans la suite de cette section nous intéresser aux propriétés de la multiplication dans cet anneau.
Optimisation : lorsqu’on travaille modulo un entier fixé, on peut optimiser les divisions euclidiennes par cet entier en utilisant un algorithme dû à Granlund et Montgomery. Le lecteur intéressé pourra rechercher des informations sur le site d’Agner Fog (volume 2, section 16.9).
4.1.2 Généralisation: relations d’équivalence, classes d’équivalence
On peut faire la même chose dans tout ensemble possédant une relation d’équivalence, i.e. une relation binaire vérifiant les propriétés de réflexivité, symétrie et transitivité, on obtient ainsi que est réunion disjointe de classes d’équivalence . Si est muni d’une loi de groupe + compatible avec la relation d’équivalence (i.e. et entraine que ), alors l’ensemble des classes d’équivalence muni de la loi + est un groupe. De même on obtient un anneau si on a compatibilité avec les deux lois + et d’un anneau .
Par exemple, sur les polynômes à coefficients dans un corps , étant donné un polynôme on peut définir la congruence modulo , i.e. si le reste de la division euclidienne de par est nul. On vérifie que cette relation est une relation d’équivalence, compatible avec l’addition et la multiplication des polynômes.
Autre exemple: si on a un anneau commutatif unitaire intègre, on peut construire son corps des fractions : il est constitué de couples , deux couples sont équivalents si . Si on a un anneau euclidien, on peut alors construire un représentant privilégié qui est une fraction telle que et soient premiers entre eux. En ajoutant des conditions pour tenir compte des inversibles de l’anneau, on peut construire un représentant unique. Ainsi pour le corps des fractions de , et sont premiers entre eux et . Pour celui des polynômes à coefficients rationnels, on peut montrer qu’on a un représentant sous forme de fraction de deux polynômes à coefficients entiers primitifs (le PGCD des coefficients vaut 1) de coefficient dominant strictement positif multiplié par une fraction irréductible d’entiers.
4.2 Le groupe des inversibles
L’ensemble des éléments inversibles pour la multiplication d’un
anneau forme un groupe multiplicatif. En effet le produit de deux
inversibles est encore inversible , le neutre
de la multiplication est inversible (son inverse est lui-même), et
tout élément admet un symétrique pour le produit puisqu’il est
supposé inversible.
Exemple : les inversibles modulo 8 sont 1, 3, 5 et 7. Pour obtenir la
table de multiplication du groupe, on peut valider la commande
On vérifie bien que chaque ligne contient un 1, donc chaque
élément est inversible.
Dans un groupe (avec une loi notée multiplicativement), on a le
Soit un groupe ayant un nombre fini d’éléments, alors pour tout , l’élément neutre du groupe (dans le produit avec les ..., il y a occurences de et signes ).
De plus le plus petit entier tel que , appelé ordre de l’élement , divise .
Exemple : pour les inversibles modulo 8, on a
Preuve dans le cas où est commutatif.
Soit fixé. L’application de dans qui à
associe est une bijection (de réciproque ). Donc le produit de tous les éléments de est égal
au produit de leurs images. Comme le groupe est supposé commutatif,
c’est le produit des images multiplié par , on en déduit que .
Montrons que divise : soit le reste de la division de
par , on a donc donc
donc car et est le plus petit entier non nul tel que .
Remarque : ce résultat est encore vrai si n’est pas commutatif, avec une preuve différente (cf. par exemple wikipedia).
Revenons aux entiers et aux entiers modulo . Soit donc un entier. Un entier est inversible modulo (ou une classe de congruence est inversible dans ) si et seulement si il existe un entier tel que est divisible par , donc si et seulement si il existe deux entiers et tels que , i.e. . On retrouve l’identité de Bézout. Les inversibles de sont les classes dont les représentants sont premiers avec et le calcul de l’inverse d’un entier modulo est obtenu par le coefficient de dans l’identité de Bézout pour et .
C’est le nombre d’éléments inversibles de . Par exemple si est premier, . Autre exemple, si , les inversibles sont et .
Le théorème de Lagrange donne alors :
Exemple : .
Remarque :On retrouve des résultats de la section motivation, si est
premier et , alors .
Calcul de connaissant la factorisation de
- Si est premier,
- si est une puissance d’un nombre premier . Comptons les nombres non premiers avec de , comme est premier, ils sont divisibles par , ce sont donc des multiples de inférieurs stricts à , i.e. des nombres de la forme inférieurs stricts à il y en a , donc
- Si est le produit de deux entiers et premiers entre eux
alors .
En effet, soit , alors et sont premiers entre eux si et seulement si et sont premiers entre eux d’une part et si et sont premiers entre eux d’autre part (considérer un diviseur premier commun). Ce qui revient au reste de la division de par premier avec et au reste de la division de par premier avec . Il y a donc possibilités pour et possibilités pour donc par le théorème des restes chinois, il y a possibilités pour .
Exemple : Si est le produit de deux nombres premiers distincts, on a Plus généralement, on a pour tout premier avec produit de deux nombres premiers distincts : résultat déjà obtenu dans la section motivations.
Remarques :
-
on a utilisé le théorème des restes chinois dans le
calcul de pour et premiers
entre eux. On peut traduire le théorème des
restes chinois en terme de de la manière
suivante:
Si et sont premiers entre eux, alors il existe une bijection entre et . Elle consiste à prendre un représentant de et lui associer le couple (classe de dans , classe de ). Cela ne dépend pas du représentant choisi. C’est une bijection à cause du théorème des restes chinois. De plus cette bijection est compatible avec les opérations d’addition et de multiplication dans les anneaux, on parle d’isomorphisme d’anneaux. Elle préserve les inversibles.
Exemple : et sont isomorphes, on peut construire la table de correspondance
les inversibles de la ligne du dessus correspondent aux couples d’inversibles en-dessous, donc le premier élément du couple doit être 1 ou 3, le deuxième 1 ou 2, ce qui donne 4 possibilités correspondant à . - connaitre la factorisation de ou la valeur de est équivalent. En effet si on connait et on en déduit la somme et le produit puis et en résolvant l’équation du second degré .
- si on connait la factorisation de , on peut calculer par récurrence :
4.3 L’algorithme d’exponentiation rapide
Il s’agit de calculer efficacement . On a vu dans la section motivation un algorithme récursif qui permet de le faire efficacement. On commence par remplacer par puis
- si on renvoie 1
- si est pair, on calcule , on renvoie
- si est impair, on calcule , on renvoie .
Il y a au plus ceil() multiplications, qui font intervenir des entiers plus petits que donc le coût est en (avec multiplication naive pour les entiers).
Il existe une version itérative de l’exponentiation rapide,
utilisant la décomposition en base 2 de l’exposant que l’on
calcule simultanément.
On initialise , , puis tant que
- si est impair, ,
- quotient euclidien de par 2,
et on renvoie . En effet après itérations, vaut et
4.4 Le corps
Lorsque est premier, tous les éléments de l’anneau sauf sont inversibles, on est alors en présence d’un corps ayant éléments, et dont le groupe multiplicatif des inversibles possède éléments.
Une propriété très importante des corps est que le nombre de racines d’un polynôme de degré est au plus . Cela résulte du fait qu’il n’y a pas de diviseurs de 0 dans un corps, donc si est racine de , alors on peut écrire où est de degré , les autres racines de sont exactement les racines de . Cette propriété n’est pas vraie lorsqu’on considère un polynôme à coefficients dans un anneau qui n’est pas un corps, par exemple admet 4 solutions 2, 7, 8, 13.
On rappelle que par le théorème de Lagrange, pour inversible modulo . On a donc car ces deux polynômes ont les mêmes racines distinctes et même coefficient dominant.
On peut alors se demander si l’ensemble des puissances de est égal à . On peut se poser la même question sur pour les racines complexes de qui sont de la forme la réponse sur est que c’est le cas si est premier avec , par exemple . Ici, on a le
est appelé racine primitive -ième de 1 (modulo ), parce que , mais pour . Les autres générateurs des inversibles sont obtenus en prenant pour premier avec , il y en a donc .
On donne la preuve de ce théorème pour être complet, étant entendu qu’elle se situe au-delà du niveau de ce cours. On factorise en produit de facteurs premiers, les étant deux à deux distincts. Montrons que pour tout , il existe un élément de d’ordre . En effet, le polynôme n’est pas identiquement nul sur car son degré est strictement inférieur à . Soit non racine de , on prend , on a et . On pose alors . On vérifie que est bien d’ordre avec le critère ci-dessous.
Critère : Pour savoir si est d’ordre , comme l’ordre d’un élément divise , si on connait les facteurs premiers de , il suffit de vérifier que Pour trouver un générateur de , on teste alors si convient en utilisant l’algorithme d’exponentiation rapide. La probabilité de trouver un générateur en tirant un entier au hasard dans vaut :
Remarque : la recherche de racines primitives est un ingrédient essentiel pour faire fonctionner la transformée de Fourier rapide exacte, plus précisément on cherche des racines primitives -ième de 1 modulo avec premier de la forme .
4.5 L’équation
Si est premier, l’équation est du premier degré dans un corps, donc
- si , elle admet une solution unique modulo :
- si , deux cas : si , tout est solution, si il n’y a pas de solutions.
Prolongement:
On peut faire le même raisonnement que ci-dessus lorsque est
inversible modulo , on a alors une solution unique.
Sinon, on écrit l’équation sous la forme
où est un représentant de la classe de et ,
de . Si le pgcd de et (qui est indépendant du
représentant choisi) vaut ,
il faut que soit divisible
par pour qu’il y ait des solutions (ceci ne dépend pas
des représentants choisis dans la classe déquivalence),
que l’on sait alors calculer en partant de l’identité
de Bézout pour et . Il y a alors
solutions modulo , en effet pour on a :
Si on connait la factorisation de , on peut un peu gagner en efficacité sur les calculs intermédiaires.
-
Si est une puissance d’un nombre premier
() et si . Notons l’inverse de modulo dans
l’intervalle .
Soit , on a .
Si est une solution de , alors donc . On pose
alors et on cherche .
On a donc
(car est bien un multiple de ).
On continue ainsi de proche en proche pour déterminer lécriture
de en base . C’est ce qu’on appelle une méthode p-adique.
Son intérêt est de faire des calculs intermédiaires avec des
entiers plus petits que au lieu de . On fait ainsi
étapes avec un cout en , i.e. , il faut
ajouter le cout de la décomposition en base de et
qui est en ,
au lieu d’une inversion modulaire avec un cout en mais avec une constante de proportionnalité plus
grande.
En pratique cette méthode -adique est surtout utilisée pour résoudre des grands systèmes linéaires à coefficients entiers. - Si n’est pas premier, on peut se ramèner au cas précédent par le théorème des restes chinois (méthode modulaire).
4.6 Prolongement : l’équation
Si , il y a une solution unique . Si , si l’équation a une solution, alors . Réciproquement, on si alors on montre qu’il y a bien 2 solutions opposées, en comptant les carrés non nuls (il y en a qui est le degré de , ils forment donc l’ensemble des solutions de ).
- Si , ces solutions s’obtiennent par l’algorithme d’exponentiation rapide .
- Si , il existe un algorithme probabiliste qui
fonctionne au moins une fois sur deux. Soit tel que .
L’idée est de tiret un entier
au hasard entre 0 et et de calculer le PGCD unitaire des polynômes
et , on s’arrête
lorsque le PGCD est de degré 1, ce sera alors ou .
En effet le PGCD est de degré 2 si et sont facteurs de donc si et sont racines de donc si . Le PGCD est de degré 0, si et sont racines de donc si . Dans les deux cas, . Cette équation en est de degré donc possède (au plus) solutions. Pour non solution, donc pour valeurs de (au moins), le PGCD sera de degré 1.
Exemple 1 : . On a donc
2 est un carré modulo 7, et les racines carrées sont
Exemple 2 : . On a donc
2 n’est pas un carré modulo 5.
Exemple 3 : . On a donc
4 est un carré modulo 5. Comme 4 est un carré parfait, les
deux racines carrées sont 2 et -2. Voici les PGCD de et
modulo 5 :
Vous pouvez essayer d’autres valeurs de premier pour vérifier
que tirer au hasard donne un PGCD de degré 1 environ une fois sur 2.
On peut alors résoudre les équations du second degré si avec la formule habituelle du discriminant.
Remarque
Comme pour l’équation du premier degré, on peut résoudre lorsque n’est pas premier. On commence par
le cas d’une puissance de nombre premier, où
on utilise un algorithme -adique comme dans la section précédente.
Le cas général il se traite avec le théorème des restes chinois.
Ainsi pour produit de deux premiers distincts, on a 4 racines
carrées en cas d’existence.
Par exemple, 4 admet 2, 7, 8, 13 comme racines carrées modulo 15.
4.7 Prolongement:
L’anneau des polynômes à coefficients dans est un anneau euclidien, on peut en effet effectuer des divisions euclidiennes comme sur . On peut donc définir le PGCD de 2 polynômes non simultanément nuls de (normalisé par coefficient dominant 1), et les calculs sont beaucoup plus simples que sur , et c’est l’ingrédient principal des algorithmes efficaces de calcul de PGCD sur .
4.8 Test de primalité
Test de Fermat : Si est premier et premier avec ,
alors . Donc si on trouve
un entier premier avec
tel que alors n’est pas
premier.
Exemple : et
Il est important de voir qu’on a alors prouvé que n’est
pas premier sans déterminer un facteur non trivial de .
Le temps de calcul d’un test de Fermat est proportionnel à
, alors que la factorisation naïve de peut couter .
Attention, si pour deux entiers et premiers entre eux, cela ne prouve pas que est premier! Il existe d’ailleurs des entiers non premiers pour lesquels cette propriété est vraie pour tout premier avec , ce sont les nombres de Carmichael, le plus petit est 561.
Exercice : Implémenter un programme déterminant tous les nombres de Carmichael plus petits que .
Prolongement : test de Miller-Rabin : c’est une version améliorée du test précédent. Il part de l’observation suivante : soit premier avec nombre premier impair, si alors (1 a exactement deux racines carrées dans ). Si et est pair, on peut recommencer le même raisonnement. Plus précisément, soit on trouve un -1, soit on ne peut plus diviser par 2 l’exposant. En raisonnant dans l’autre sens, si avec impair, alors ou l’un des carrés successifs doit valoir -1 modulo (si on tombe sur un 1 le test échoue et le nombre n’est pas premier)
def millerrabin(a,n): t=0 s=n-1 while s % 2==0: t += 1 s = s // 2 a=pow(a,s,n) if a==1 or a==n-1: return True for j in range(t): a=(a*a) % n if a==1: return False if a==n-1: return True return False
onload
Comme pour le test de Fermat, si le test renvoie faux, on est sur
que le nombre n’est pas premier ( est appelé témoin de Miller),
s’il renvoie vrai on ne peut rien prouver.
Mais on peut montrer que si est non premier, le nombre d’entiers
qui renvoient True
est au plus , ce qu’on
peut plus ou moins traduire par si on tire
entiers “au hasard” et que le test de Miller-Rabin renvoie vrai
à chaque fois, il y a moins d’une chance sur que ne soit
pas premier. Par exemple pour et en prenant les 20 premiers
nombres premiers, est de l’ordre de 1e-12
, la
probabilité de considérer premier un nombre qui ne l’est pas
est très faible. On parle de nombre pseudo-premier.
Remarques :
- On peut montrer qu’un entier 64 bits est premier si et seulement si il passe le test de Miller-Rabin pour les 12 premiers nombres premiers (de 2 à 37).
- Si l’hypothèse de Riemann généralisée (voir par exemple wikipedia) est vraie, et est composé, on peut montrer qu’il existe un témoin de Miller plus petit que , et le test de Miller-Rabin pour tous les entiers plus petit que devient un test déterministe de primalité (en opérations avec multiplication naïve).
4.9 Application: cryptographie RSA
4.9.1 Interprétation du codage et du décodage.
Nous revenons sur la cryptographie RSA, en utilisant les notions vue dans les sections précédentes. On rappelle qu’on se donne un entier produit de deux nombres premiers et . Les entiers et sont secrets alors que est public. On choisit ensuite deux entiers et tels que , ou encore et sont inverses modulo l’indicatrice d’Euler de : Le codage et le décodage se font avec l’algorithme de la puissance modulaire rapide : le cout est de et opérations.
On peut maintenant facilement montrer que ces deux fonctions sont réciproques l’une de l’autre lorsque est premier avec . En effet, on a donc :
4.9.2 Comment générer des clefs
On choisit et en utilisant le test de Miller-Rabin. Par
exemple
On choisit un couple de clefs privée-publique en utilisant
l’identité de Bézout (ou inverse modulaire). Par exemple
Ici, on a pris E
de sorte que l’exponentiation
modulaire rapide à la puissance E
nécessite peu d’opérations arithmétiques (17),
comparé au calcul de la puissance d
, ceci
permet de faire l’opération “publique” plus rapidement
(encodage ou vérification d’une signature) si le microprocesseur
a peu de ressources (par exemple puce d’une carte bancaire).
On peut utiliser un programme tel que ssh-keygen
pour
générer des clefs de taille industrielle. Par exemple la commande
suivante
ssh-keygen -t rsa -b 2048 -m PEM -f rsa2048.key
génère une clef 2048 bits stockée dans le fichier
rsa2048.key
, on peut afficher en clair les valeurs
de avec la commande
openssl rsa -in rsa2048.key -text -inform PEM -noout
On obtient une sortie analogue à ce qui suit
Private-Key: (2048 bit) modulus: 00:a1:e8:58:f4:81:34:c7:0c:e6:4e:12:a3:22:f3: 2b:49:c4:7a:e0:c0:b6:11:07:3f:ea:f3:23:eb:d2: 3d:97:99:35:65:ce:42:d6:9a:e9:53:3e:60:a2:90: 28:7a:bf:23:1f:84:b0:10:4a:15:8c:dd:e1:29:43: 53:cb:74:22:4f:f6:ae:ec:62:35:1e:61:0b:66:2e: fe:21:41:c1:56:6d:68:70:51:8b:42:6a:db:ca:13: e1:06:1d:db:da:33:82:96:70:0c:df:d6:4f:51:c2: 51:57:ae:ec:a8:6d:06:f3:a3:02:b7:5d:79:3e:58: 1b:e2:a2:e6:d8:97:58:c9:7f:f6:81:8d:ab:1e:9b: 22:0a:4f:77:41:49:e6:5c:71:03:ee:70:9a:60:22: 28:76:c1:53:a7:99:06:2a:22:9c:0b:9d:68:cc:2f: 36:5a:84:bd:b1:03:36:cc:be:07:4b:17:8c:85:84: ef:d5:1e:31:e0:82:32:0e:52:85:d0:8e:40:0d:a8: ac:c7:ff:c1:c0:e9:fe:6c:49:a5:c9:ff:cc:15:f1: 2b:1c:c6:70:29:97:77:da:3e:7c:f7:3f:bf:6f:17: 91:ee:b6:5b:90:d8:ba:49:07:96:1b:a7:a9:5e:37: 8f:4c:be:de:c4:07:ef:12:e6:78:a0:4d:95:42:13: 2e:3b publicExponent: 65537 (0x10001) privateExponent: 69:0e:97:f2:07:88:d4:84:15:48:a1:a5:43:7f:60: 1e:5c:a4:93:03:d8:df:d1:c1:72:d5:d4:00:28:0a: 99:3c:eb:be:24:89:90:31:32:a7:36:39:84:22:60: 71:cd:66:a0:03:fc:2e:85:b3:d8:14:fd:0e:46:46: b0:24:aa:43:12:c1:4c:57:29:3a:8e:23:d4:69:37: b3:22:b4:ae:3d:0d:e0:9b:b8:ee:1e:e2:81:0c:47: 1e:2d:ef:c3:75:5b:0d:fc:a5:0d:f5:44:c0:bb:83: 06:8f:55:b6:b0:10:2b:b5:21:85:13:dd:21:4c:10: c4:0d:8a:17:0e:95:a9:21:1b:91:17:86:07:de:74: d5:3f:05:21:e7:57:67:3b:32:de:68:cf:a5:9b:61: 6f:f7:89:c3:db:a4:75:53:71:b8:77:ab:b5:22:e7: dc:23:43:30:26:3b:d0:0f:6a:46:d0:b6:51:88:49: 4b:c8:25:3e:b6:eb:32:cf:56:63:db:7a:79:43:35: 0f:f7:e5:9e:fa:60:66:c7:78:f3:e8:a9:4e:c2:ed: 3d:04:a9:c3:7d:ae:23:86:25:e9:8d:a9:5c:33:4a: db:16:31:85:88:b6:c1:5b:bd:02:4b:41:73:eb:c1: 9c:d1:0b:ee:d8:74:4d:a4:ca:ab:0a:97:4d:a0:26: 59 prime1: 00:d7:99:be:f5:71:ee:79:b1:a0:28:77:f3:eb:10: ab:83:0a:f4:00:b9:76:ca:4c:d1:1a:00:97:37:de: 32:3f:b1:5c:fb:8d:e5:ec:9b:ec:c6:10:06:2d:2e: 13:38:97:4a:f4:f8:5d:1a:3b:9b:e2:cb:74:4d:b7: 31:f5:22:f2:f4:c1:25:78:2d:df:ee:ca:00:0f:65: 16:5b:fe:ea:e3:49:02:61:05:52:cd:d9:66:42:34: b9:d0:09:23:62:ff:c5:51:2f:b7:fb:bf:62:ed:02: a5:7c:85:c6:e4:db:9f:6f:4a:44:ca:41:01:40:c9: 8b:a1:53:0f:f5:b2:4c:a0:a5 prime2: 00:c0:3e:f9:f1:c9:a9:ed:23:7c:ae:f3:41:97:0f: 4a:83:b6:df:e0:1f:e1:b7:4c:b8:d6:3f:79:0c:41: 29:e9:5b:f8:95:48:8d:7d:10:8c:8b:a0:b7:09:91: e2:aa:35:31:60:b9:d3:88:0f:d1:33:61:0c:b3:55: 4f:dc:ed:18:1d:d9:7a:7e:1a:e4:61:11:2c:4a:2d: 03:3d:02:b3:e2:48:ca:e9:08:e0:bd:da:90:a4:00: f5:cc:ed:9b:90:5e:c0:17:e1:4e:aa:4c:94:5d:14: 79:7a:b7:c0:39:9f:36:45:df:38:48:7d:32:48:5e: 17:d7:ba:ea:d5:96:f7:7d:5f
Exercice : vérifiez que le produit de prime1
par
prime2
donne bien modulus
et que l’indicatrice
d’Euler est correcte, ainsi que la clef secrète.
Ci-dssous un programme C de cryptage/décryptage utilisant ces clefs.
Il utilise la librairie GMP pour les opérations sur les entiers de
grande taille et se compile par la commande
gcc rsa.c -lgmp
Bien entendu, un vrai programme de
cryptage/décryptage ne doit pas contenir de clé(s) privée(s) dans son
code source, car le code objet les contiendra, il doit les lire dans un fichier.
#include <gmp.h> #include <stdio.h> const unsigned char rsa_n_tab[]={ 0xa1,0xe8,0x58,0xf4,0x81,0x34,0xc7,0x0c,0xe6,0x4e,0x12,0xa3,0x22,0xf3, 0x2b,0x49,0xc4,0x7a,0xe0,0xc0,0xb6,0x11,0x07,0x3f,0xea,0xf3,0x23,0xeb,0xd2, 0x3d,0x97,0x99,0x35,0x65,0xce,0x42,0xd6,0x9a,0xe9,0x53,0x3e,0x60,0xa2,0x90, 0x28,0x7a,0xbf,0x23,0x1f,0x84,0xb0,0x10,0x4a,0x15,0x8c,0xdd,0xe1,0x29,0x43, 0x53,0xcb,0x74,0x22,0x4f,0xf6,0xae,0xec,0x62,0x35,0x1e,0x61,0x0b,0x66,0x2e, 0xfe,0x21,0x41,0xc1,0x56,0x6d,0x68,0x70,0x51,0x8b,0x42,0x6a,0xdb,0xca,0x13, 0xe1,0x06,0x1d,0xdb,0xda,0x33,0x82,0x96,0x70,0x0c,0xdf,0xd6,0x4f,0x51,0xc2, 0x51,0x57,0xae,0xec,0xa8,0x6d,0x06,0xf3,0xa3,0x02,0xb7,0x5d,0x79,0x3e,0x58, 0x1b,0xe2,0xa2,0xe6,0xd8,0x97,0x58,0xc9,0x7f,0xf6,0x81,0x8d,0xab,0x1e,0x9b, 0x22,0x0a,0x4f,0x77,0x41,0x49,0xe6,0x5c,0x71,0x03,0xee,0x70,0x9a,0x60,0x22, 0x28,0x76,0xc1,0x53,0xa7,0x99,0x06,0x2a,0x22,0x9c,0x0b,0x9d,0x68,0xcc,0x2f, 0x36,0x5a,0x84,0xbd,0xb1,0x03,0x36,0xcc,0xbe,0x07,0x4b,0x17,0x8c,0x85,0x84, 0xef,0xd5,0x1e,0x31,0xe0,0x82,0x32,0x0e,0x52,0x85,0xd0,0x8e,0x40,0x0d,0xa8, 0xac,0xc7,0xff,0xc1,0xc0,0xe9,0xfe,0x6c,0x49,0xa5,0xc9,0xff,0xcc,0x15,0xf1, 0x2b,0x1c,0xc6,0x70,0x29,0x97,0x77,0xda,0x3e,0x7c,0xf7,0x3f,0xbf,0x6f,0x17, 0x91,0xee,0xb6,0x5b,0x90,0xd8,0xba,0x49,0x07,0x96,0x1b,0xa7,0xa9,0x5e,0x37, 0x8f,0x4c,0xbe,0xde,0xc4,0x07,0xef,0x12,0xe6,0x78,0xa0,0x4d,0x95,0x42,0x13, 0x2e,0x3b }; int public_key=65537; const unsigned char pri_n_tab[]={ 0x69,0x0e,0x97,0xf2,0x07,0x88,0xd4,0x84,0x15,0x48,0xa1,0xa5,0x43,0x7f,0x60, 0x1e,0x5c,0xa4,0x93,0x03,0xd8,0xdf,0xd1,0xc1,0x72,0xd5,0xd4,0x00,0x28,0x0a, 0x99,0x3c,0xeb,0xbe,0x24,0x89,0x90,0x31,0x32,0xa7,0x36,0x39,0x84,0x22,0x60, 0x71,0xcd,0x66,0xa0,0x03,0xfc,0x2e,0x85,0xb3,0xd8,0x14,0xfd,0x0e,0x46,0x46, 0xb0,0x24,0xaa,0x43,0x12,0xc1,0x4c,0x57,0x29,0x3a,0x8e,0x23,0xd4,0x69,0x37, 0xb3,0x22,0xb4,0xae,0x3d,0x0d,0xe0,0x9b,0xb8,0xee,0x1e,0xe2,0x81,0x0c,0x47, 0x1e,0x2d,0xef,0xc3,0x75,0x5b,0x0d,0xfc,0xa5,0x0d,0xf5,0x44,0xc0,0xbb,0x83, 0x06,0x8f,0x55,0xb6,0xb0,0x10,0x2b,0xb5,0x21,0x85,0x13,0xdd,0x21,0x4c,0x10, 0xc4,0x0d,0x8a,0x17,0x0e,0x95,0xa9,0x21,0x1b,0x91,0x17,0x86,0x07,0xde,0x74, 0xd5,0x3f,0x05,0x21,0xe7,0x57,0x67,0x3b,0x32,0xde,0x68,0xcf,0xa5,0x9b,0x61, 0x6f,0xf7,0x89,0xc3,0xdb,0xa4,0x75,0x53,0x71,0xb8,0x77,0xab,0xb5,0x22,0xe7, 0xdc,0x23,0x43,0x30,0x26,0x3b,0xd0,0x0f,0x6a,0x46,0xd0,0xb6,0x51,0x88,0x49, 0x4b,0xc8,0x25,0x3e,0xb6,0xeb,0x32,0xcf,0x56,0x63,0xdb,0x7a,0x79,0x43,0x35, 0x0f,0xf7,0xe5,0x9e,0xfa,0x60,0x66,0xc7,0x78,0xf3,0xe8,0xa9,0x4e,0xc2,0xed, 0x3d,0x04,0xa9,0xc3,0x7d,0xae,0x23,0x86,0x25,0xe9,0x8d,0xa9,0x5c,0x33,0x4a, 0xdb,0x16,0x31,0x85,0x88,0xb6,0xc1,0x5b,0xbd,0x02,0x4b,0x41,0x73,0xeb,0xc1, 0x9c,0xd1,0x0b,0xee,0xd8,0x74,0x4d,0xa4,0xca,0xab,0x0a,0x97,0x4d,0xa0,0x26, 0x59 }; void tabunsignedchar2mpz(const unsigned char tab[],int len,mpz_t & res){ mpz_set_si(res,0); for (int i=0;i<len;++i){ mpz_mul_si(res,res,256); // res=256*res; mpz_add_ui(res,res,tab[i]); // res+=tab[i]; } } // ./rsa crypt|decrypt filein fileout int main(int argc,const char ** argv){ if (argc<4){ printf("Usage: %s c|d filein fileout\nc crypt, d decrypt\n",argv[0]); return 1; } mpz_t z,p,n; mpz_init(z); mpz_init(p); mpz_init(n); tabunsignedchar2mpz(rsa_n_tab,sizeof(rsa_n_tab),n); if (argv[1][0]=='c'){ printf("crypting\n"); mpz_set_si(p,public_key); } else { tabunsignedchar2mpz(pri_n_tab,sizeof(pri_n_tab),p); printf("decrypting\n"); mpz_out_str(stdout,10,n); printf("\n"); mpz_out_str(stdout,10,p); printf("\n"); } FILE * f=fopen(argv[2],"r"); int res=mpz_inp_str(z,f,0); fclose(f); mpz_powm(z,z,p,n); f=fopen(argv[3],"w"); mpz_out_str(f,10,z); fclose(f); mpz_clear(z); mpz_clear(p); mpz_clear(n); }
4.9.3 Sur quoi repose la sécurité de RSA.
-
Difficulté de factoriser .
Si on arrive à factoriser , tous les autres calculs se font en temps polynomial en donc la clef est compromise. Il faut bien choisir et pour que certains algorithmes de factorisation ne puissent pas s’appliquer. Par exemple choisir
n’est pas une bonne idée car a beaucoup trop de 0 dans son écriture décimale, on peut donc facilement le factoriser, en essayant les diviseurs de 801 ajoutés à . - Difficulté de trouver connaissant et , si on ne connait pas
la factorisation de .
On peut tenter de trouver sans chercher à factoriser . Notons que ce n’est pas avec l’identité de Bézout qu’on procèdera, car connaitre l’indicatrice d’Euler de permet de trouver et immédiatement. Une attaque de ce type pour et presque egaux et petit devant est décrite par exemple ici. - Difficulté de trouver connaissant .
Il n’y a pas d’algorithme connu permettant de le faire dans tous les
cas.
Mais il faut quand même faire attention au choix de .
Par exemple prendre pour minimiser le nombre d’étapes de la puissance modulaire n’est pas judicieux, car si un message commun est envoyé à 3 personnes ayant des clefs publiques , , , la connaissance de permet de retrouver par le lemme chinois si sont premiers entre eux 2 à deux, puis puis .
Il faut aussi que l’ordre de dans les inversibles de ne soit pas trop petit, sinon un attaquant peut itérer la fonction de cryptage sur et obtenir .
Une référence sur l’utilisation de RSA pour la sécurisation des transactions par carte bancaire.
4.10 Pour aller plus loin
Les polynômes de modulo un polynôme irréductible de degré forment un corps fini de cardinal une puissance d’un nombre premier . On montre que les corps de cardinal fini sont tous de cette forme.
La recherche de solutions est un objectif de pas mal d’algorithmes de factorisation, par exemple cfrac (fractions continues), le crible quadratique, et même l’algorithme de Shor (pour ordinateur quantique).
5 Algèbre linéaire
5.1 Systèmes linéaires sur , pivot de Gauss
Les systèmes linéaires sur se résolvent comme les systèmes linéaires à coefficients réels ou complexes, par combinaison linéaire d’équations. On peut encoder par une matrice, en créant une ligne de matrice par équation et en y stockant les coefficients des inconnues. Les combinaisons linéaires d’équations correspondent alors à des combinaisons linéaires de lignes de la matrice. L’algorithme du pivot de Gauss consiste à triangulariser la matrice de manière équivalente (i.e. avec des opérations réversibles).
Algorithme du pivot de Gauss (sur un corps)
Soit une matrice ayant lignes et colonnes. On numérote
à partir de 0.
- initialiser les indices du pivot ligne et à 0.
- Tant que et faire :
-
Parcourir la colonne à partir de la ligne et chercher
un coefficient non nul.
Si tous les coefficients sont nuls, incrémenter de 1 et passer à l’itération suivante de la boucle Tant que. - Si nécessaire, échanger la ligne ayant un coefficient non nul avec la ligne
- Maintenant . Si on effectue : Cette opération est réversible car on multiplie la ligne par un coefficient non nul.
- Pour de à effectuer la manipulation de ligne sur la
matrice (dont la ligne a été modifiée) :
Cette opération crée un 0 à la ligne en colonne
sans détruire les 0 existants en ligne pour des indices de
colonne strictement inférieurs à .
Cette opération est réversible d’inverse (avec le coefficient de la matrice avant l’opération) - Incrémenter et de 1.
-
Parcourir la colonne à partir de la ligne et chercher
un coefficient non nul.
Toutes les opérations sont réversibles, donc le système obtenu est équivalent au système initial. Mais il est plus simple, car triangulaire supérieur, il y a des 0 en-dessous d’une “diagonale généralisée” (normalement c’est une diagonale, mais elle peut se décaler vers la droite s’il y a des colonnes où on n’a pas trouvé de 0). On résoud alors le système de bas en haut.
On peut aussi faire l’étape 2d pour de 0 jusque et et obtenir une matrice échelonnée, avec des 0 de part et d’autre de la diagonale, plus précisément des 0 dans toutes les colonnes correspondant à des coefficients non nuls sur la diagonale généralisée.
def piv(M): L,C=dim(M) l,c=0,0 while l<L and c<C: for j in range(l,L): if M[j,c]!=0: break if j==L: c += 1 continue if j!=l: M[j],M[l]=M[l],M[j] if M[l,c]!=1: M[l]=inv(M[l,c])*M[l] for j in range(l+1,L): M[j] -= M[j,c]*M[l] c += 1 l += 1 return M
onload
Donc :
sa solution s’obtient en résolvant l’équation correspondant à la dernière
ligne de la matrice réduite, donc , puis
l’avant-dernière ligne donne puis la première
équation donne .
On vérifie
Prolongement:
L’algorithme du pivot de Gauss peut s’effectuer directement sur la
matrice d’un système sans son second membre, c’est la factorisation
d’une matrice, qui permet de se ramener à résoudre
deux systèmes triangulaires.
Optimisations
- Si , on peut représenter les lignes comme des suites de bits, donc travailler 64 colonnes simultanément avec une représentation par des entiers non signés sur 64 bits. Une combinaison linéaire non triviale de 2 lignes est alors une addition qui se traduit par un ou exclusif sur les entiers 64 bits de la représentation.
- si on veut faire la réduction complète d’une grande matrice sous forme échelonnée, il est plus rapide de commencer par faire une réduction sous-diagonale complète, puis une réduction au-dessus de la diagonale.
- Pour , l’opération qui est la plus couteuse dans une
combinaison linéaire de lignes est le calcul du reste euclidien par
. Il peut être intéressant d’effectuer plusieurs combinaisons
linéaires de lignes avec une ligne donée et faire un seul calcul
de reste, en prenant garde à éviter les dépassements.
Ainsi si avec le nombre d’opérations, on peut utiliser des entiers 64 bits pour toutes les opérations intermédiaires, sans réduction modulo avant la fin. Si on a seulement , on peut représenter par des entiers non signés 64 bits, effectuer une opération et enlever si le résultat dépasse .
En particulier la réduction au-dessus de la diagonale d’une matrice réduite sous la diagonale se prête à ce genre d’optimisation. - Il est possible d’utiliser des algorithmes diviser pour régner en faisant des opérations par blocs et en se ramenant à des multiplications de matrice plus rapides que la multiplication naïve.
5.2 Algèbre linéaire sur
La théorie des espaces vectoriels sur se généralise à un corps fini. Les définitions et propriétés d’espace vectoriel, de dimension finie, de famille libre, génératrice, de bases, d’applications linéaires, de matrice d’applications linéaires sont identiques.
On montre qu’un sous-ensemble de stable par addition (si alors ) et par multiplication par une constante de (si alors ) est un espace vectoriel, on dit que c’est un sous-espace vectoriel de .
Dans la suite, on n’écrira plus explicitement le signe de multiplication entre un scalaire et un vecteur.
Exemples d’espaces vectoriels: (qui est en bijection avec les entiers naturels plus petits que , mais attention l’addition des entiers ne correspond pas à l’addition dans l’espace vectoriel), les polynômes de degré inférieur à à coefficients dans , les matrices à coefficients dans
Une combinaison linéaire de est un vecteur de la forme avec les . On vérifie que l’ensemble des combinaisons linéaires de est un sous-espace vectoriel de noté Vect.
On dit que est libre si aucun des vecteurs de n’est combinaison linéaire des autres ce qui revient à dire que la relation entraine .
Une famille est génératrice si tout vecteur de peut s’écrire comme combinaison linéaire des éléments de .
Un espace vectoriel est de dimension finie s’il admet une famille génératrice (ayant un nombre fini d’éléments).
D’une famille génératrice finie (non réduite à des vecteurs nuls), on peut extraire une famille génératrice et libre. En effet si est libre, c’est terminé. Sinon, l’un des vecteurs de est combinaison linéaire des autres, on peut l’enlever de en conservant l’espace vectoriel engendré. Comme le nombre d’éléments de est fini, on s’arrête.
Par exemple est une famille génératrice de Vect() et on peut éliminer de la famille pour la rendre libre (on peut aussi éliminer 1 ou éliminer ).
En effet, tout élément de peut sécrire de manière unique comme combinaison linéaire des éléments de la famille. Les composantes sur une base fixée peuvent prendre valeurs chacune, il y a donc éléments dans . C’est une grande différence avec (qui simplifie la preuve que toutes les bases ont le même nombre d’éléments).
Sinon, on aurait pour les combinaisons linéaires d’une famille libre trop d’éléments, et pour les combinaisons linéaires d’une famille génératrice pas assez.
Exemples : la multiplication par des polynômes de degré inférieur à 2 () dans les polynômes de degré inférieur à 3 (), ou la dérivée de dans .
Si et sont de dimension finie, de base et de base on peut caractériser par les coordonneées des dans la base . On appelle matrice de dans ces bases le tableau des coordonnées des en colonnes. On a alors On vérifie que si a pour coordonnées le vecteur colonne , alors a pour coordonnées le vecteur colonne
Exercice : calculer les matrices des exemples ci-dessus.
Soit , les coordonnées de dans la base s’obtiennent alors en faisant le produit de la matrice de par le vecteur colonne des coordonnées de dans la base
La somme de 2 matrices correspond alors à la somme de deux applications linéaires ayant ces matrices, la multiplication d’une matrice par un élément de correspond à la multiplication de l’application linéaire correspondante par cet élément. Le produit de deux matrices correspond à la composition de deux applications linéaires (c’est d’ailleurs une manière de prouver que le produit de deux matrices est associatif).
L’ensemble des vecteurs tels que est un sous-espace vectoriel de appelé noyau de et noté Ker. Par abus, on note aussi Ker le noyau de l’application linéaire de matrice dans les bases canoniques de et . Une application linéaire est injective si et seulement si son noyau est réduit au vecteur nul.
L’ensemble des images est un sous-espace vectoriel de appelé image de et noté Im. De même, on parle de Im par abus de notation. La dimension de l’image est appelé range de (ou de la matrice ).
Preuve : on utilise le procédé de création d’une base à partir d’une famille génératrice de Im() obtenue en prenant les images des vecteurs d’une base de , i.e. on transpose la matrice de l’application linéaire et on applique Gauss. Les combinaisons linéaires sur la base de de l’algorithme de Gauss forment toujours une base de . Les lignes nulles de correspondent à des vecteurs qui forment une base de Ker, les lignes non nulles forment une base de Im.
L’algorithme du pivot de Gauss permet de traiter tous les aspects calculatoires :
- liberté d’une famille, on résoud le système par le pivot de Gauss,
- extraction d’une famille génératrice, on écrit les coordonnées de la famille dans une matrice en ligne, et on garde les lignes non nulles après le pivot.
- rang d’une matrice, il est conservé par le pivot de Gauss, c’est le nombre de lignes non nulles à la fin
- détermination d’une base de l’image d’une application linéaire de matrice , il suffit de mettre en ligne les vecteurs colonnes de la matrice (puisqu’ils forment une famille génératrice), et de faire le pivot de Gauss (puisque les opérations de ligne sont alors des combinaisons linéaires de vecteurs),
- détermination du noyau
d’une matrice, on résoud le système correspondant.
Remarque : il existe une version plus algorithmique de calcul du noyau. - inverse d’une matrice de taille . On résout simultanément systèmes linéaires avec comme second membre les vecteurs de la base canonique.
- déterminant d’une matrice. Il faut refléter l’effet des opérations sur les lignes sur le déterminant: un échange de ligne change le signe du déterminant, la multiplication de l’étape 2c multiplie le déterminant
5.3 Application : cryptographie de Hill
Au lieu de coder un message caractère par caractère, ce qui est sujet à des attaques par fréquence, on décide de grouper les caractères par -uplets et on les considère comme des vecteurs de (en fait, il n’est pas indispensable de travailler sur un corps, on peut aussi travailler sur un anneau ). On choisit une matrice carrée de taille inversible sur ce corps qui sera la clef secrète de chiffrement. Le message crypté correspondant au message en clair est alors le message . On retrouve connaissant le message crypté et la clef de chiffrement en calculant .
Pour assurer la sécurité du cryptage, il faut bien sur prendre et assez grands pour que l’ensemble des matrices possibles soit suffisamment grands (il y a matrices possibles, et elles ne sont pas toutes inversibles, la première colonne ne doit pas être nulle, la deuxième ne doit pas être dans l’espace engendré par la première, etc. il y a donc matrices inversibles). On peut aussi décider d’éviter les attaques par fréquence en prenant une partie de -uplet dans le message à coder, et une autre partie avec des caractères aléatoires (le message crypté sera plus long mais le cryptage sera plus résistant).
5.4 Application aux codes
Cette section ne sera peut-être plus au programme
à partir de 2021/22 en raison de la diminution de l’horaire de l’UE
On travaillera dans cette section avec , i.e. sur le corps
, mais les définitions et énoncés
restent valables dans ou dans un corps
fini plus général.
On appellera symbole d’information l’unité de base transmise, qu’on supposera appartenir à un corps fini , par exemple pour un bit un élément de (pour un octet, il faudrait prendre un élément du corps à 256 éléments ).
On veut coder un message de longueur avec des possibilités de détection et de correction d’erreurs, pour cela on rajoute des symboles calculés à partir des précédents, on envoie un élément d’un code ayant symboles.
5.4.1 Code de répétition
Par exemple, on peut décider de répéter 3 fois chaque symbole . Le récepteur du message peut détecter des erreurs de transmission (sauf si la même altération se produit 3 fois de suite, ce qui est très improbable). Il peut corriger des erreurs par exemple s’il reçoit 2 fois le même symbole et un 3ième symbole distinct , il garde le symbole . Mais ce code simple est assez couteux à transmettre, on envoie 3 fois plus de données qu’il n’y a d’informations. On va présenter des méthodes plus efficaces.
5.4.2 Le bit de parité.
On prend bits et bits. On compte le nombre de 1 parmi les 7 bits envoyés, si ce nombre est pair, on envoie 0 comme 8ième bit, sinon 1. Au final le nombre de bits à 1 de l’octet (1 octet=8 bits) est pair. On peut ainsi détecter une erreur de transmission si à la réception le nombre de bits d’un octet est impair, mais on ne peut pas corriger d’erreurs.
5.4.3 Codes linéaires
L’ensemble des mots de code est l’image de , c’est un sous-espace vectoriel de . La matrice de d’un code linéaire s’obtient en calculant les images des vecteurs de la base canonique.
Exemple : le code de répétition est un code linéaire de
paramètres
, de matrice la matrice colonne
.
Le bit de parité est un
code linéaire de paramètres , de matrice
Remarque : On utilise ici la convention de l’algèbre linéaire (avec des vecteurs colonnes et multiplication par une matrice à gauche). Certains auteurs utilisent l’autre convention, i.e. des vecteurs lignes et multiplication par la matrice à droite, tout est transposé. Ceci vient peut-être du fait qu’il est plus facile de saisir un vecteur ligne qu’un vecteur colonne avec certains logiciels. Xcas utilise la convention que si on multiplie une matrice par un vecteur ligne, alors le vecteur ligne est automatiquement transformé en vecteur colonne (il n’y a en effet aucune confusion possible).
Pour assurer qu’on peut identifier un antécédent unique à partir d’une image, il faut que corresponde à une application linéaire injective, ce qui entraine . On dit qu’un vecteur de symboles est un mot du code s’il est dans l’image de l’application linéaire.
Pour assurer l’injectivité tout en facilitant le décodage, on utilise très souvent une matrice identité comme sous-bloc de la matrice , par exemple on prend l’identité pour les premières lignes de , on ajoute ensuite lignes. On parle de code systématique.
Pour savoir si un vecteur est un mot de code, il faut vérifier qu’il est dans l’image de . On peut par exemple vérifier qu’en ajoutant la colonne de ses coordonnées à , on ne change pas le rang de (qui doit être ), mais c’est assez couteux. On préfère déterminer une matrice de controle telle que :
En effet : donc :
Exemple, pour le bit de parité, et .
5.4.4 Codes polynomiaux
Il s’agit d’un cas particulier de codes linéaires.
Le récepteur peut controler que le reste de la division euclidienne du polynôme reçu par est nul, et extraire l’information qui est le quotient de la division euclidienne par . Ce code n’est pas systématique, mais on peut facilement l’adapter pour le rendre systématique.
Les mots de code sont les polynômes divisibles par .
Exemple : le bit de parité correspond à . Le polynôme () était utilisé par le Minitel.
5.4.5 Erreurs
Si le mot recu n’est pas dans l’image de l’application linéaire il y a eu erreur de transmission. Sinon, il n’y a pas eu d’erreur détectable (il pourrait y avoir eu plusieurs erreurs qui se “compensent”).
Plutôt que de demander la réémission du mot mal transmis
(ce qui serait par exemple impossible en temps réel
depuis un robot en orbite autour de Mars),
on essaie d’ajouter suffisamment d’information pour
pouvoir corriger des erreurs en supposant
que leur nombre est majoré par .
Si les erreurs de
transmissions sont indépendantes, la probabilité d’avoir
au moins erreurs dans un message de longueur
est ,
où est la probabilité d’une erreur
de transmission, c’est aussi 1-binomial_cdf(L,epsilon,N)
.
Par exemple, pour un message de caractères,
chacun ayant une probabilité d’erreur de transmission de ,
si on prend , alors la probabilité d’avoir au moins 4 erreurs
est de 0.019 (arrondi par excès) :
ou directement
5.4.6 Distance
Exercice : écrire une procédure de calcul de la distance de Hamming de 2 mots. En Xcas, la fonction s’appelle hamdist.
La distance d’un code est la distance de Hamming minimale de 2 mots différents du code. Pour un code linéaire, la distance est aussi le nombre minimal de coefficients non nuls d’un vecteur non nul de l’image. Pour un code polynomial, la distance du code est le nombre minimal de coefficients non nuls d’un multiple de de degré inférieur à .
Majoration de la distance du code:
La distance minimale d’un code linéaire est inférieure ou égale à où est le nombre de symboles ajoutés.
Preuve : On écrit en ligne les coordonnées des images de la base canonique (ce qui revient à transposer la matrice) et on réduit par le pivot de Gauss, comme l’application linéaire est injective, le rang de la matrice est , donc la réduction de Gauss crée zéros dans chaque ligne, donc le nombre de coefficients non nuls de ces lignes (qui sont toujours des mots de code) est au plus de .
Remarque : la borne n’est pas toujours atteinte, par exemple un code de paramètres a une distance minimale d’au plus 3 (et non 4).
5.4.7 Correction au mot le plus proche
Une stratégie de correction basée sur la distance consiste à trouver le mot de code le plus proche d’un mot donné. Si la distance d’un code est supérieure ou égale à , et s’il existe un mot de code de distance inférieure à au mot donné, alors ce mot de code est unique. On corrige alors le mot transmis en le remplaçant par le mot de code le plus proche. Il n’est pas toujours possible de corriger un mot de code, sauf si la propriété suivante est vérifiée :
Pour et , la boule de rayon 1 de a pour cardinal (on est soit au centre, soit une des composantes est différente de celle du centre), on doit donc avoir : On a aussi donc . Les codes possibles sont donc:
- c’est le code de répétition
- : voir le code donné dans la section motivations 1.2.
- ...
- : on peut montrer que le code polynomial de polynôme est un code de Hamming binaire
- ...
Ces codes 1 correcteurs parfaits sur sont appelés codes de Hamming binaires. On les construit pour pouvoir corriger facilement une erreur avec la matrice de contrôle. Par exemple pour , il y a une seule matrice de controle de taille telle que donne la position de l’erreur (en base 2), elle est obtenue en écrivant les entiers de 1 à 15 en base 2 on déplace les colonnes de la matrice identité (colonnes 1, 2, 4, 8) en fin pour écrire , le code correspondant est , il permet de corriger une erreur, on calcule et si le résultat est non nul, on change le bit d’indice en tenant compte de la permutation d’indices sur les colonnes de .
5.5 Prolongement
On peut construire des corps finis GF(2,n)
ayant
éléments en prenant les classes de congruence de polynômes
à coefficients dans pour la division euclidienne
par un polynôme fixé de degré qui n’admet pas de diviseur
non trivial (comme pour les entiers premiers). Pour certains
polynômes
bien choisis, on peut fixer la distance de Hamming du code
correspondant à pour quelconque et donc corriger
jusqu’à erreurs.
A Programmes
A.1 Triangle de Pascal pour calculer les coefficients binomiaux
# on calcule une ligne en fonction de la precedente def Pascal(n): # local tableau,ligne,prec,j,k; tableau=[[1]] for j in range(1,n+1): ligne=[1] prec=tableau[j-1] for k in range(1,j): ligne.append(prec[k-1]+prec[k]) ligne.append(1) tableau.append(ligne); return tableau
onload
A.2 PGCD itératif avec reste symétrique
def pgcd(a,b): while b!=0: a,b=b,abs(mods(a,b)) return abs(a) # abs est necessaire si b==0 au debut
onload
En Python, il faut aussi définir la fonction mods
:
def mods(a,b): if b<0: b = -b a %= b if 2*a>b: return a-b return a