<jupyter_start><jupyter_text>Cryptographie - principes de base  0- Principes Le but de la cryptographie est de cacher un texte donné de tel manière que seul son destinataire légitime puisse le lire.Les manières de cacher ce textes sont multiples. On s'intéresse ici à une transformation du texte pour le rendre illisible. En pratique pour nous, le texte à cacher est une chaîne de caractères s. On cherche donc à calculer une chaîne de caractère tirée de s de telle manière que notre destinataire (avec qui on peut éventuellement partager un secret, la clé), et avec un peu de chance seul lui, puisse déchiffrer ce texte.  Cadre On se limite à des textes contenant uniquement des caractères en minuscule et des espaces, pour simplifier les raisonnements. Dans le texte de référence utilisé, tous les autres caractères seront remplacés par des espaces.  Texte à cacher On fixe pour tout le cours un texte à crypter : les histoires extraordinaires d'<NAME>, traduites par <NAME> et disponible librement sur le site du projet Gutenberg ici : http://www.gutenberg.org/ebooks/20761Le fichier texte, légèrement nettoyé (majuscules enlevé, préface enlevée) est dans le dossier contenant ce notebook et peut donc être ouvert directement par Python :<jupyter_code>with open('HistoiresExtraordinaires.txt') as f:
    texte = f.read()
    texte = texte.replace('\r', '')
print(texte[:2500]) #on regarde si le texte a bien été importé<jupyter_output><NAME>

histoires extraordinaires

traduction par charles baudelaire premiere publication en france en 1856

double assassinat dans la rue morgue

quelle chanson chantaient les sirenes? quel nom achille avait-il pris,
quand il se cachait parmi les femmes?-questions embarrassantes, il est
vrai, mais qui ne sont pas situees au dela de toute conjecture.

sir <NAME>.


les facultes de l'esprit qu'on definit par le terme _analytiques_ sont
en elles-memes fort peu susceptibles d'analyse. nous ne les apprecions
que par leurs resultats. ce que nous en savons, entre autres choses,
c'est qu'elles sont pour celui qui les possede a un degre extraordinaire
une source de jouissances des plus vives. de meme que l'homme fort se
rejouit dans son aptitude physique, se complaÃ®t dans les exercices qui
provoquent les muscles a l'action, de meme l'analyse prend sa gloire
dans cette activite spirituelle dont la fonction est de debrouiller. il
tire du plaisir meme des plus triviales occasions[...]<jupyter_text>1- Chiffrement de César  1.1 - Description Le principe est assez simple :on donne un numéro à chaque lettre de l'alphabetla clé de chiffrage est un nombre entre 1 et le nombre de lettre - 1la chaîne cryptée est obtenue en décalant tous les caractères de clé dans l'alphabet, étant entendu que l'on revient au début à la fin de la liste...<jupyter_code>LETTRES = 'abcdefghijklmnopqrstuvwxyz '

def indice(lettre):
    """
    Retourne l'indice de la lettre donnée dans la chaîne LETTRES
    indice('a') retourne 0, indice('z') retourne 25
    indice(' ') retourne 26, indice(n'importe quoi d'autre) retourne 26
    """
    if 97<=ord(lettre)<=122:
        return ord(lettre)-ord("a")
    else:
        return 26
def cesar(chaine, cle):
    #s est notre chaine codée qu'on construit au fur et à mesure
    s = ''
    n = len(LETTRES)
    for i in range(len(chaine)):
        #on décale la lettre chaine[i] de clé. On fait % n pour revenir à 0 si on dépasse 26
        s = s + LETTRES[(indice(chaine[i]) + cle) % n]
    return s
