Rappel : un signe est un élément d’imprimerie. Les signes correspondent à l’ensemble des caractères imprimables ainsi que les espaces et les caractères spéciaux.
Il s’agit d'observer aujourd’hui une méthode de chiffrement symétrique à transposition : on modifie mathématiquement l’ordre du contenu, sans en changer les symboles représentés. Elle utilise un principe similaire à celui utilisé par le chiffrement AES. Ce type de chiffrement symétrique n’augmente pas la taille totale transférée du message, en dehors de la clé utilisée (qui peut être utilisée à plusieurs reprises, même si ce n'est pas conseillé...).
Cette méthode semble indéchiffrable (le contenu est temporairement « perdu ») à une condition : que ladite clé reste secrète. C’est pour cette raison que l’utilisation sur Internet n’est pas possible – tout du moins sans utiliser d’abord un chiffrement asymétrique à clés publiques ! Dans ce cas, la sécurité totale du système d’échange repose sur la fiabilité du chiffrement asymétrique : l’interception de la clé symétrique sur une connexion chiffrée asymétriquement mais écoutée et piratée, fait perdre toute la confidentialité. Le couple symétrique/asymétrique n’a donc intérêt (sauf saucuneous certaines conditions trop particulières, comme sur des lignes optiques polarisées) car la fiabilité du chiffrement asymétrique suffit à garantir la sécurité du message.
Il existe de nombreuses raisons de chiffrer symétriquement ses messages : la confidentialité bien sûr, mais parfois aussi des raisons techniques, en obligeant à des opérations sur le message qui permet d’en garantir « totalement » le contenu. Ne rentrons pas ici sur les limites juridiques ainsi que les questions illégales que peuvent cacher le chiffrement : il s’agit d’un outil, comme un couteau - qui peut aider l’homme à manger et à se défendre, comme à tuer son prochain ou à agresser. C’est moins la fonction de l’outil que l’usage qui en détermine une forme de moralité…
Dans mon exemple, un texte (long) et une phrase « passe » (donc la longueur doit être raisonnable, au moins égale au quart de la taille de la matrice utilisée et où cette matrice est dimensionnée au paquet de signes à traiter à un instant t), seront utilisés conjointement.
L’intérêt de la phrase « passe » est d’en retenir une suite de chiffres – ça tombe bien, Python utilise ce principe pour gérer ses strings -, qui sera associée à des opérations (en réalité à des fonctions bêtes). Ces opérations peuvent être potentiellement publiques : c’est leur ordre et leur fréquence qui comptent. La connaissance de l’opération individuelle (où même d’une partie) ne permet pas de connaître le chiffrement général. Le code source de cet outil peut être diffusé sans restriction.
La phrase « passe » est donc la recette du chiffrement : elle donne à la fois l’ordre et le nombre d’opérations à effectuer une matrice de caractères/signes, eux aussi convertis en nombre. Ainsi, tel que les noms de domaine sur le web, on retient une phrase humainement compréhensible (donc plus facile à retenir) qu’une suite de chiffres ou d'expédients / d'indications…
Exemple :
Code python : | Sélectionner tout |
1 2 3 | >>> list(map(ord,list("toto va au marché"))) # ---> la fonction native de Python "ord", donne la représentation le code Unicode [116, 111, 116, 111, 32, 118, 97, 32, 97, 117, 32, 109, 97, 114, 99, 104, 233] { effectuer lopération 116 puis 111 puis 116 puis 111 puis 32 } |
Pour simplifier, nous utiliserons seulement trois caractères différents, qui resteront des caractères, correspondant à une opération « simple » de matrice sous Numpy :
Code python : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | def operation_A(npArray): return np.roll(npArray,1) def operation_B(npArray): return np.rot90(npArray) def operation_C(npArray): return np.flip(npArray,1) OPERATIONS = { "a" : operation_A, "b" : operation_B, "c" : operation_C } |
Fonctionnement
Nous allons tout d’abord créer une matrice avec Numpy, en veillant à ne pas dépasser une certaine taille de matrice, qui correspond à une portion arbitraire du texte à chiffrer. Si la portion est trop petite, on ajoute à DROITE le nombre de signes nécessaires par un signe pris au hasard DANS L’ENSEMBLE du texte à chiffre. Gardons à l'esprit que ce signe ajouté se doit d'être plutôt dans une fréquence d’apparition basse.
Création de la matrice :
Code python : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | MATRICES_taille = (5,5) MATRICES_nbre = MATRICES_taille[0] * MATRICES_taille[1] import numpy as np def matrice_creer(texte_portion,ajout="*"): if len(texte_portion)<MATRICES_nbre: texte_portion = texte_portion.rjust(MATRICES_nbre, ajout) elif len(texte_portion)>MATRICES_nbre: raise ValueError( "la portion de texte ne peut excédent %s signes" % MATRICES_nbre ) texte_portion = np.asarray( list(map(ord,list(texte_portion))) ) texte_portion = np.reshape( texte_portion, MATRICES_taille ) return texte_portion |
Au choix, trouver l’ajout :
Code python : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 | def occurences_chgt_frequence(texte): # ---> « hasard » total return ''.join(random.choice(list(texte)) for _ in range(1)) def occurences_chgt_frequence(texte): # ---> le premier des signes les moins fréquents frequence = list( ( lettre, texte.count(lettre) ) for lettre in list(texte) ) return sorted( frequence, key = lambda couple: couple[1] )[0][0] |
Le corps du script, ici pour seulement les 20 premiers caractères afin de garder de se placer dans un cas un peu spécial :
Code python : | Sélectionner tout |
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 26 | phrase = "abcaabacaabacccaba" texte_original = "Ceci est un message totalement confidentiel: \ les coordonnées du paquet de cookies sont au placard haut, \ étagère de droite. Stop." ajout = occurences_chgt_frequence( texte_original ) texte_np = matrice_creer( texte_original[0:20], ajout ) for signe in phrase: texte_np = OPERATIONS[signe]( texte_np ) texte_np = np.reshape( texte_np, (1, MATRICES_nbre) ) print("".join(list(map(chr,texte_np.tolist()[0])))) |
Lorsque j’exécute le script, en laissant le signe d’ajout à l’opérateur étoile (« * »), voici ce que cela donne pour la portion 0:20 :
Code : | Sélectionner tout |
1 2 3 | Phrase "abcaabacaabacccaba" → ****** ic*Cu t*e***ens ** Phrase "abcaabacaabaccaba" → eu t*e ic*C***** ***sn*** |
Voici pour la totalité du message :
Code : | Sélectionner tout |
1 2 3 | Phrase "abcaabacaabacccaba" → uot egaCttnemel tedifnos l :leiandrooc ced seéntoteuqapsukooc en tnos s icalp ud tuah dearègatéa,ord edreotS .e ise ice pt.sem n Phrase "abcaabacaabaccaba" → otS .e iord edrerègatéa,tuah deacalp ud tnos s ikooc en teuqapsud seéntodrooc cel :leianedifnos tnemel tot egaCt.sem nutpse ice |
– Il est possible d’associer plusieurs phrases « passe », en fonction du nombre de relais : chacun peut ainsi avoir un « maillon de la chaîne » (principe du block-chain) lisible et le reste du contenu indéchiffrable. Utile pour avoir un relais sans confiance (il n’a pas la possibilité de connaître le contenu) ou par exemple donner des indications (envoyer le reste du contenu à tel relais si on envoie des paquets, effectuer un traitement / une vérification, etc).
– Le travail non directement sur des nombres sous Numpy, mais des matrices de bits, change l’ordre du contenu et aussi la représentation envoyées : en effet, chaque bit étant rangé dans un ordre différent, la matrice revenue à une seule dimension, donnera des signes RADICALEMENT différents.
– Rendre publiques les opérations (et notamment la taille générique de la matrice), pour ne transférer secrètement que les phrases « passe » et le texte chiffré, économise BEAUCOUP de données transférées.
– Utiliser tous les signes usuels de l’alphabet, pour n’envoyer que des indications de ressources entre deux personnes, qui échangeront ensuite numériquement : ainsi la page 112 de telle édition de tel ouvrage sert de phrase « passe »… D’ailleurs la phrase peut être envoyée généralement par tout moyen sécurisé (connexion TSL, liaison optique polarisée, pigeon avec un entraînement militaire, bouteille à la mer… ah non pas la bouteille).
– Avoir un jeu de plusieurs alternatives pour une même opération et en avoir, à l’avance, déterminé l’usage : ainsi l’opération 1-1 est utilisée une fois, puis si elle est appelée à nouveau (pour une nouvelle portion) l’opération 1-2, puis si elle est appelée à nouveau l'opération 1-3, puis on revient à 1-1, etc. L’intérêt est de rajouter un (gros) grain de sable et de rendre pratiquement impossible à déterminer une série d’opérations, pourtant toutes connues par le potentiel espion...
– Une telle méthode est idéale pour « couper » le chiffrement d’un contenu très important en une multitude de portions, permettant l’usage facilité de l’asynchronisme.
Edition - ajout d'un exemple plus complet
Voici un exemple complet, asynchrone, qui n'utilise pas Numpy et à vocation pédagogique :
Code python : | Sélectionner tout |
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 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 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 | import re class MatriceSimple: debug = False MATRICE_taille_colonne = 8 OPERATIONS = {} operations_binaires = False def __init__(self, contenu_original, binaire=False ): self.contenu_original = contenu_original self.matriceComplexe = False self.operations_binaires = True if binaire is True else False def __log__(self, *args): """méthode de log interne ; à surcharger""" if self.debug: print(*args) def peupler(self): if self.operations_binaires: matriceSimple = list( "".join( ( bin(ord(signe))[2:] ).rjust(8,"0") for signe in self.contenu_original ) ) else: matriceSimple = list(self.contenu_original) yield self.matriceComplexe = [ [] ] y = 0 for bit in matriceSimple: if len(self.matriceComplexe[y]) >= self.MATRICE_taille_colonne: self.matriceComplexe.append( [] ) y += 1 self.matriceComplexe[y].append( bit ) yield def crypter(self, phrase, inverse=False): yield from self.__matrice_transformer__( phrase if inverse is False else phrase[::-1], inverse ) def decrypter(self, phrase): yield from self.crypter( phrase, True ) def __matrice_creer__(self): yield matriceSimple = list( "".join( ( bin(ord(signe))[2:] ).rjust(8,"0") for signe in self.contenu_original ) ) yield self.matriceComplexe = [ [] ] y = 0 for bit in matriceSimple: if len(self.matriceComplexe[y]) > self.MATRICES_taille[1]: self.matriceComplexe.append( [] ) y += 1 self.matriceComplexe[y].append( bit ) yield def __matrice_transformer__(self, phrase, inverse): for op_id in phrase: try: fct = getattr( self, "operation_%s" % op_id ) self.__log__( "opération '%s' demandée : %s " % ( op_id, fct.__doc__ ) ) yield from fct( op_id, inverse ) except: raise ValueError("cette opération '%s' n'existe pas" % op_id) def extraire(self): matrice = yield from self.__matrice_reduire__() message = yield from self.__matrice_traduire__( matrice ) return message def __matrice_reduire__(self): if self.matriceComplexe is False: raise Exception( "aucun contenu encore crypté" ) bits_modifies = [] for ligne in self.matriceComplexe: for bit in ligne: bits_modifies.append(bit) yield return bits_modifies def __matrice_traduire__(self, matriceSimple): message = [] if self.operations_binaires: for bit in re.finditer( "([01]{8})", "".join(matriceSimple) ): message.append( chr( int( bit.group(0), 2 ) ) ) yield else: for signe in matriceSimple: message.append( signe ) yield return "".join(message) class CryptRapide(MatriceSimple): def operation_A(self, op_id, inverse=False): """décalage d'une ligne""" ancienne_matrice = self.matriceComplexe[:] yield if inverse: self.matriceComplexe = [ancienne_matrice[-1],]+ancienne_matrice[0:-1] else: self.matriceComplexe = ancienne_matrice[1:]+[ancienne_matrice[0],] def operation_B(self, op_id, inverse=False): """décalage d'une colonne dans chaque ligne""" ancienne_matrice = self.matriceComplexe[:] yield nouvelle_matrice = [] if inverse: for ligne in ancienne_matrice: ligne = ligne[:] nouvelle_matrice.append( [ligne[-1],] + ligne[0:-1] ) yield else: for ligne in ancienne_matrice: ligne = ligne[:] nouvelle_matrice.append( [*ligne[1:], ligne[0]] ) yield self.matriceComplexe = nouvelle_matrice def operation_C(self, op_id, inverse=False): """inversion des lignes""" ancienne_matrice = self.matriceComplexe[:] yield nouvelle_matrice = [] for ligne in ancienne_matrice: nouvelle_matrice.append( ligne[::-1] ) yield self.matriceComplexe = nouvelle_matrice def auto(binaire, phrase_id, phrase, texte_original): global verrou_affichage ma_matrice = CryptRapide( texte_original ) ma_matrice.operations_binaires = binaire yield from ma_matrice.peupler() print("! log (%s) : La matrice du contenu a été créée" % phrase_id) yield from ma_matrice.crypter( phrase ) print("! log (%s) : le contenu a été crypté" % phrase_id) valeur_message = yield from ma_matrice.extraire() print("! log (%s) : message crypté :" % phrase_id, valeur_message) yield from ma_matrice.decrypter( phrase ) print("! log (%s) : message décrypté :" % phrase_id) valeur_message = yield from ma_matrice.extraire() print("! log (%s) : message décrypté :" % phrase_id, valeur_message) return "Fin de la tache %s" % phrase_id def boucle_asynchrone(listes_taches): taches_generateurs = [] for tache in listes_taches: fct, *args = tache taches_generateurs.append( fct(*args) ) while True: try: tache = taches_generateurs.pop(0) next(tache) taches_generateurs.append( tache ) except StopIteration as err: print(">>> Le générateur a terminé et dit : ", err) if len(taches_generateurs)==0: break if __name__=="__main__": #VERSION NON-BINAIRE -> moins efficace boucle_asynchrone( [ ( auto, # fct appelée dans la boucle événementielle False, # ne sera pas traitement binairement 1, # id de la tache "AAACBABCBCABACBBB", # phrase passe "Ceci est un super message secret, plus long \ mais proportionnellement il y a moins de traitements, \ c'est bizarre hein ? Mais c'est comme ça, il fallait un exemple..." # contenu du message ), ( auto, False, 2, "AAACBABCBCABACBACBBCAAABBBB", "Ceci est un message secret de Nothus (Julien Garderon, août 2017)." ) ] ) print("- - - - - - - - - - - - - - -") #VERSION BINAIRE -> plus efficace (car la matrice générée est plus grande ; plus de dimensions (3, 4, ... au lieu de 2D utilisées dans cet exemple) ou une taille de phrase plus grande = plus de sécurité) boucle_asynchrone( [ ( auto, True, 1, "AAACBABCBCABACBBB", "Ceci est un super message secret, plus long \ mais proportionnellement il y a moins de traitements, \ c'est bizarre hein ? Mais c'est comme ça, il fallait un exemple..." ), ( auto, True, 2, "AAACBABCBCABACBACBBCAAABBBB", "Ceci est un message secret de Nothus (Julien Garderon, août 2017)." ) ] ) |