Cryptographie Élements de correction TP1 d’informatique deuxième année Vu que vous avez tous réussi les questions, les principaux enseignements que vous trouverez dans ce document sont une façon propre de faire le chiffrement de César et des informations sur les différentes façons de parcourir et de construire des séquences (listes, chaı̂nes, ...). Le fait qu’une boucle for puisse parcourir toute sorte de séquence ordonnée (les types itérables), et la construction d’une liste par compréhension, qui est plus rapide à écrire, quand on comprend bien ce qu’elle fait. Lisez bien la partie sur la fonction sans combi, et surtout la différence entre liste.append(element) et liste=liste+[element] ! Faites VRAIMENT l’expérience de comparer les deux versions sur un texte un peu long comme votre oeuvre complète ! Préliminaire : pour le faire soi-même on utilise une boucle for. En python cette boucle peut itérer sur tout ce qui représente une séquence : un range, une liste, une chaı̂ne... On a donc ici deux possibilités naturelles : itérer sur le range des indices, ou sur la liste elle-même. def rassembler(liste): s=’’ for i in range(len(liste)): #iteration sur les indices s=s+liste[i] return s def rassembler(liste): s=’’ for chaine in liste: #iteration sur la liste s=s+chaine return s Sinon, voir help(str.join), et essayer s="xxx".join(["nous","sommes","réunies"]), puis s="".join(["nous","sommes","réunies"]). Unicode 10F F F F 16 = 1 × 165 + 0 × 164 + 15 × 163 + 15 × 162 + 15 × 161 + 15 × 160 . Pour donner à python un nombre représenté en base 2 (binaire), 8 (octale), ou 16 (héxadécimale), on le préfixe d’un zéro et de la lettre b, o, ou x. Donc python donne la réponse à la première question si on lui demande de calculer 0x10FFFF+1. Un peu plus d’un million de codepoints : de quoi encoder l’ensemble des caractères de toutes les langues, chinois compris. Fabriquer la liste des majuscules ["A","B",...,"Z"]. Réponse : Ce sont les codepoints 65 à 90, donc : alpha=[] for i in range(65,91): alpha.append(chr(i)) 1 ou encore, en définissant cette liste par compréhension : alpha=[chr(i) for i in range(65,91)] Écrire une fonction qui prend un texte en argument et retourne le même texte où tous les caractères de combinaison ont disparu : On ne peut pas modifier une chaı̂ne de caractères, c’est pourquoi on doit construire une nouvelle chaı̂ne. On décompose notre texte pour séparer les lettres de leurs accents, et on copie tous ses caractères qui ne sont pas des caractères de combinaison. On utilise une boucle for, et on peut soit itérer sur la chaı̂ne de caractères elle-même, soit itérer sur un ensemble d’indices (entiers). Voir les deux versions ci-dessous : def sans_combi(texte): s=unicodedata.normalize("NFD",texte) nouveau_texte="" for caractere in s: # iteration sur la chaine if unicodedata.combining(caractere)==0: nouveau_texte=nouveau_texte+caractere return nouveau_texte def sans_combi(texte): s=unicodedata.normalize("NFD",texte) nouveau_texte="" for i in range(len(s)): #iteration sur les indices if unicodedata.combining(s[i])==0: nouveau_texte=nouveau_texte+s[i] return nouveau_texte Certains ont préféré construire une liste au lieu de construire une chaı̂ne de caractères, puis à la fin rassembler la liste en une seule chaı̂ne. Mais attention ! Pour construire une liste élément par élément on utilise liste.append(element) qui modifie l’objet liste, et surtout pas liste=liste+[element], qui crée une toute nouvelle liste puis la remplit avec le contenu de l’ancienne et le nouvel élément ! def sans_combi(texte): s=unicodedata.normalize("NFD",texte) nouvelle_liste=[] for i in range(len(s)): if unicodedata.combining(s[i])==0: nouvelle_liste.append(s[i]) # remplacez la ligne ci-dessus par : nouvelle_liste=nouvelle_liste+[s[i]] # et comparez le temps d’execution sur une chaine de 10^5 ou 10^6 caracteres. return rassembler(nouvelle_liste) On peut aussi construire cette liste “par compréhension” : def sans_combi(texte): s=unicodedata.normalize("NFD",texte) nouvelle_liste=[c for c in s if unicodedata.combining(c)==0] return rassembler(nouvelle_liste) Toutes ces solutions sont correctes et se valent à peu près du point de vue du temps de calcul. Fonction clean : 2 def clean(texte): return sans_combi( texte.upper() ) # ou bien : return sans_combi(texte).upper() Chiffrement de César Il en existe 25 versions si on ne compte pas la fonction identité comme un chiffrement. Écrire une fonction cesar caractere(c,k) qui retourne le caractère c décalé de k rangs dans l’alphabet si c’est une lettre majuscule, et retourne c inchangé sinon : Pour tester si on a affaire à une lettre majuscule, il suffit de vérifier si son codepoint est entre 65 et 90. Ce test est plus rapide que de parcourir la liste des lettres majuscules pour vérifier si c s’y trouve. Concernant le décalage de k rangs, vous avez presque tous pu vous rendre compte qu’une solution pas assez rigoureuse à ce genre de question mène à une longue séance de débuggage. Donc procédons méthodiquement : prenons notre lettre, et considérons son codepoint. Changeons-le en un nombre entre 0 et 25 en retranchant 65. Ajoutons k et ramenons-nous dans l’intervalle [[0, 25]] à l’aide de l’opérateur modulo : %. Ajoutons 65 pour obtenir le codepoint de la lettre correspondante, et appliquons chr pour avoir cette lettre : def cesar_caractere(c,k): codepoint=ord(c) if codepoint < 65 or codepoint > 90: return c else : numero=codepoint-65 numero_decale=(numero+65)%26 codepoint_decale=65+numero_decale return chr(codepoint_decale) Cette fonction fonctionne avec n’importe quel k ∈ Z. Écrire une fonction cesar(s,k) qui chiffre un texte avec le code de César avec le décalage fourni. Comme pour retirer les caractères de combinaison, on avait le choix entre plein de variantes très proches les unes des autres. On pouvait itérer sur la chaı̂ne, ou sur ses indices, ou sur la liste de ses caractères, et on pouvait construire directement une chaı̂ne ou contruire une liste pour la rassembler ensuite. Par exemple, en itérant sur la chaı̂ne et en construisant directement une chaı̂ne : def cesar(texte,k): resultat="" for c in texte: resultat=resultat+cesar_caractere(c,k) return resultat ou bien en construisant une liste par compréhension pour ensuite la rassembler : def cesar(texte,k): liste_resultat=[cesar_caractere(c,k) for c in texte] return rassembler(liste_resultat) Pour décoder un texte codé par un décalage de k, on effectue un décalage de −k. Pour déchiffrer le texte “V’XLM NG IXN VHNKM, CXNGX AHFFX”, on pouvait essayer les 26 décalages possibles et trouver celui qui retournait un texte lisible. 3 Analyse fréquentielle Échantillon de texte : attention à le sauver sous la forme d’un fichier texte “brut”, pas un document word ou autre. Pour cela, utiliser le logiciel “bloc-notes”, et si il n’est pas dans votre menu Démarrer, alors dans le menu Démarrer, cliquer sur “Éxécuter...” et taper “notepad”. Pour lire son contenu depuis python, ne pas oublier l’extension .txt du fichier, et si le chemin contient des backslash, mieux vaut les doubler. Python interprétera toujours un double backslash comme le caractère ‘\’. En revanche, un simple backslash suivi de certaines lettres comme ‘a’, ‘t’, ‘r’ ou ‘n’ sera compris comme un caractère spécial (tabulation, saut de ligne...) f=open("chemin_vers_le_fichier.txt") texte=f.read() Compter le nombre d’occurrences de chaque lettre dans l’échantillon, et en déduire la fréquence de chaque lettre : occurrences=[0]*26 for c in texte : codepoint=ord(c) if codepoint>=65 and codepoint <=90 : occurrences[codepoint-65]=occurrences[codepoint-65]+1 total=sum(occurrences) # merci la fonction ’sum’ de python frequences=[] for n in occurrences: frequences.append(n/total) Histogramme des fréquences : les abscisses des bâtons seront 1, 2, 3, ..., 26 : pour les obtenir, on convertit le range(1,27) en une liste. Leurs hauteurs seront les fréquences. Leurs noms seront les lettres de l’alphabet, dont on a fabriqué la liste alpha précédemment. import matplotlib.pyplot as plt abscisses=list(range(1,27)) plt.bar(abscisses, frequences) plt.xticks(abscisses, alpha) plt.show() Codage par substitution Pas de corrigé sur cette partie. 4