cesar(texte, 14)[:500]<jupyter_output><empty_output><jupyter_text>1.2 - Retrouver le texte d'origine Le principe pour notre correspondant est assez simple, il suffit de décaler dans l'autre sens.<jupyter_code>#on crypte
messageCode=cesar('hello world',3)
print(messageCode)
#on décrypte
messageDecode=cesar(messageCode,-3)
print(messageDecode)
#modulo 27, faire +14 ou -13 revient au même
cesar(cesar('hello world', 13), 14)
#bien sûr, avec notre texte qui contient des caractères autres que des minuscules
#on perd un peu d'information qui se transforme en espace
cesar(cesar(texte, 4), -4)[:500]<jupyter_output><empty_output><jupyter_text>1.3 - Cryptanalyse Il est très facile de retrouver la clé secrète du chiffrement de césar si on connait l'alphabet utilisé.Ainsi un espion, le gouvernement, ou toute autre personne satisfaisant votre sens de la théorie du complot, peut retrouver le message en clair. La méthode est simple : force brute. On essaie toutes les clés possible et on regarde à la main les messages obtenus. il y a tellement peu de clés possible que la cryptanalyse est quasiment immédiate.<jupyter_code>import random
#on choisit une clé aléatoire qu'on n'affiche pas pour la démonstration
cle_choisie = random.randint(1, len(LETTRES) - 1)

message = 'ceci est un message un tout petit peu plus long'
crypte = cesar(message, cle_choisie)

for i in range(1, len(LETTRES)):
    print('clé : ', i, ' message décodé : ' + cesar(crypte, -i))<jupyter_output>clé :  1  message décodé :  b fxbpqxrkxjbppydbxrkxqlrqxmbqfqxmbrxmirpxilkd
clé :  2  message décodé : zazewaopwqjwiaooxcawqjwpkqpwlapepwlaqwlhqowhkjc
clé :  3  message décodé : y ydv novpivh nnwb vpivojpovk odovk pvkgpnvgjib
clé :  4  message décodé : xzxcuzmnuohugzmmvazuohunionujzncnujzoujfomufiha
clé :  5  message décodé : wywbtylmtngtfyllu ytngtmhnmtiymbmtiyntienltehg 
clé :  6  message décodé : vxvasxklsmfsexkktzxsmfslgmlshxlalshxmshdmksdgfz
clé :  7  message décodé : uwu rwjkrlerdwjjsywrlerkflkrgwk krgwlrgcljrcfey
clé :  8  message décodé : tvtzqvijqkdqcviirxvqkdqjekjqfvjzjqfvkqfbkiqbedx
clé :  9  message décodé : susypuhipjcpbuhhqwupjcpidjipeuiyipeujpeajhpadcw
clé :  10  message décodé : rtrxotghoiboatggpvtoibohcihodthxhodtiod igo cbv
clé :  11  message décodé : qsqwnsfgnhan sffousnhangbhgncsgwgncshnczhfnzbau
clé :  12  message décodé : prpvmrefmg mzreentrmg mfagfmbrfvfmbrgmbygemya t
clé :  13  message décodé : oqoulqdelfzlyqddmsqlfzle felaqeuelaqflaxfdlx zs
clé :  14  message dé[...]<jupyter_text>Pas trop difficile de deviner ce qu'avait choisi python comme clé...  2 - Chiffrement par permutation  2.1 - Principe Au lieu de bêtement décaler chaque lettre, on choisit une bijection de l'alphabet dans lui même, c'est à dire que chaque lettre est transformée en une autre. En pratique une bijection est représentée par une chaîne de caractère ou une liste possédant les même lettres que LETTRES mais dans le désordre<jupyter_code>def permute(chaine, bijection):
    s = ''
    n = len(LETTRES)
    for c in chaine:
        s = s + bijection[indice(c)]
    return s
#ou on peut faire une permutation très simple en inversant simplement les "o" et les "e"
bij1='abcdofghijklmnepqrstuvwxyz '
print(permute('hello world', bij1))
#ou une permutation plus complexe car complètement aléatoire
bij = list(LETTRES)
random.shuffle(bij)
print(permute('hello world', bij))<jupyter_output>holle werld
uciiwanwxiv<jupyter_text>2.2 - Déchiffrement Comment retrouver le message d'origine connaissant le message chiffré et la permutation ? Facile, il suffit de calculer la réciproque, c'est à dire trouver la position du caractère courant dans bijection.<jupyter_code>def decode_permute(chiffre, bijection):
    s = ''
    n = len(LETTRES)
    for c in chiffre:
        s = s + LETTRES[bijection.index(c)]
    return s
