Autrefois réservée aux communications militaires et diplomatiques, la cryptographie fait aujourd'hui partie du quotidien de tout informaticien, et en fait de tout utilisateur d'un site de commerce en ligne, d'une carte bancaire, d'un téléphone portable ou même d'une télécommande de porte de garage, même si ceux-ci n'en sont pas forcément conscients.
On peut distinguer quelques grandes périodes dans l'histoire de la cryptographie. De l'antiquité à la fin du moyen-âge, on utilisait essentiellement des systèmes de transposition simple, ou chiffres monoalphabétiques : chaque lettre est remplacée par une autre, toujours la même (une permutation de l'alphabet, donc). Les plus anciens de ces systèmes sont la scytale (mentionnée par Plutarque) et le chiffre de César (mentionné par Suétone dans la vie des douze Cesars).
La cryptanalyse de ces systèmes n'est pas difficile et relève plus de la rubrique « jeux » des magazines que de la cryptologie moderne. La résolution de certains cryptogrammes peut malgré tout demander une bonne dose de patience et d'ingéniosité, et reste une bonne source d'exercices de programmation.
À titre d'exercice, on pourra chercher à reproduire à l'aide d'un ordinateur le decryptage exposé dans la nouvelle d'Edgar Poe le scarabée d'or.
À la renaissance, on voit apparaître de nouveaux systèmes beaucoup plus résistants, dont le plus célèbre est le chiffre de Vigenère. Considéré comme sûr pendant près de trois siècles, il a été craqué dans la seconde moitié du XIXème siècle.
Entre cette période et la seconde guerre mondiale, on voit apparaître de nombreux chiffres nouveaux, et parallèlement de nouvelles méthodes de cryptanalyse.
On peut considérer que la cryptographie « moderne » commence pendant la seconde guerre mondiale, avec la construction en Angleterre du premier ordinateur, destiné au décryptage des communications allemandes.
Dans les années 1970, le déploiement des réseaux informatiques oblige à repenser la cryptographie : si $N$ noeuds d'un réseau doivent pouvoir communiquer confidentiellement les uns avec les autres, il faut en principe $N(N-1)/2$ clefs (une pour chaque lien bidirectionnel), ce qui est impraticable, ou à la rigueur router les communications par un noeud central, mais comment assurer que les $N$ clefs puissent être distribuées sans qu'aucune ne soit interceptée ? La solution est fournie par la cryptographie à clef publique.
On trouvera ici les definitions des principaux termes utilisée en cryptographie.
Le principe est de décaler cycliquement la valeur de chaque lettre (de 3 dans l'exemple historique).
Le plus simple est de construire une table de traduction (str.maketrans
).
from string import ascii_uppercase as AA
from string import ascii_lowercase as aa
k = 3
BB = AA[k:]+AA[:k]
tt = str.maketrans(aa,BB)
print(AA)
print(aa)
print(BB)
print(tt) ### c'est un dictionaire
ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz DEFGHIJKLMNOPQRSTUVWXYZABC {97: 68, 98: 69, 99: 70, 100: 71, 101: 72, 102: 73, 103: 74, 104: 75, 105: 76, 106: 77, 107: 78, 108: 79, 109: 80, 110: 81, 111: 82, 112: 83, 113: 84, 114: 85, 115: 86, 116: 87, 117: 88, 118: 89, 119: 90, 120: 65, 121: 66, 122: 67}
text = 'le cheval de mon cousin ne mange du foin que le dimanche'
msg = text.translate(tt)
print (msg)
OH FKHYDO GH PRQ FRXVLQ QH PDQJH GX IRLQ TXH OH GLPDQFKH
Comme il n'y a que 25 clés possibles, il est évidemment possible de les essayer toutes. Toutefois, la méthode statistique nous sera utile dans des cas plus difficiles ...
En français, la lettre la plus fréquente est le "e" ...
# Cryptanalyse
stats = {c:msg.count(c) for c in set(msg)}
print (stats)
{'I': 1, 'D': 3, 'L': 3, 'F': 3, 'Y': 1, 'J': 1, 'R': 3, 'Q': 6, 'K': 2, ' ': 11, 'T': 1, 'G': 3, 'P': 3, 'O': 3, 'V': 1, 'X': 3, 'H': 8}
ll = list(stats.items()) # items() retourne un itérateur en python 3
ll.sort(key=lambda x:x[1],reverse=True)
print (ll)
[(' ', 11), ('H', 8), ('Q', 6), ('D', 3), ('L', 3), ('F', 3), ('R', 3), ('G', 3), ('P', 3), ('O', 3), ('X', 3), ('K', 2), ('I', 1), ('Y', 1), ('J', 1), ('T', 1), ('V', 1)]
# Si H chiffre le e ...
i = AA.index('H') - aa.index('e')
CC = AA[i:]+AA[:i]
uu = str.maketrans(CC,aa)
msg.translate(uu)
'le cheval de mon cousin ne mange du foin que le dimanche'
# Ca ne marche pas à tous les coups
# Contre-exemple tiré de "La disparition" de Georges Perec
perec = '''Anton Voyl n'arrivait pas à dormir. Il alluma. Son Jaz marquait minuit vingt. Il poussa un profond soupir,
s'assit dans son lit, s'appuyant sur son polochon. Il prit un roman, il l'ouvrit, il lut; mais il n'y saisissait qu'un
imbroglio confus, il butait à tout instant sur un mot dont il ignorait la signification.
Il abandonna son roman sur son lit. Il alla à son lavabo; il mouilla un gant qu'il passa sur son front, sur son cou.
Son pouls battait trop fort. Il avait chaud. Il ouvrit son vasistas, scruta la nuit. Il faisait doux.
Un bruit indistinct montait du faubourg. Un carillon, plus lourd qu'un glas, plus sourd qu'un tocsin,
plus profond qu'un bourdon, non loin, sonna trois coups. Du canal Saint-Martin, un clapotis plaintif signalait
un chaland qui passait.
Sur l'abattant du vasistas, un animal au thorax indigo, à l'aiguillon safran, ni un cafard, ni un charançon,
mais plutôt un artison, s'avançait, traînant un brin d'alfa. Il s'approcha, voulant l'aplatir d'un coup vif, mais
l'animal prit son vol, disparaissant dans la nuit avant qu'il ait pu l'assaillir.'''
# En Python 3, les chaînes sont en unicode,
# on peut donc se débarasser des lettres accentuées sans avoir à bricoler les encodages
xx = 'àâäéèêëïîôöùûüÿç'
yy = 'aaaeeeeiioouuuyc'
uu = str.maketrans(xx,yy)
text = perec.translate(uu).lower()
print (text)
anton voyl n'arrivait pas a dormir. il alluma. son jaz marquait minuit vingt. il poussa un profond soupir, s'assit dans son lit, s'appuyant sur son polochon. il prit un roman, il l'ouvrit, il lut; mais il n'y saisissait qu'un imbroglio confus, il butait a tout instant sur un mot dont il ignorait la signification. il abandonna son roman sur son lit. il alla a son lavabo; il mouilla un gant qu'il passa sur son front, sur son cou. son pouls battait trop fort. il avait chaud. il ouvrit son vasistas, scruta la nuit. il faisait doux. un bruit indistinct montait du faubourg. un carillon, plus lourd qu'un glas, plus sourd qu'un tocsin, plus profond qu'un bourdon, non loin, sonna trois coups. du canal saint-martin, un clapotis plaintif signalait un chaland qui passait. sur l'abattant du vasistas, un animal au thorax indigo, a l'aiguillon safran, ni un cafard, ni un charancon, mais plutot un artison, s'avancait, trainant un brin d'alfa. il s'approcha, voulant l'aplatir d'un coup vif, mais l'animal prit son vol, disparaissant dans la nuit avant qu'il ait pu l'assaillir.
# Maintenant, chiffrons le :
msg = text.translate(tt)
print (msg)
DQWRQ YRBO Q'DUULYDLW SDV D GRUPLU. LO DOOXPD. VRQ MDC PDUTXDLW PLQXLW YLQJW. LO SRXVVD XQ SURIRQG VRXSLU, V'DVVLW GDQV VRQ OLW, V'DSSXBDQW VXU VRQ SRORFKRQ. LO SULW XQ URPDQ, LO O'RXYULW, LO OXW; PDLV LO Q'B VDLVLVVDLW TX'XQ LPEURJOLR FRQIXV, LO EXWDLW D WRXW LQVWDQW VXU XQ PRW GRQW LO LJQRUDLW OD VLJQLILFDWLRQ. LO DEDQGRQQD VRQ URPDQ VXU VRQ OLW. LO DOOD D VRQ ODYDER; LO PRXLOOD XQ JDQW TX'LO SDVVD VXU VRQ IURQW, VXU VRQ FRX. VRQ SRXOV EDWWDLW WURS IRUW. LO DYDLW FKDXG. LO RXYULW VRQ YDVLVWDV, VFUXWD OD QXLW. LO IDLVDLW GRXA. XQ EUXLW LQGLVWLQFW PRQWDLW GX IDXERXUJ. XQ FDULOORQ, SOXV ORXUG TX'XQ JODV, SOXV VRXUG TX'XQ WRFVLQ, SOXV SURIRQG TX'XQ ERXUGRQ, QRQ ORLQ, VRQQD WURLV FRXSV. GX FDQDO VDLQW-PDUWLQ, XQ FODSRWLV SODLQWLI VLJQDODLW XQ FKDODQG TXL SDVVDLW. VXU O'DEDWWDQW GX YDVLVWDV, XQ DQLPDO DX WKRUDA LQGLJR, D O'DLJXLOORQ VDIUDQ, QL XQ FDIDUG, QL XQ FKDUDQFRQ, PDLV SOXWRW XQ DUWLVRQ, V'DYDQFDLW, WUDLQDQW XQ EULQ G'DOID. LO V'DSSURFKD, YRXODQW O'DSODWLU G'XQ FRXS YLI, PDLV O'DQLPDO SULW VRQ YRO, GLVSDUDLVVDQW GDQV OD QXLW DYDQW TX'LO DLW SX O'DVVDLOOLU.
stats = {c:msg.count(c) for c in set(msg)}
ll = list(stats.items())
ll.sort(key=lambda x:x[1], reverse=True)
print (ll)
[(' ', 176), ('D', 110), ('L', 94), ('Q', 92), ('V', 72), ('X', 70), ('R', 68), ('W', 67), ('O', 64), ('U', 45), ('S', 27), (',', 23), ('G', 22), ("'", 20), ('F', 19), ('.', 16), ('P', 16), ('Y', 14), ('I', 13), ('J', 10), ('\n', 10), ('E', 10), ('T', 8), ('K', 6), ('B', 3), (';', 2), ('A', 2), ('-', 1), ('M', 1), ('C', 1)]
# C'est le 'D' qui est le plus fréquent. Si on fait l'hypothèse qu'il code le 'E', on essaie
CC = AA[-1:]+AA[:-1]
print (CC)
ZABCDEFGHIJKLMNOPQRSTUVWXY
vv = str.maketrans(CC,aa) ###
msg.translate(vv)
"erxsr zscp r'evvmzemx tew e hsvqmv. mp eppyqe. wsr ned qevuyemx qmrymx zmrkx. mp tsywwe yr tvsjsrh wsytmv, \nw'ewwmx herw wsr pmx, w'ettycerx wyv wsr tspsglsr. mp tvmx yr vsqer, mp p'syzvmx, mp pyx; qemw mp r'c wemwmwwemx uy'yr \nmqfvskpms gsrjyw, mp fyxemx e xsyx mrwxerx wyv yr qsx hsrx mp mkrsvemx pe wmkrmjmgexmsr.\nmp eferhsrre wsr vsqer wyv wsr pmx. mp eppe e wsr pezefs; mp qsymppe yr kerx uy'mp tewwe wyv wsr jvsrx, wyv wsr gsy.\nwsr tsypw fexxemx xvst jsvx. mp ezemx gleyh. mp syzvmx wsr zewmwxew, wgvyxe pe rymx. mp jemwemx hsyb. \nyr fvymx mrhmwxmrgx qsrxemx hy jeyfsyvk. yr gevmppsr, tpyw psyvh uy'yr kpew, tpyw wsyvh uy'yr xsgwmr, \ntpyw tvsjsrh uy'yr fsyvhsr, rsr psmr, wsrre xvsmw gsytw. hy gerep wemrx-qevxmr, yr gpetsxmw tpemrxmj wmkrepemx \nyr gleperh uym tewwemx.\nwyv p'efexxerx hy zewmwxew, yr ermqep ey xlsveb mrhmks, e p'emkymppsr wejver, rm yr gejevh, rm yr glevergsr, \nqemw tpyxsx yr evxmwsr, w'ezergemx, xvemrerx yr fvmr h'epje. mp w'ettvsgle, zsyperx p'etpexmv h'yr gsyt zmj, qemw \np'ermqep tvmx wsr zsp, hmwtevemwwerx herw pe rymx ezerx uy'mp emx ty p'ewwemppmv."
# Visiblement, c'est raté. Essayons avec la deuxième lettre la plus fréquente du français, qui est le 'S'
ord('D')-ord('S')
-15
CC=AA[21:]+AA[:21]
vv = str.maketrans(CC,aa)
msg.translate(vv)
"ivbwv dwgt v'izzqdiqb xia i lwzuqz. qt ittcui. awv rih uizyciqb uqvcqb dqvob. qt xwcaai cv xzwnwvl awcxqz, \na'iaaqb liva awv tqb, a'ixxcgivb acz awv xwtwkpwv. qt xzqb cv zwuiv, qt t'wcdzqb, qt tcb; uiqa qt v'g aiqaqaaiqb yc'cv \nqujzwotqw kwvnca, qt jcbiqb i bwcb qvabivb acz cv uwb lwvb qt qovwziqb ti aqovqnqkibqwv.\nqt ijivlwvvi awv zwuiv acz awv tqb. qt itti i awv tidijw; qt uwcqtti cv oivb yc'qt xiaai acz awv nzwvb, acz awv kwc.\nawv xwcta jibbiqb bzwx nwzb. qt idiqb kpicl. qt wcdzqb awv diaqabia, akzcbi ti vcqb. qt niqaiqb lwcf. \ncv jzcqb qvlqabqvkb uwvbiqb lc nicjwczo. cv kizqttwv, xtca twczl yc'cv otia, xtca awczl yc'cv bwkaqv, \nxtca xzwnwvl yc'cv jwczlwv, vwv twqv, awvvi bzwqa kwcxa. lc kivit aiqvb-uizbqv, cv ktixwbqa xtiqvbqn aqovitiqb \ncv kpitivl ycq xiaaiqb.\nacz t'ijibbivb lc diaqabia, cv ivquit ic bpwzif qvlqow, i t'iqocqttwv ainziv, vq cv kinizl, vq cv kpizivkwv, \nuiqa xtcbwb cv izbqawv, a'idivkiqb, bziqvivb cv jzqv l'itni. qt a'ixxzwkpi, dwctivb t'ixtibqz l'cv kwcx dqn, uiqa \nt'ivquit xzqb awv dwt, lqaxiziqaaivb liva ti vcqb idivb yc'qt iqb xc t'iaaiqttqz."
# Toujours pas ... Essayons la suivante, le 'A'
CC=AA[3:]+AA[:3]
vv = str.maketrans(CC,aa)
msg.translate(vv)
"anton voyl n'arrivait pas a dormir. il alluma. son jaz marquait minuit vingt. il poussa un profond soupir, \ns'assit dans son lit, s'appuyant sur son polochon. il prit un roman, il l'ouvrit, il lut; mais il n'y saisissait qu'un \nimbroglio confus, il butait a tout instant sur un mot dont il ignorait la signification.\nil abandonna son roman sur son lit. il alla a son lavabo; il mouilla un gant qu'il passa sur son front, sur son cou.\nson pouls battait trop fort. il avait chaud. il ouvrit son vasistas, scruta la nuit. il faisait doux. \nun bruit indistinct montait du faubourg. un carillon, plus lourd qu'un glas, plus sourd qu'un tocsin, \nplus profond qu'un bourdon, non loin, sonna trois coups. du canal saint-martin, un clapotis plaintif signalait \nun chaland qui passait.\nsur l'abattant du vasistas, un animal au thorax indigo, a l'aiguillon safran, ni un cafard, ni un charancon, \nmais plutot un artison, s'avancait, trainant un brin d'alfa. il s'approcha, voulant l'aplatir d'un coup vif, mais \nl'animal prit son vol, disparaissant dans la nuit avant qu'il ait pu l'assaillir."
La clef est une permutation de l'alphabet. Chaque lettre est donc toujours codée de la même manière. Un cryptogramme assez long aura donc la même distribution de fréquences qu'un texte en langue naturelle.
Pour la cryptanalyse, on démarre avec les fréquences et on essaie d'aligner une table de traduction. Si le texte est court, ce n'est pas forcément facile.
On peut trouver des fréquences de lettres ici. Comme on l'a vu, les fréquences peuvent dépendre du type de texte, et ne sont pas forcément significatives sur un texte court.
Pour le français, les lettres classées par fréquences décroissantes sont
E A I S T N R U L O D M P C V Q G B F J H Z X Y K W
Comme il est dfficile de retenir par coeur une permutation de l'alphabet, on a utilisé divers systèmes permettant d'en engendrer à partir d'une information plus simple.
Le chiffre de César revient à représenter les 26 lettres de l'alphabet par un entier modulo 26 :
$$A=0, B=1, C=2,\ldots, Y=24, Z=25\ \mod 26$$.
et à remplacer la lettre de code $x$ par celle de code $y=x+b$, où $b\in {\mathbb Z}/26{\mathbb Z}$ est la clef.
On peut compliquer un peu le système en la codant par $y=ax+b$, où $a\in ({\mathbb Z}/26{\mathbb Z})^*$ doit être inversible pour que le message soit déchiffrable.
La notation ${\mathbb Z}/26{\mathbb Z}$ désigne l'ensemble des entiers modulo 26, c'est à dire des symboles $\{\bar 0,\bar 1,\bar 2,\cdots,\overline{25}\}$, où $\bar a=\bar b \Leftrightarrow a-b$ est divisible par 26, ce qu'on note encore $a\equiv b\ [26]$, et se lit "$a$ est congru à $b$ modulo 26".
d = {x:aa.index(x) for x in aa}
print (d)
{'a': 0, 'b': 1, 'c': 2, 'd': 3, 'e': 4, 'f': 5, 'g': 6, 'h': 7, 'i': 8, 'j': 9, 'k': 10, 'l': 11, 'm': 12, 'n': 13, 'o': 14, 'p': 15, 'q': 16, 'r': 17, 's': 18, 't': 19, 'u': 20, 'v': 21, 'w': 22, 'x': 23, 'y': 24, 'z': 25}
Voici la table d'addition modulo 26 :
def tabadd(n):
print(' + |', end=' ')
for y in range(n):
print ("%2d "% (y % n),end=' ')
print()
print('-'*(4*n+3))
for x in range(n):
print('%2d |'%x, end= ' ')
for y in range(n):
print ("%2d "% ((x+y) % n),end=' ')
print()
# d'abord modulo 5 pour comprendre le principe
tabadd(5)
+ | 0 1 2 3 4 ----------------------- 0 | 0 1 2 3 4 1 | 1 2 3 4 0 2 | 2 3 4 0 1 3 | 3 4 0 1 2 4 | 4 0 1 2 3
tabadd(26)
+ | 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ----------------------------------------------------------------------------------------------------------- 0 | 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 0 2 | 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 0 1 3 | 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 0 1 2 4 | 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 0 1 2 3 5 | 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 0 1 2 3 4 6 | 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 0 1 2 3 4 5 7 | 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 0 1 2 3 4 5 6 8 | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 0 1 2 3 4 5 6 7 9 | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 0 1 2 3 4 5 6 7 8 10 | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 0 1 2 3 4 5 6 7 8 9 11 | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 0 1 2 3 4 5 6 7 8 9 10 12 | 12 13 14 15 16 17 18 19 20 21 22 23 24 25 0 1 2 3 4 5 6 7 8 9 10 11 13 | 13 14 15 16 17 18 19 20 21 22 23 24 25 0 1 2 3 4 5 6 7 8 9 10 11 12 14 | 14 15 16 17 18 19 20 21 22 23 24 25 0 1 2 3 4 5 6 7 8 9 10 11 12 13 15 | 15 16 17 18 19 20 21 22 23 24 25 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 16 | 16 17 18 19 20 21 22 23 24 25 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 17 | 17 18 19 20 21 22 23 24 25 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 18 | 18 19 20 21 22 23 24 25 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 19 | 19 20 21 22 23 24 25 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 20 | 20 21 22 23 24 25 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 21 | 21 22 23 24 25 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 22 | 22 23 24 25 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 23 | 23 24 25 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 24 | 24 25 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 25 | 25 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
Et la table de multiplication
def tabmul(n):
print(' * |', end=' ')
for y in range(1,n):
print ("%2d "% (y % n),end=' ')
print()
print('-'*(4*n-1))
for x in range(1,n):
print('%2d |'%x, end= ' ')
for y in range(1,n):
print ("%2d "% ((x*y) % n),end=' ')
print()
tabmul(5)
* | 1 2 3 4 ------------------- 1 | 1 2 3 4 2 | 2 4 1 3 3 | 3 1 4 2 4 | 4 3 2 1
tabmul(26)
* | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ------------------------------------------------------------------------------------------------------- 1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 2 | 2 4 6 8 10 12 14 16 18 20 22 24 0 2 4 6 8 10 12 14 16 18 20 22 24 3 | 3 6 9 12 15 18 21 24 1 4 7 10 13 16 19 22 25 2 5 8 11 14 17 20 23 4 | 4 8 12 16 20 24 2 6 10 14 18 22 0 4 8 12 16 20 24 2 6 10 14 18 22 5 | 5 10 15 20 25 4 9 14 19 24 3 8 13 18 23 2 7 12 17 22 1 6 11 16 21 6 | 6 12 18 24 4 10 16 22 2 8 14 20 0 6 12 18 24 4 10 16 22 2 8 14 20 7 | 7 14 21 2 9 16 23 4 11 18 25 6 13 20 1 8 15 22 3 10 17 24 5 12 19 8 | 8 16 24 6 14 22 4 12 20 2 10 18 0 8 16 24 6 14 22 4 12 20 2 10 18 9 | 9 18 1 10 19 2 11 20 3 12 21 4 13 22 5 14 23 6 15 24 7 16 25 8 17 10 | 10 20 4 14 24 8 18 2 12 22 6 16 0 10 20 4 14 24 8 18 2 12 22 6 16 11 | 11 22 7 18 3 14 25 10 21 6 17 2 13 24 9 20 5 16 1 12 23 8 19 4 15 12 | 12 24 10 22 8 20 6 18 4 16 2 14 0 12 24 10 22 8 20 6 18 4 16 2 14 13 | 13 0 13 0 13 0 13 0 13 0 13 0 13 0 13 0 13 0 13 0 13 0 13 0 13 14 | 14 2 16 4 18 6 20 8 22 10 24 12 0 14 2 16 4 18 6 20 8 22 10 24 12 15 | 15 4 19 8 23 12 1 16 5 20 9 24 13 2 17 6 21 10 25 14 3 18 7 22 11 16 | 16 6 22 12 2 18 8 24 14 4 20 10 0 16 6 22 12 2 18 8 24 14 4 20 10 17 | 17 8 25 16 7 24 15 6 23 14 5 22 13 4 21 12 3 20 11 2 19 10 1 18 9 18 | 18 10 2 20 12 4 22 14 6 24 16 8 0 18 10 2 20 12 4 22 14 6 24 16 8 19 | 19 12 5 24 17 10 3 22 15 8 1 20 13 6 25 18 11 4 23 16 9 2 21 14 7 20 | 20 14 8 2 22 16 10 4 24 18 12 6 0 20 14 8 2 22 16 10 4 24 18 12 6 21 | 21 16 11 6 1 22 17 12 7 2 23 18 13 8 3 24 19 14 9 4 25 20 15 10 5 22 | 22 18 14 10 6 2 24 20 16 12 8 4 0 22 18 14 10 6 2 24 20 16 12 8 4 23 | 23 20 17 14 11 8 5 2 25 22 19 16 13 10 7 4 1 24 21 18 15 12 9 6 3 24 | 24 22 20 18 16 14 12 10 8 6 4 2 0 24 22 20 18 16 14 12 10 8 6 4 2 25 | 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
Les lignes (ou colonnes) paires et celle de 13 ne contiennent pas 1 (et contiennent 0). Il faut donc que $$a\in\{1,3,5,7,9,11,15,17,19,21,23,25\}$$ si on veut pouvoir déchiffrer : il faut pouvoir retrouver $x$ à partir de $y=ax+b$.
Par exemple, l'inverse de 7 est 15 car $7\times 15 = 105 = 8\times 13+1\equiv 1\ \mod 26$.
Donc, $(a,b)=(7,12)$ serait une clef valable, et la formule de déchiffrement serait
$$y=ax+b=7x+12\ \Leftrightarrow\ x = a^{-1}(y-b) = 15\times (y-12) \mod 26.$$On verra dans la suite un algorithme pour calculer les inverse modulaires. Ici, on peut se contenter d'une recherche exhaustive ...
# Inverses par force brute
def i26(x):
if x%2 and x%13: return [y for y in range(1,26,2) if (x*y)%26==1][0]
else: return None
i26(7)
15
print (i26(4))
None
On implantera les systèmes cryptographiques comme des classes possédant des méthodes crypt et uncrypt :
class Affine(object):
def __init__(self,a,b):
assert a%2 and a%13
self.a = a; self.b = b
self.d = i26(a)
def crypt(self,msg):
return ''.join([aa[(self.a*aa.index(c)+self.b)%26] for c in msg if c in aa]).upper()
def uncrypt(self,crpt):
return ''.join([aa[(self.d*(AA.index(c)-self.b))%26] for c in crpt if c in AA])
A = Affine(7,12)
y=A.crypt('le cheval de mon cousin ne mange du foin que le dimanche')
y
'LOAJODMLHOSGZAGWIQZZOSMZCOHWVGQZUWOLOHQSMZAJO'
A.uncrypt(_)
'lechevaldemoncousinnemangedufoinqueledimanche'
Pour la cryptanalyse, on doit déterminer $a$ et $b$. On peut commencer par rechercher les deux lettres le plus fréquentes.
stats = {c:y.count(c) for c in set(y)}
ll = list(stats.items())
ll.sort(key=lambda x:x[1],reverse=True)
print (ll)
[('O', 8), ('Z', 6), ('A', 3), ('L', 3), ('W', 3), ('S', 3), ('Q', 3), ('M', 3), ('G', 3), ('H', 3), ('J', 2), ('I', 1), ('C', 1), ('D', 1), ('V', 1), ('U', 1)]
Les deux lettres les plus fréquentes sont O et Z, qu'on peut essayer d'identifier à E et A :
print (AA.index('O'), AA.index('Z'), AA.index('E'), AA.index('A'))
14 25 4 0
Ça donne le système $4a+b=14$, $0a+b=25$ qui n'a pas de solutions.
L'hypothèse E=O est correcte mais Z chiffre le N. Il faut donc tâtonner un peu.
Mais si on sait que la phrase commence par le mot "le" ... (attaque "clair connu" ou "mot probable")
aa.index('l'),aa.index('e'), AA.index('L'),AA.index('O')
(11, 4, 11, 14)
... on a alors les équations $$11a+b\equiv 11\mod 26$$ $$4a+b\equiv 14 \mod 26$$ d'où $7a\equiv 11-14=-3\equiv 23\mod 26$, ce qui donne $$a=-3\times 15=-45\equiv 7\mod 26$$ et $$b=14-4\times 7=-14\equiv 12\mod 26.$$
Pour s'essayer aux chiffres monoalphabétiques arbitraires, on pouvait jouer au Cryptochallenge de la NSA (une application pour ios et android qui n'existe plus).
On utilise l'analyse fréquentielle. On trouvera ici des tables de fréquences pour plusieurs langues.
Voilà quelques exemples des cryptogrames proposés (en anglais).
On fait évoluer une table de traduction en s'aidant du dictionnaire anglais et d'expressions régulières.
def freqs(y):
stats = {c:y.count(c) for c in set(y)}
ll = list(stats.items())
ll.sort(key=lambda x:x[1],reverse=True)
return ll
### Un exemple
nsa = 'X 1998 KDUQHSH GNDQP QDMEYJ INDYV PFXP PFH SXZNQEPW NI HMMEK EKMXYV, X JXPHLXW INQ SEMMENYK NI ESSEJQXYPK, EK EY YHL ZHQKHW.'
print(freqs(nsa))
[(' ', 21), ('E', 10), ('X', 8), ('H', 8), ('Q', 7), ('K', 7), ('N', 7), ('Y', 7), ('P', 7), ('M', 6), ('S', 5), ('I', 4), ('D', 4), ('J', 3), ('W', 3), ('L', 2), ('V', 2), ('F', 2), ('9', 2), ('Z', 2), (',', 2), ('G', 1), ('1', 1), ('.', 1), ('U', 1), ('8', 1)]
yy = ''.join([x[0] for x in freqs(nsa) if x[0] in AA])
yy
'EXHQKNYPMSIDJWLVFZGU'
len(yy)
20
# On essaie d'aligner avec les fréquences en anglais
xx='ETAOINSRHDLUCMFYWGPB'.lower()
yy='EHXKNQPYMSDIJWFLVZGU'
ww=str.maketrans(yy,xx)
print (nsa)
print (nsa.translate(ww))
X 1998 KDUQHSH GNDQP QDMEYJ INDYV PFXP PFH SXZNQEPW NI HMMEK EKMXYV, X JXPHLXW INQ SEMMENYK NI ESSEJQXYPK, EK EY YHL ZHQKHW. a 1998 olbntdt pilns nlherc uilrw sfas sft daginesm iu thheo eoharw, a castyam uin dehheiro iu eddecnarso, eo er rty gtnotm.
# Le X->A et N->I sont probablkement corrects. I->S ?
# IU -> IS ? N->I, I->S
xx='ETAOINURHDLSCMFYWGPB'.lower()
yy='EHXKNQPYMSDIJWFLVZGU'
ww=str.maketrans(yy,xx)
print (nsa)
print (nsa.translate(ww))
X 1998 KDUQHSH GNDQP QDMEYJ INDYV PFXP PFH SXZNQEPW NI HMMEK EKMXYV, X JXPHLXW INQ SEMMENYK NI ESSEJQXYPK, EK EY YHL ZHQKHW. a 1998 olbntdt pilnu nlherc silrw ufau uft dagineum is thheo eoharw, a cautyam sin dehheiro is eddecnaruo, eo er rty gtnotm.
# NI ->IS, PFH -> THE ?
xx = 'AISTHE'.lower()
yy = 'XNIPFH'
ww=str.maketrans(yy,xx)
print (nsa)
print (nsa.translate(ww))
X 1998 KDUQHSH GNDQP QDMEYJ INDYV PFXP PFH SXZNQEPW NI HMMEK EKMXYV, X JXPHLXW INQ SEMMENYK NI ESSEJQXYPK, EK EY YHL ZHQKHW. a 1998 KDUQeSe GiDQt QDMEYJ siDYV that the SaZiQEtW is eMMEK EKMaYV, a JateLaW siQ SEMMEiYK is ESSEJQaYtK, EK EY YeL ZeQKeW.
# C'est sur la bonne voie
# JATELAW : ^.ATE.A.$ dans le dictionnaire donne GATEWAY
# etc ...
xx = 'ASUPREMISNTHCOJFLGWYD'.lower()
yy = 'XKDUQHSEKYPFGNZIMJLWV'
ww=str.maketrans(yy,xx)
print (nsa)
print (nsa.translate(ww))
X 1998 KDUQHSH GNDQP QDMEYJ INDYV PFXP PFH SXZNQEPW NI HMMEK EKMXYV, X JXPHLXW INQ SEMMENYK NI ESSEJQXYPK, EK EY YHL ZHQKHW. a 1998 supreme court ruling found that the majority of ellis island, a gateway for millions of immigrants, is in new jersey.
# Autre exemple, semaine du 2/11/2015
nsa = 'C VTZCA VLT NKXGGQR STIIQQ TA LQH GCK CAR NBIIQHQR PLXHR-RQEHQQ FBHAN VTA AQCHGO $3 ZXGGXTA XA RCZCEQN IHTZ ZSRTACGR\'N XA 1992.'
stats = {c:nsa.count(c) for c in set(nsa)}
print (stats)
{'L': 3, '-': 1, 'S': 2, 'R': 8, 'Q': 11, "'": 1, 'K': 2, ' ': 21, 'G': 7, 'V': 3, '1': 1, 'T': 8, 'I': 5, 'X': 6, 'F': 1, 'N': 5, '9': 2, '.': 1, 'O': 1, 'H': 7, 'A': 10, 'B': 2, '2': 1, 'Z': 5, '$': 1, 'P': 1, 'E': 2, '3': 1, 'C': 8}
ll = list(stats.items())
ll.sort(key=lambda x:x[1], reverse=True)
print (ll)
[(' ', 21), ('Q', 11), ('A', 10), ('R', 8), ('T', 8), ('C', 8), ('G', 7), ('H', 7), ('X', 6), ('I', 5), ('N', 5), ('Z', 5), ('L', 3), ('V', 3), ('S', 2), ('K', 2), ('9', 2), ('B', 2), ('E', 2), ('-', 1), ("'", 1), ('1', 1), ('F', 1), ('.', 1), ('O', 1), ('2', 1), ('$', 1), ('P', 1), ('3', 1)]
xx='ETAOINSRHDLUCMFYWGPB'.lower()
yy='QACRTGHXINZLVBEKSFOP'
ww=str.maketrans(yy,xx)
print (nsa)
print (nsa.translate(ww))
C VTZCA VLT NKXGGQR STIIQQ TA LQH GCK CAR NBIIQHQR PLXHR-RQEHQQ FBHAN VTA AQCHGO $3 ZXGGXTA XA RCZCEQN IHTZ ZSRTACGR'N XA 1992. a cilat cui dyrnneo wihhee it ues nay ato dmhheseo burso-oefsee gmstd cit teasnp $3 lrnnrit rt oalafed hsil lwoitano'd rt 1992.
xx='ENADOLFISMWRHCUGPCBYT'.lower()
yy='QACRTGIXNZVHLWBEKSFOP'
ww=str.maketrans(yy,xx)
print (nsa)
print (nsa.translate(ww))
C VTZCA VLT NKXGGQR STIIQQ TA LQH GCK CAR NBIIQHQR PLXHR-RQEHQQ FBHAN VTA AQCHGO $3 ZXGGXTA XA RCZCEQN IHTZ ZSRTACGR'N XA 1992. a woman who spilled coffee on her lap and suffered third-degree burns won nearly $3 million in damages from mcdonald's in 1992.
Dans les communications militaires, on supprimait espaces et ponctuation, et les chiffres étaient écrit en toutes lettres. Pour la lisibilté, on présentait en général le cryptogramme par blocs de cinq lettres.
La clef est un mot que l'on répète indéfiniment et que l'on ajoute au texte modulo le cadinal de l'alphabet (26 pour nous) :
clef = SECRET texte : LE CHEVAL DE MON COUSIN NE MANGE DU FOIN QUE LE DIMANCHE clef : SE CRETSE CR ETS ECRETS EC RETSE CR ETSE CRE TS ECRETSEC cryptogramme : DI EYIOSP FV QHF GQLWBF RG DEGYI FL JHAR SLI EW HKDEGULG
print (list(map(ord,'SECRET')))
print (list(map(ord, AA)))
[83, 69, 67, 82, 69, 84] [65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90]
s = 'LECHEVALDEMONCOUSINNEMANGEDUFOINQUELEDIMANCHE'
k = 'SECRET'
print (''.join([chr((ord(s[i])-65+ord(k[i%6])-65)%26 + 65) for i in range(len(s))]))
DIEYIOSPFVQHFGQLWBFRGDEGYIFLJHARSLIEWHKDEGULG
Le système revient donc à utiliser $m$ alphabets de César différents, où $m$ est la longueur de la clef.
Si on parvient à déterminer la longueur de la clef, l'analyse des fréquences permettra de la retouver à partir d'un texte assez long.
Il existe pour cela deux méthodes : l'analyse des trigrammes (Babbage/Kasiski) et l'indice de coïncidence (Friedman).
Méthode des trigrammes (Babbage, Kasiski)
On construit un dictionnaire dont les clefs sont les facteurs de longueur 3 du cryptogramme :
dic['xyz']=[12,56,123]si 'xyz' apparaît aux positions 12, 56, 123. Pour les trigrammes qui apparaissent plus d'une fois, on calcule les distances entre leurs occurences successives. La longueur de la clef divise la majorité de ces distances. Ce sera leur pgcd après élimination des faux positifs. Par exemple, pour le cryptogramme
XAUNM EESYI EDTLL FGSNB WQUFX PQTYO RUTYI INUMQ IEULS MFAFX GUTYB XXAGB HMIFI IMUMQ IDEKR IFRIR ZQUHI ENOOO IGRML YETYO VQRYS IXEOK IYPYO IGRFB WPIYR BQURJ IYEMJ IGRYK XYACP PQSPB VESIR ZQRUF REDYJ IGRYK XBLOP JARNP UGEFB WMILX MZSMZ YXPNB PUMYZ MEEFB UGENL RDEPB JXONQ EZTMB WOEFI IPAHP PQBFL GDEMF WFAHQ
la liste (triée) des distances entre occurences d'un même trigramme est
[20, 20, 25, 25, 25, 30, 30, 30, 30, 30, 30, 30, 30, 65, 70, 75, 75, 95, 170, 201]et on en conclut facilement que la longueur de la clef doit être 5 (mais ce n'est pas toujours aussi évident).
Pour trouver la clef, on peut ensuite procéder à l'analyse des fréquences sur les 5 sous chaines
s[i::5]
.
Mais ce n'est pas le plus efficace. Il vaut mieux recourir à la méthode statistique.
Méthode de Friedman (indice de coïncidence)
L'indice de coïncidence $I_c(w)$ d'une chaîne de caractères $w$ est la probabilité pour que deux caractères pris au hasard (à des positions différentes, évidemment) soient identiques.
Si $n$ est le nombre total de lettres de $w$ et $n_x$ le nombre de lettres $x$ dans $w$, le nombre total de couples de lettres $(w_i,w_j)$ ($i\not=j)$ vaut $n(n-1)$, et le nombre de couples pour lesquels $w_i=w_j=x$ vaut $n_x(n_x-1)$.
Donc, si $X$ est l'alphabet de $w$, $$I_c(w) =\sum_{x\in X}\frac{n_x(n_x-1)}{n(n-1)}.$$
Dans une langue naturelle, les lettres n'apparaissent pas toutes avec la même fréquence, et l'indice de coincidence d'un texte sera très différent de celui d'une chaîne aléatoire, lequel vaudrait environ $1/26$ (pourquoi ?). Pour le français, la valeur moyenne est $0.0746$.
Dans un texte chiffré avec un clef de longueur $k$, deux lettres sont
Comme il y a $n(n-1)/2$ paires de lettres dans le texte, l'indice de coïncidence probable du texte $w$ sera $$ I_c(w)\simeq\frac{n(i_L-i_A)+k(ni_A-i_L)}{k(n-1)} $$ où $i_A=1/N$ où $N$ est le nombre de lettres de l'alphabet, et $i_L$ est l'indice de coïncidence de la langue considérée.
En inversant cette formule, on en déduit une estimation de la longueur $k$ $$ k=\frac{n(i_L-i_A)}{(n-1)I_c(w)-ni_A+i_L}. $$
Cette formule ne donne pas toujours de très bons résultats. La méthode la plus efficace consiste simplement à calculer l'indice de coïncidence des sous-chaînes w[::m], la longueur de la clef sera en général le $m$ qui donne la valeur maximale.
On ne travaille plus avec un alphabet de 26 lettres, mais avec des octets. On a donc maintenant 256 "caractères".
On remplace le décalage par le OU exclusif (XOR), qui a l'avantage d'être involutif.
class CyberCesar(object):
def __init__(self,k):
self.k = k
def crypt(self,buf):
return [b^self.k for b in buf]
def uncrypt(self,buf):
return self.crypt(buf)
C = CyberCesar(0xe2)
y = C.crypt(map(ord,'À bon chat bon rat !'))
print(y) ### Résultat différent ! x est une chaîne unicode ici
[34, 194, 128, 141, 140, 194, 129, 138, 131, 150, 194, 128, 141, 140, 194, 144, 131, 150, 194, 195]
''.join(list(map(chr,y))) ###
'"Â\x80\x8d\x8cÂ\x81\x8a\x83\x96Â\x80\x8d\x8cÂ\x90\x83\x96ÂÃ'
Il n'y a aucun caractère imprimable dans le résultat. On utilise principalement deux encodages pour les données binaires : hex et base64.
On les trouve dans le module codecs.
Pour les calculs, on travaille avec des entiers, il faut donc aussi des fonctions de conversion.
from codecs import encode, decode ### pour hex et base64
def hex2buf(hh):
return bytes(decode(hh,'hex'))
def buf2hex(bb):
return encode(bytes(bb),'hex')
def str2buf(s):
return [ord(c) for c in s]
def buf2str(bb):
return ''.join([chr(b) for b in bb])
x = 'À bon chat bon rat !'
print(buf2hex(str2buf(x)))
b'c020626f6e206368617420626f6e207261742021'
y = C.crypt(str2buf(x))
print (y)
[34, 194, 128, 141, 140, 194, 129, 138, 131, 150, 194, 128, 141, 140, 194, 144, 131, 150, 194, 195]
z = buf2hex(y)
print (z)
b'22c2808d8cc2818a8396c2808d8cc2908396c2c3'
u = C.uncrypt(y)
print (u)
[192, 32, 98, 111, 110, 32, 99, 104, 97, 116, 32, 98, 111, 110, 32, 114, 97, 116, 32, 33]
print (buf2str(u))
À bon chat bon rat !
# base64
def b642buf(cc):
return decode(cc,'base64')
def buf2b64(bb):
return encode(bytes(bb),'base64')
z = buf2b64(y)
print (z)
b'IsKAjYzCgYqDlsKAjYzCkIOWwsM=\n'
bb = b642buf(z)
print (bb)
b'"\xc2\x80\x8d\x8c\xc2\x81\x8a\x83\x96\xc2\x80\x8d\x8c\xc2\x90\x83\x96\xc2\xc3'
print (C.uncrypt(bb)) ### buffer vu comme liste d'entiers
[192, 32, 98, 111, 110, 32, 99, 104, 97, 116, 32, 98, 111, 110, 32, 114, 97, 116, 32, 33]
### Vigenère
class CyberVigenere(object):
def __init__(self,k):
self.k = k
self.m = len(k)
def crypt(self,buf):
return [buf[i]^self.k[i%self.m] for i in range(len(buf))]
def uncrypt(self,buf):
return self.crypt(buf)
key = str2buf('Vigenère')
key
[86, 105, 103, 101, 110, 232, 114, 101]
V = CyberVigenere(key)
x = 'Il est plus facile de se laver les dents dans un verre à pied que de se laver les pieds dans un verre à dents.'
xx = str2buf(x)
yy = V.crypt(xx)
print (buf2b64(yy))
print (buf2hex(yy))
b'HwVHAB2cUhU6HBRFCIkRDDoMRwELyAEAdgUGEwuaUgkzGkcBC4YGFnYNBgsdyAcLdh8CFxyNUoV2\nGQ4ACsgDEDNJAwBOmxdFOggRABzIHgAlSRcMC4wBRTIICRZOnRxFIAwVFwvIkkUyDAkRHcY=\n' b'1f0547001d9c52153a1c14450889110c3a0c47010bc80100760506130b9a5209331a47010b860616760d060b1dc8070b761f02171c8d528576190e000ac80310334903004e9b17453a0811001cc81e002549170c0b8c0145320809164e9d1c45200c15170bc89245320c09111dc6'
buf2str(yy)
'\x1f\x05G\x00\x1d\x9cR\x15:\x1c\x14E\x08\x89\x11\x0c:\x0cG\x01\x0bÈ\x01\x00v\x05\x06\x13\x0b\x9aR\t3\x1aG\x01\x0b\x86\x06\x16v\r\x06\x0b\x1dÈ\x07\x0bv\x1f\x02\x17\x1c\x8dR\x85v\x19\x0e\x00\nÈ\x03\x103I\x03\x00N\x9b\x17E:\x08\x11\x00\x1cÈ\x1e\x00%I\x17\x0c\x0b\x8c\x01E2\x08\t\x16N\x9d\x1cE \x0c\x15\x17\x0bÈ\x92E2\x0c\t\x11\x1dÆ'
print (buf2str(V.uncrypt(yy)))
Il est plus facile de se laver les dents dans un verre à pied que de se laver les pieds dans un verre à dents.
Le one-time pad ou masque jetable consiste à utiliser une clef aussi longue que le message et à ne jamais la réutiliser. C'est le seul système absolument sûr, mais il n'est évidemment pas très pratique.
# Clef : 256 octets aléatoires
K = open('/dev/urandom','rb').read(256)
print (encode(K,'hex'))
b'5fdd3625b3104ab92d6f15f13eb4de23a37ea0310e2469e7d9da0e98bf2398da414dd9b7764590230bc19d672cb9a953c92e554b9cbb32ab2771c3c81e5578ec954a3cb3033acfba3da46f1b5d418b4ea6555c0a578326c0cd0202623bd816b7b6b91fc34caf74b9ea930770163500502921fefc438651a5346d54aa5ec4f72bfdaaf53e320a9ca5495be5a4b5a02258b0e4410a89d20a0904c7c90583642e5ba09e58aa2f11623c717c5e4c75c0639bd3c743083c79f577b47f562689d456eb396555419e0c3735bdee623a8a71865b809db9f24d3cc9773c41f90758b8ba808b97d5b3d89e4e71e96f615abd3d9a6db85665394c5bc3ff0adc035ee4d21fa3'
def otp(msg,key,offset):
return bytes([ord(c)^k for c,k in zip(msg,key[offset::])]) ###
msg = 'abracadacra'
otp(msg,K,0)
b'>\xbfDD\xd0q.\xd8N\x1dt'
class OneTimePad(object):
def __init__(self,key):
self.key=key
def crypt(self,buf,offset):
return bytes([c^k for c,k in zip(buf,self.key[offset::])])
def uncrypt(self,buf,offset):
return self.crypt(buf,offset)
OTP=OneTimePad(K)
msg = 'Il est plus facile de se laver les dents dans un verre à pied que de se laver les pieds dans un verre à dents.'
buf = str2buf(msg)
print (K)
b'_\xdd6%\xb3\x10J\xb9-o\x15\xf1>\xb4\xde#\xa3~\xa01\x0e$i\xe7\xd9\xda\x0e\x98\xbf#\x98\xdaAM\xd9\xb7vE\x90#\x0b\xc1\x9dg,\xb9\xa9S\xc9.UK\x9c\xbb2\xab\'q\xc3\xc8\x1eUx\xec\x95J<\xb3\x03:\xcf\xba=\xa4o\x1b]A\x8bN\xa6U\\\nW\x83&\xc0\xcd\x02\x02b;\xd8\x16\xb7\xb6\xb9\x1f\xc3L\xaft\xb9\xea\x93\x07p\x165\x00P)!\xfe\xfcC\x86Q\xa54mT\xaa^\xc4\xf7+\xfd\xaa\xf5>2\n\x9c\xa5I[\xe5\xa4\xb5\xa0"X\xb0\xe4A\n\x89\xd2\n\t\x04\xc7\xc9\x05\x83d.[\xa0\x9eX\xaa/\x11b<q|^Lu\xc0c\x9b\xd3\xc7C\x08<y\xf5w\xb4\x7fV&\x89\xd4V\xeb9eUA\x9e\x0c75\xbd\xeeb:\x8aq\x86[\x80\x9d\xb9\xf2M<\xc9w<A\xf9\x07X\xb8\xba\x80\x8b\x97\xd5\xb3\xd8\x9eNq\xe9oaZ\xbd=\x9am\xb8Ve9L[\xc3\xff\n\xdc\x03^\xe4\xd2\x1f\xa3'
y = OTP.crypt(buf,0)
print (y)
b'\x16\xb1\x16@\xc0dj\xc9A\x1af\xd1X\xd5\xbdJ\xcf\x1b\x80Uk\x04\x1a\x82\xf9\xb6o\xee\xdaQ\xb8\xb6$>\xf9\xd3\x13+\xe4P+\xa5\xfc\t_\x99\xdc=\xe9X09\xee\xde\x12K\x07\x01\xaa\xadzu\t\x99\xf0jX\xd6#I\xaa\x9aQ\xc5\x19~/a\xe7+\xd5u,c2\xe7U\xe0\xa9cl\x11\x1b\xadx\x97\xc0\xdcm\xb1)\x8f\x94\x99\x8e\xf6i\x04e\x1b'
z = OTP.uncrypt(y,0)
z
b'Il est plus facile de se laver les dents dans un verre \xe0 pied que de se laver les pieds dans un verre \xe0 dents.'
print(buf2str(z))
Il est plus facile de se laver les dents dans un verre à pied que de se laver les pieds dans un verre à dents.