#on crypte
messageCode=permute('hello world', bij)
print(messageCode)
#on décrypte
messageDecode=decode_permute(messageCode,bij)
print(messageDecode)<jupyter_output>uciiwanwxiv
hello world<jupyter_text>Cette fois le nombre de clé est plutôt vaste : le "a" peut se transformer en 26 lettres différentes, le "b" en 25 (puisque ça ne peut pas être la même que "a"), le "c" en 24, etc. Cela fait donc $26 \times 25 \times 24 \times ... \times 2 \times 1 = 26! \approx 3 \times 10^{29}$ possibilités de clés. Si on pouvait lire 1 000 000 de textes à la seconde (pour vérifier si c'est le bon), il faudrait quand même un million de milliard d'année pour arriver au bout...On ne peut donc pas attaquer ce genre de code par de la force brute.  2.3 - Cryptanalyse Idée : la probabilité d'apparition des lettres dans un texte en français n'est pas du tout uniforme. On doit pouvoir identifier les voyelles et les consones courantes assez rapidement. On peut aussi utiliser le fait que certaines lettres sont souvent doublées en français, comme le l ou le t ou le p ou encore que de nombreux mots se terminent par s.<jupyter_code>def frequence(chaine):
    """
    Calcule le nombre d'occurences de chaque lettre dans la chaine donnée.
    """
    #on crée un liste de compteurs initialisés à 0
    occ = [ 0 for c in LETTRES]
    
    #on incrémente le compteur de la lettre rencontrée dans la chaine
    for c in chaine:
        i = indice(c)
        occ[i] = occ[i] + 1
    
    #on modifie les compteurs en transformant les effectifs en fréquences
    total = max(len(chaine), 1)
    for i in range(len(LETTRES)):
        occ[i] = 100 * occ[i] / total
    
    #on affiche les résultats triés par ordre de fréquence en affichant la fréquence et la lettre
    resultat = [(occ[i], LETTRES[i]) for i in range(len(LETTRES))]
    resultat.sort()
    return resultat
#on suppose que le texte est codée par une bijection secrète IL FAUT FAIRE COMME SI ON NE CONNAISSAIT PAS CETTE BIJECTION!!!
bij=['u', 'm', 'k', 'i', 'q', 'y', 'l', 'c', 'p', 'a', 'j', ' ', 't', 'n', 'x', 'f', 'r', 'g', 'v', 'o', 'e', 's', 'h', 'w', 'z', 'd', 'b']
messageCode = permute(texte, bij)
frequence(messageCode)<jupyter_output><empty_output><jupyter_text>Il suffit maintenant d'aller sur [wikipedia](https://fr.wikipedia.org/wiki/Fr%C3%A9quence_d%27apparition_des_lettres_en_fran%C3%A7aisFr%C3%A9quence_des_caract%C3%A8res_dans_le_corpus_de_Wikip%C3%A9dia_en_fran%C3%A7ais) ou tout autre site pour trouver une table référence des fréquence de lettres dans les textes en français. On peut deviner plusieurs choses :l'espace correspond au 23,56%le e doit correspondre à la lettre la plus fréquentePour trouver des chiffres plus proches des tables données, il faut considérer des textes sans espaces. Allons-y<jupyter_code>messageCode2 = messageCode.replace('b', '')
frequence(messageCode2)<jupyter_output><empty_output><jupyter_text>Les chiffres obtenus sont proches de ceux de la table de référence. Essayons de décrypter.On va créer une fonction qui permute deux lettres données :<jupyter_code>def echange(chaine, c1, c2):
    s = ''
    for c in chaine:
        #si on rencontre c1, on remplace par c2
        if c == c1:
            s += c2
        #si on rencontre c2, on remplace par c2
        elif c == c2:
            s += c1
        #sinon on garde le même caractère
        else:
            s += c
    return s
c2 = echange(messageCode, 'b', ' ');
c2 = echange(c2, 'q', 'e')
c2 = echange(c2, 'v', 'a')
c2 = echange(c2, 'u', 'i')
c2 = echange(c2, 'o', 's')
c2 = echange(c2, 'n', 'n')
c2 = echange(c2, 'p', 'r')
c2 = echange(c2, 'g', 't')

c2[:5000]<jupyter_output><empty_output><jupyter_text>Beaucoup de mots se terminent par a, ce sont sûrement des s. On voit aussi le 2eme mot avec un double b, on parie sur un double l à la place :<jupyter_code>c2 = echange(c2, 'a', 's')
c2 = echange(c2, 'b', 'l')

c2[0:5000]<jupyter_output><empty_output><jupyter_text>Prochaine étape, utiliser les espaces de manière plus systématiques, pour réutiliser l'idée précédente. Par exemple, trouver les mots de longueur 1 et 2.<jupyter_code>mots = c2.split()

long_1 = [m for m in mots if len(m)== 1]
mots_1 = set() #ensemble : ne peut pas contenir deux fois le même élément
for m in long_1:
    mots_1.add((m, long_1.count(m)))
print(mots_1)

long_2 = [m for m in mots if len(m) == 2]
mots_2 = set() #ensemble : ne peut pas contenir deux fois le même élément
for m in long_2:
    mots_2.add((m, long_2.count(m)))
print(mots_2)<jupyter_output>{('d', 5), ('z', 366), ('g', 1076), ('u', 2765), ('a', 972), ('v', 625), ('y', 123), ('o', 233), ('k', 538), ('f', 241), ('h', 3), ('s', 1602), ('m', 118), ('e', 1024), ('l', 2566), ('q', 19), ('c', 88), ('n', 1020), ('x', 178), ('i', 618), ('p', 3), ('r', 8), ('w', 3), ('j', 1), ('b', 167), ('t', 804)}
{('ve', 1768), ('ag', 57), ('bt', 29), ('fi', 2), ('ac', 36), ('fc', 26), ('xq', 314), ('rn', 79), ('bq', 23), ('fb', 1), ('ne', 835), ('ro', 1), ('ee', 1), ('iq', 631), ('xk', 29), ('xt', 142), ('as', 9), ('he', 9), ('xy', 118), ('iu', 2), ('im', 17), ('oe', 29), ('gz', 1), ('ry', 21), ('na', 4), ('yl', 27), ('sq', 9), ('xg', 3), ('en', 1247), ('fr', 25), ('ig', 42), ('ux', 17), ('ru', 120), ('yi', 29), ('fw', 2), ('cz', 2), ('ec', 15), ('ni', 3), ('sk', 9), ('li', 3165), ('ai', 11), ('qe', 4), ('ic', 17), ('it', 2), ('mr', 1), ('lr', 2), ('is', 20), ('cr', 4), ('nr', 80), ('rs', 28), ('ia', 13), ('rr', 1), ('sa', 5), ('sf', 28), ('gq', 2), ('mx', 8), ('ke', 772), ('ts', 4), ('xm', 5),[...]<jupyter_text>Le but est de trouver a, l, d qui sont très commun en mots d'une lettre comme en mots de deux ("l'", "d'", "la", "le"...)On observe que le mot "ue" apparait 5249 fois, comme on est assez sûr du e et sans doute du l, on peut parier que le u se transforme en d.De même, on on observe que "le" et "li" apparaissent environ 3000 fois, donc on parie que ce sont les mots le et la. On va donc inverser le i et le a.<jupyter_code>c3 = echange(c2, 'u', 'd')
c3 = echange(c2, 'i', 'a')
c3[0:5000]<jupyter_output><empty_output><jupyter_text>On voit "elles sxni" qui ressemblent à "elles sont".On va tenter les permutations xo et it.<jupyter_code>c3 = echange(c3, 'x', 'o')
c3 = echange(c3, "i", "t")
c3[:5000]
c3 = echange(c3, 'z', 'y')
c3 = echange(c3, "i", "r")
c3[:5000]
c3 = echange(c3, 'c', 'h')
c3 = echange(c3, "x", "w")
c3 = echange(c3, "u", "d")
c3[:5000]<jupyter_output><empty_output><jupyter_text>et ainsi de suite... La décodage allant, on commence à comprendre le sens et pouvoir déchiffrer plus facilement.Pour faciliter la lecture, il faudrait afficher différemment les lettres déjà trouvées. On y est presque. Le reste est à finir en exercice.  3- Vigénère  3.1 - Principe La principale faiblesse du codage par substitution est que tous les "e" deviennnent une seule et même lettre dans le message codé et qu'une analyse statistique permet d'identifier par quoi elle a été substituée.Le code de Vigénère apporte une soution à cette faiblesse car il introduit l'idée d'une substitution polyalphabétique, c'est à dire qu'une lettre donnée ne sera pas toujours transformée en la même lettre.Au lieu d'utiliser un décalage uniforme sur tout le texte comme dans le code de César, on choisit un mot clé qui sert à indiquer de combien on se décale. On transforme le mot clé en liste de chiffres, qui seront nos décalages (a->+0, b->+1, c->+2,...), par exemple, si la clé est bac, alors la liste des décalages est de (1,0,2) (a->+0, b->+1, c->+2,...)la première lettre du texte est décalée grâce au premier chiffre de la cléla deuxième lettre du texte est décalée grâce au deuxième chiffre de la cléon poursuit ainsi, en reprennant la clé depuis le début quand on l'a épuiséeLa vidéo suivante, à regarder, explique le cryptage par substitution et de Vigénère : https://youtu.be/PIw_nuWsOFU<jupyter_code>def vigenere(texte, mot_cle):
    liste_cle = [indice(c) for c in mot_cle]
    s = ''
    long_cle = len(liste_cle)
    long_alph = len(LETTRES)
    for i in range(len(texte)):
        s = s + LETTRES[(indice(texte[i]) + liste_cle[i % long_cle]) % long_alph]
    return s
vigenere('ceci est un test', 'motcle')
def dechiffre_vigenere(texte, mot_cle):
    liste_cle = [-indice(c) for c in mot_cle]
    s = ''
    long_cle = len(liste_cle)
    long_alph = len(LETTRES)
    for i in range(len(texte)):
        s = s + LETTRES[(indice(texte[i]) + liste_cle[i % long_cle]) % long_alph]
    return s
dechiffre_vigenere('osvkkidgswydeskv', 'motcle')<jupyter_output><empty_output><jupyter_text>3.2 - Cryptanalyse Elle est beaucoup plus délicate que la précédente. L'idée est de trouver d'abord la longueur de la clé en procédant à des mesures d'incidences. Ensuite, on peut procéder à une analyse de fréquence pour déterminer chaque décalage.La page Bibmath consacrée à la cryptanalyse de Vigénère détaille assez clairement ce procédé : http://www.bibmath.net/crypto/index.php?action=affiche&quoi=poly/viganalyseOn va regarder un exemple pour montrer le procédé :<jupyter_code>#on code notre texte avec une clé de 5 caractères. ON SUPPOSE QU'ON NE CONNAIT PAS CETTE CLÉ
cle= "alien"
messageCode=vigenere(texte, cle)
messageCode=messageCode[:5000] #on se restreint à 5000 caractères pour ne pas surcharger le processeur 
print(messageCode)<jupyter_output>eooed ltpnnkxsr kpmetzqvrskmafrlwvqiyimdechdfrllypttwrmplzdphlzprskjegdptevrphtdexqddekxyoltkefizvdrnknvnnnmdrnkhdm khhaumtimac eestvef oire widduphqarrbim abiylphguay s  npe tlqi tktie cqvmnp dmqempmnzudncsqpyekiznidhmy  zme kyynnohmy cmdpanpevtkxedmthprsknizmp dmqemwfizvwmexjedrl wnndmwm ttdrsdhzdathdzat dcuthrr cwrf  iwmstaymecheg ompm omdfoeaimczvnrcdbvr khwvrkalaml dorzdrr khdyechjncetxmsklimlkmwbrtadcukwrmdknm idhtnrktimtpzqr kirnliamcup dmszvxmeyhiylp dz xmwmfzzxmppbdeuckibttjprskldnnltbeekhrauchrr wmwma xvmctwre abimplzdyeezwmrk yytlawm nmdcuphrauchi  cizanchdrndzimaeavrskklasp dmckmwf abdrlwmwmszvxmpzbvmcptyv abmmlp dboc dqekhdgnklitrkhijtbisddtvevrphy ek sgrnmdqekrsgic e cp dqechtyuchzvvp dmdphqmmphugektduoxuimfzzxmsphvmjzbmf oire cwrma amfuomdbhi mcuphdeekkszpwidf oire wmwmehmvpinmwmqeqdbrzcscupvxmlp dzuckprskhdy lkxvoyhdqekudzektdnnltbeekxvrnohwn rtsvrphhnnchgrtdmdncdqzvtkhwbibqxgewtimdzvxmllhjannamankmwf omdq mzsgiwtid kqpmttzimdehtyat md xhqr omwmpwbwmtbqzvawmwmonkeeizvwm[...]<jupyter_text>On commence par lister tous les trigrammes existant dans ce texte et on les met dans un dicionnaire. La valeur associée dans le dictionnaire correspond à l'index de sa position dans le texte.<jupyter_code>#on crée le dictionnaire vide
dictTrigrammes={}

#on parcourt le message codé au complet jusqu'à l'avant dernier caractère
for i in range(len(messageCode)-2):
    if messageCode[i:i+3] not in dictTrigrammes: #si le trigramme n'a jamais été rencontré
        dictTrigrammes[messageCode[i:i+3]]=[i]   #on crée une liste des positions de ce trigramme initialisée à [i]
    else:
        dictTrigrammes[messageCode[i:i+3]].append(i) #si le trigramme a déjà été rencontré, on ajoute sa nouvelle position
print(dictTrigrammes)<jupyter_output>{'eoo': [0], 'ooe': [1], 'oed': [2], 'ed ': [3, 1623, 2238, 3493], 'd l': [4], ' lt': [5], 'ltp': [6], 'tpn': [7], 'pnn': [8], 'nnk': [9], 'nkx': [10], 'kxs': [11, 3201], 'xsr': [12], 'sr ': [13], 'r k': [14, 359, 379, 434, 1589, 1604, 1759, 3299, 3369, 3514, 3649, 4424], ' kp': [15], 'kpm': [16], 'pme': [17, 4373, 4393], 'met': [18], 'etz': [19], 'tzq': [20], 'zqv': [21], 'qvr': [22, 3647], 'vrs': [23, 583, 2358], 'rsk': [24, 64, 254, 489, 584, 829, 1044, 1409, 1564, 1984, 2004, 2314, 2359, 2864, 3219, 3259, 3374, 3484], 'skm': [25], 'kma': [26], 'maf': [27, 3094], 'afr': [28], 'frl': [29, 44], 'rlw': [30, 604, 1629, 1834, 4484], 'lwv': [31], 'wvq': [32, 4702], 'vqi': [33, 4703], 'qiy': [34, 3826, 4704], 'iyi': [35, 2985, 4705], 'yim': [36, 4706], 'imd': [37, 908, 953, 1177, 2288, 2608, 2882, 2913, 3308, 3768, 4537, 4707], 'mde': [38, 954, 1178, 1277, 2883, 3294, 3778, 3899, 4538, 4708], 'dec': [39, 3579], 'ech': [40, 330, 385, 695, 1110, 1215, 2230, 2380, 2715, 2870, 3285, 3460, 4630[...]<jupyter_text>On veut connaitre l'écart qu'il y a entre les trigrammes identiques. On crée donc le dictionnaire dictTrigrammesEcart qui recense l'écart entre chacun des trigrammes.<jupyter_code>dictTrigrammesEcart=dict()
for (key,value) in dictTrigrammes.items():
    dictTrigrammesEcart[key]=[value[i+1]-value[i] for i in range(len(value)-1)]
print(dictTrigrammesEcart)<jupyter_output>{'eoo': [], 'ooe': [], 'oed': [], 'ed ': [1620, 615, 1255], 'd l': [], ' lt': [], 'ltp': [], 'tpn': [], 'pnn': [], 'nnk': [], 'nkx': [], 'kxs': [3190], 'xsr': [], 'sr ': [], 'r k': [345, 20, 55, 1155, 15, 155, 1540, 70, 145, 135, 775], ' kp': [], 'kpm': [], 'pme': [4356, 20], 'met': [], 'etz': [], 'tzq': [], 'zqv': [], 'qvr': [3625], 'vrs': [560, 1775], 'rsk': [40, 190, 235, 95, 245, 215, 365, 155, 420, 20, 310, 45, 505, 355, 40, 115, 110], 'skm': [], 'kma': [], 'maf': [3067], 'afr': [], 'frl': [15], 'rlw': [574, 1025, 205, 2650], 'lwv': [], 'wvq': [4670], 'vqi': [4670], 'qiy': [3792, 878], 'iyi': [2950, 1720], 'yim': [4670], 'imd': [871, 45, 224, 1111, 320, 274, 31, 395, 460, 769, 170], 'mde': [916, 224, 99, 1606, 411, 484, 121, 639, 170], 'dec': [3540], 'ech': [290, 55, 310, 415, 105, 1015, 150, 335, 155, 415, 175, 1170, 50, 230], 'chd': [530, 655, 650, 995, 590, 1220], 'hdf': [3420, 745], 'dfr': [2700, 1465], 'rll': [], 'lly': [], 'lyp': [], 'ypt': [], 'ptt': [3830], 'ttw': [3830], [...]<jupyter_text>L'idée en regardant ce dictionnaire est de dire qu'un trigramme réapparait quand ce sont les trois même lettres qui ont été codées par les mêmes lettres de la clé. Cela veut donc dire que les écarts trouvés entre les trigrammes sont des multiples de la longueur de la clé.Ici par exemple, presque tous les écarts semblent être des multiples de 5, ce qui nous amène à poser l'hypothèse que la clé est de longueur 5. Tous les 5 caractères, le décalage est donc le même. Il faut donc trouver quel décalage a été fait sur les caractères en position 0,5,10,15... puis de trouver quel décalage a été fait sur les caractères en position 1,6,11,16..., puis ceux en position 2,7,12,17 et ainsi de suite.Pour cela on va créer une fonction sousTexte(texte,longueur,pos) (à faire en exercice)<jupyter_code>def sousTexte(texte,longueur,pos):
    #renvoit les caractères de texte situés aux positions pos, pos+longueur, pos+longueur*2, pos+longueur*3...
    #par exemple sousTexte("abcdefghijkl",2,0) renvoit "acegik" (on commence à 0 et on saute de 2 en 2)
    #par exemple sousTexte("abcdefghijkl",2,1) renvoit "bdfhjl" (on commence à 1 et on saute de 2 en 2)
    #par exemple sousTexte("abcdefghijkl",3,1) renvoit "behk"  (on commence à 1 et on saute de 3 en 3)<jupyter_output><empty_output><jupyter_text>On peut désormais analyser les fréquences des sous-textes. L'analyse des fréquences permettra de deviner le décalage.Par exemple, si mon sous-texte contient une très grande fréquence de "g", alors je peux supposer que la lettre "e" a été décalée en "g" et que le décalage est donc de +2. Le caractère de la clé à cette position est donc c (a->+0, b->+1, c->+2,...)<jupyter_code>sousTexte0=sousTexte(messageCode,5,0) #on récupère le sous-texte composé des caractères 0,5,10,15...
print(frequence(sousTexte))           #on observe quelle lettre est la plus fréquente, c'était sûrement un "e" dans le message en clair<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.12387894853103601, 'fraction_numerical': 0.05283852440910095, 'mean_word_length': 4.756866734486267, 'pattern_counts': {'":': 0, '<': 75, '<?xml version=': 0, '>': 82, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '19', 'dir': 'jupyter-structured-clean-dedup', 'id': '13121', 'license': 'MIT', 'n_tokens_mistral': 9673, 'n_tokens_neox': 8813, 'n_words': 3003, 'path': '.ipynb_checkpoints/cryptographie-principes-de-base-checkpoint.ipynb', 'repo_name': 'egouello/TermNSI', 'size': '22666'}