Blog traitant d'informatique, de programmation, de python, de golang, de linux...
Ce vrai-faux pdf s'ouvre sans soucis avec tout bon lecteur et contient en apparence une seule page affichant le message "Nothing To Hide"...
...en apparence seulement, puisqu'évidemment, ce fichier contient aussi l'ensemble des blocs chiffrés qui nous intéressent, et qui ne seront pas détectés par le lecteur pdf.
Cette nouvelle version apporte aussi la possibilité de travailler avec plusieurs jeux de clés différents, ce qui étend un peu la sécurité et permet éventuellement de partager certains jeux de clés sans partager votre jeu de clés "personnel".
Si vous ne l'avez pas encore fait, je vous conseille quand même d'aller lire l'article concernant la 1ère version de pyFC ici avant de continuer...
1/ Création des clés de chiffrement :
Dans cette version 0.2, lors de la 1ère utilisation, il est nécessaire (comme dans la version 0.1) de créer un premier jeu de clés de chiffrement. Cela se fait de la même manière, à la différence près que maintenant, les jeux de clés sont nommés. Cette opération est toutefois transparente pour la création du 1er jeu de clés (qui portera le nom "default"), et que vous pourrez créer comme suit à partir du terminal :
you@yourComputer:~/pyFC$ python3 pyFC.py -init
Comme dans la version précédente, le script vous demandera confirmation. Entrez 'OK' pour valider la création de ce 1er jeu, puis quand le programme vous le demandera, entrez dans la console le chemin où vous souhaitez installer les fichiers clés.
Vous pouvez maintenant créer autant de jeu de clés que vous le souhaitez avec la commande suivante :
you@yourComputer:~/pyFC$ python3 pyFC.py -createkey unNouveauJeuDeClés
"unNouveauJeuDeClés" étant le nom que l'on souhaite attribuer à ce nouveau jeu. Cette opération '-createkey' suit exactement les mêmes étapes que l'opération '-init', la seule différence étant le nom passé en paramètre. Lors de la création d'un nouveau jeu de clé, le script va vérifier que le nom passé en paramètre pour le nouveau jeu n'est pas déjà utilisé, et vous renvoyer un message d'erreur sinon. Si la création d'un nouveau jeu de clé se déroule correctement, alors ce nouveau jeu de clé devient le jeu de clé courant.

Pour toute opération exécutée, pyFC vous indiquera le temps écoulé.
Pour choisir le jeu de clé avec lequel vous souhaitez chiffrer/déchiffrer un fichier, il suffit de passer le paramètre '-setkey' suivi du nom du jeu de clé à utiliser. Ici, exemple avec le jeu de clés 'default':
you@yourComputer:~/pyFC$ python3 pyFC.py -setkey default

Vous pouvez supprimer un jeu de clés à condition qu'il existe et qu'il ne soit pas en cours d'utilisation :
you@yourComputer:~/pyFC$ python3 pyFC.py -removekey unNouveauJeuDeClés

Enfin, vous pouvez vérifier la configuration des clés avec :
you@yourComputer:~/pyFC$ python3 pyFC.py -showkeyinfo

2/ Chiffrement/déchiffrement d'un fichier ou d'un répertoire :
Cette version 0.2 procède pour le chiffrement/déchiffrement de la même manière que dans la version précédente, et rajoute quelques étapes supplémentaires afin d'empaqueter les blocs chiffrés dans un fichier pdf. Voici les différentes étapes du chiffrement :
a/ pyFC va zipper le fichier ou répertoire à traiter dans un fichier nommé 'precrypted.zip'
b/ Ce fichier 'precrypted.zip' va être lu puis découpé (selon sa taille, en blocs de quelques mégas à quelques dizaines de méga-octets). Chaque bloc va être placé dans un byteArray puis chiffré avec le jeu de clé en cours. Pour plus de détails sur cette partie chiffrement, voir mon article sur la version précédente de pyFC ici. Cette opération de chiffrement va sauver chaque bloc (autant de blocs que nécessaires) sous le nom 'Unix timestamp' du moment, puis utilisera comme extension le n° de bloc (".000", ".001", ".002", etc...)
c/ Chacun de ces blocs va être intégré dans une nouvelle archive zip dont le nom sera "nomDuFichierOuRépertoireATraiter.pdf". Oui, vous avez bien lu ".pdf" ! C'est une ruse à 2 francs pour rajouter un peu de confusion, ce nom étant aussi le nom du fichier final que l'on obtiendra en dernière étape. En gros, on a simplement un fichier zip dont l'extension n'est pas "zip", mais "pdf". Cette archive zip sera protégée par un mot de passe généré automatiquement comme étant la 14ème itération du SHA-512 du nom de cette même archive.
d/ On efface chaque fichier de bloc chiffré (ceux dont l'extension est ".000", ".001", ".002", etc...).
e/ On crée une nouvelle archive zip, portant le nom "nomDuFichierOuRépertoireATraiter.zip". On y intègre le fichier "nomDuFichierOuRépertoireATraiter.pdf" précédemment créé. On protège cette nouvelle archive "nomDuFichierOuRépertoireATraiter.zip" par un autre mot de passe généré automatiquement comme étant la 23ème itération du SHA-512 du nom de cette même archive (celle en ".zip"). Le script se charge alors d'effacer le fichier "nomDuFichierOuRépertoireATraiter.pdf" qui n'est plus nécessaire, puisqu'intégré dans la dernière archive zip.
f/ Dans le répertoire ../pyFC_V0.2 (là où se trouve le script) se trouve aussi un fichier 'pdfroot.bytes'. Ce fichier est un vrai fichier pdf que j'ai obtenu tout simplement en exportant une page que j'ai créé à partir d'Open-Office, puis que j'ai renommé 'pdfroot.bytes'. pyFC va charger ce fichier 'pdfroot.bytes' dans un byteArray, puis charger la dernière archive zip créée ("nomDuFichierOuRépertoireATraiter.zip") à la suite dans ce même byteArray, et enfin sauver le tout dans un seul et même fichier nommé "nomDuFichierOuRépertoireATraiter.pdf".
g/ Je n'ai pas testé tous les lecteurs pdf, mais des essais que j'ai pu faire, le dernier fichier produit est lu sans aucun problème, le lecteur ignorant totalement ce qui se trouve après les 'balises" indiquant la fin du document pdf. Nous voilà donc avec un document pdf en apparence tout ce qu'il y a de plus standard, mais contenant en fait toutes vos données chiffrées.
h/ Pour info, vous pouvez à la place du fichier 'pdfroot.bytes' fourni mettre n'importe quel fichier pdf de votre choix, dans la mesure où vous le renommer à l'identique.
Je ne re-détaillerai pas ici toutes les étapes du déchiffrement, ces étapes étant grosso-modo les mêmes que pour le chiffrement mais parcourues à l'envers. Les paramètres de la ligne de commande pour le chiffrement et le déchiffrement sont les même que pour la version 0.1. Voici ce que cela donne dans une console :
Chiffrement du répertoire "/media/Data2_SDA7/essai/Cours d'Allemand":
you@yourComputer:~/pyFC$ python3 pyFC.py -e "/media/Data2-SDA7/essai/Cours d'Allemand"

Déchiffrement du fichier "/media/Data2_SDA7/essai/Cours d'Allemand.pdf":
you@yourComputer:~/pyFC$ python3 pyFC.py -d "/media/Data2-SDA7/essai/Cours d'Allemand.pdf"
Les plus observateurs d'entre vous auront noté que la commande elle-même n'apparaît pas dans les copies d'écran de la console. C'est parce que j'ai lancé les opérations de chiffrement et déchiffrement non pas directement dans un terminal, mais à partir de mon explorateur de fichiers (caja) auquel j'ai rajouté dans le menu contextuel des raccourcis vers pyFC me permettant de chiffrer/déchiffrer directement en sélectionnant le fichier. J'écrirai probablement un article à ce sujet. Le soucis avec cette méthode est que le terminal se ferme directement à la fin du process. Pour parer à cela, j'ai donc rajouté une option supplémentaire dans pyFC qui me permet d'attendre un appui sur une touche avant de fermer le terminal (en réalité, avant de terminer le script python lui-même). Les commandes complètes sont :
you@yourComputer:~/pyFC$ python3 pyFC.py -waitForKeyboard -e "/media/Data2-SDA7/essai/Cours d'Allemand"
et
you@yourComputer:~/pyFC$ python3 pyFC.py -waitForKeyboard -d "/media/Data2-SDA7/essai/Cours d'Allemand.pdf"
Enfin, dans les choses utiles, n'oubliez pas l'aide :
you@yourComputer:~/pyFC$ python3 pyFC.py -help

3/ Limites actuelles et évolution possibles :
Dans l'état actuel, cette version 0.2 n'est toujours pas "Windows ready". Je développe ce script sur mon temps libre, et je n'ai pas d'ordinateur sous Windows à ma disposition (linux, quand tu nous tiens :-)) pour le moment pour avancer sur ce point. Au-delà de ça, je pense que pour la prochaine version de pyFC, je mettrai "de grands coups de ciseaux" dans le script de cette version 0.2, qui m'a l'air OK d'un point de vue fonctionnel (mais j'attends vos retours :-)), mais qui est devenu trop long et trop lourd (1030 lignes !!!). Un découpage en modules s'impose, ne serait-ce que pour la lisibilité !
Question évolution des fonctionnalités ou de l'interface, je laisse venir/murir les idées pour le moment... En attendant, vous pouvez télécharger l'archive zip qui contient le répertoire du script et les fichiers qui vont bien ici.
Script pyFC.py :
1 #!/usr/bin/env python3 2 # -*- coding: utf-8 -*- 3 # V0.2 du 05/07/2019 4 5 6 7 import os 8 import sys 9 from glob import glob as gloglob 10 from shutil import move as shutil_move 11 from shutil import rmtree as shutil_rmtree 12 import secrets 13 import hashlib 14 import time 15 import random 16 import string 17 import zipfile 18 19 20 21 22 23 def removeRNFromListe(listName): 24 ''' Enlève les caractères \r ou \r\n en fin de ligne pour chaque string contenue dans listName ''' 25 newList = [] 26 for oneLine in listName: 27 if oneLine.endswith("\r\n"): 28 oneLine = oneLine[0:(len(oneLine)-2)] 29 if oneLine.endswith("\n"): 30 oneLine = oneLine[0:(len(oneLine)-1)] 31 if len(oneLine) >0: 32 newList.append(oneLine) 33 return newList 34 35 36 37 def readFileLines(filename): 38 ''' Lit le fichier filename (fichier texte), et stocke chacune de ses lignes dans la <list> newList ''' 39 if os.path.exists(filename): 40 oneStringList = [] 41 with open(filename, 'r') as myFile: 42 oneStringList = myFile.readlines() 43 newList = removeRNFromListe(oneStringList) 44 return newList 45 else: 46 return ("Le fichier "+filename+" n'existe pas!") 47 48 49 50 def setConfFileAccess(accessibility): 51 ''' Permet de rendre /home/user/.pyFC/.pyFC.conf ainsi que tous les fichiers .conf de ce répertoire 52 visibles et accessibles ou non selon les besoins''' 53 os.chmod(pfcDir, 0o774) # On met les droits de lecture/écriture sur le répertoire 54 fileList = gloglob(pfcDir+'/.*') #On liste uniquement les fichiers cachés 55 fileList.sort() 56 if accessibility: 57 #if os.path.exists(pfcConfFile): 58 # os.chmod(pfcConfFile, 0o774) # On met les droits de lecture/écriture sur le fichier conf 59 for oneFile in fileList: 60 os.chmod(oneFile, 0o774) # On met les droits de lecture/écriture sur le fichier conf 61 #todo : gérer le attrib sous win 62 else: 63 os.chmod(pfcDir, 0o774) # On met les droits de lecture/écriture sur le répertoire 64 try: 65 for oneFile in fileList: 66 os.chmod(oneFile, 0o000) # On met les droits de lecture/écriture sur le fichier conf 67 os.chmod(pfcDir, 0o000)# On supprime les droits de lecture/écriture sur le répertoire 68 #todo : gérer le attrib sous win 69 except PermissionError: 70 pass 71 72 73 74 def setKeyfilesAccess(accessibility): 75 ''' Permet de rendre (tous) les fichiers clé (la clé courante mais aussi toutes les autres) 76 visibles et accessibles ou non selon les besoins. ''' 77 fileList = gloglob(pfcDir+'/.*') #On liste uniquement les fichiers cachés 78 fileList.sort() 79 nindex = 0 80 nindice = -1 81 for oneFile in fileList: 82 if '.pyFC.conf' in oneFile: 83 nindice = nindex 84 nindex += 1 85 if nindice>-1: 86 del fileList[nindice] # On supprime le fichier pyFC.conf de la liste des fichiers fileList 87 allKeyPath = [] 88 for oneFile in fileList: 89 allKeyPath.extend(readFileLines(oneFile)) 90 nindex = 0 91 for fullPath in allKeyPath: 92 if accessibility: # On rend les clés accessibles 93 fullPath = fullPath[0:len(fullPath)-145] 94 for n in range(29): 95 dirStr = allKeyPath[nindex][0:len(fullPath)+(n+1)*5] 96 os.chmod(dirStr, 0o774) 97 #todo : gérer le attrib sous win 98 else: # On rend les clés inaccessibles 99 for n in range(29): 100 dirStr = allKeyPath[nindex][0:len(fullPath)-n*5] 101 try: 102 os.chmod(dirStr, 0o000) 103 #todo : gérer le attrib sous win 104 except PermissionError: 105 pass 106 nindex += 1 107 108 109 110 def readKeyList(): 111 ''' Lit le contenu du répertoire ~/.pyFC/ ''' 112 setConfFileAccess(True) 113 fileList = gloglob(pfcDir+'/.*') #On liste uniquement les fichiers cachés 114 fileList.sort() 115 nindex = 0 116 nindice = -1 117 for oneFile in fileList: 118 if '.pyFC.conf' in oneFile: 119 nindice = nindex 120 nindex += 1 121 if nindice>-1: 122 del fileList[nindice] # On supprime le fichier pyFC.conf de la liste des fichiers fileList 123 setConfFileAccess(False) 124 return fileList 125 126 127 128 def readConfFile(): 129 ''' Lit le fichier /home/user/.pyFC/.pyFC.conf et 130 stocke son contenu ligne par ligne dans keyFilepathList ''' 131 global currentKeyName 132 global keyFilepathList 133 setConfFileAccess(True) 134 if os.path.exists(pfcConfFile): 135 keyFilepathList = readFileLines(pfcConfFile) 136 currentKeyName = keyFilepathList[0] 137 del keyFilepathList[0] 138 setConfFileAccess(False) 139 140 141 142 def showkeyinfo(): 143 ''' Affiche la liste des jeux de clés existant ainsi que celui en cours d'utilisation ''' 144 readConfFile() 145 print('************************************************') 146 print('*') 147 print("* Liste des clés valides :") 148 keyList = readKeyList() 149 for oneKey in keyList: 150 lineInfo = '* --> '+oneKey.split('.')[-2] 151 if currentKeyName in (oneKey.split('.')[-2]) and (oneKey.split('.')[-2]) in currentKeyName: 152 lineInfo += " (en cours d'utilisation)" 153 print(lineInfo) 154 print('*') 155 156 157 158 def setkey(keyname): 159 setConfFileAccess(True) 160 newKeyList = readFileLines(pfcDir+'/.'+keyname+'.conf') 161 if os.path.exists(pfcConfFile): 162 os.remove(pfcConfFile) # On efface le fichier .pyFC.conf existant (obsolète) 163 with open(pfcConfFile, 'a') as confFile: # On reporte thiskey dans pyFC.conf 164 confFile.write(keyname+'\r\n') 165 for nindex in range(5): 166 with open(pfcConfFile, 'a') as confFile: # On reporte newKeyList dans pyFC.conf 167 confFile.write(newKeyList[nindex]+'\r\n') 168 setConfFileAccess(False) 169 170 171 172 def print4(message): 173 ''' Simple fonction print() affichant une ligne avant le message et deux lignes après ''' 174 print('************************************************') 175 print('*') 176 print('*', message) 177 print('*') 178 print('*') 179 180 181 182 def removekey(keyname): 183 readConfFile() 184 if keyname in currentKeyName and currentKeyName in keyname: 185 print4("Erreur, la clé ne doit pas être la clé courante, changez de clé avant de la supprimer !") 186 else: 187 print("* Cette opération va supprimer le jeu de clés "+keyname) 188 print('************************************************') 189 print('*') 190 strOK = input("* Voulez-vous vraiment continuer ? (Abandon si différent de OK) : ") 191 print('*') 192 if len(strOK)==2 and 'OK' in strOK: 193 print("* Suppression du jeu de clé "+keyname+" :") 194 print('*') 195 setConfFileAccess(True) 196 setKeyfilesAccess(True) 197 keyFilepathToDelete = readFileLines(pfcDir+'/.'+keyname+'.conf') 198 onePath=[] 199 for oneFile in keyFilepathToDelete: 200 theList = oneFile.split('/') 201 repLevel = len(theList)-29 202 makeStrPath = '' 203 for n in range(repLevel): 204 makeStrPath += '/'+theList[n+1] 205 print('* Effacement de '+str(makeStrPath) + '...') 206 shutil_rmtree(makeStrPath, ignore_errors=True) 207 os.remove(pfcDir+'/.'+keyname+'.conf') 208 print('*') 209 print('* Suppression du fichier de configuration '+pfcDir+'/.'+keyname+'.conf') 210 print('*') 211 setKeyfilesAccess(False) 212 setConfFileAccess(False) 213 else: 214 print("* Abandon de l'opération de suppression de la clé "+keyname) 215 print('*') 216 217 218 219 def createKeyFiles(keyname): 220 ''' Permet de créer les 5 fichiers clés (fichiers 'binaires') contenant nbBytes générés aléatoirement. ''' 221 global pfcConfFile 222 global pfcDir 223 global keyFilepathList 224 print("Création d'un nouveau jeu de clés de chiffrement. Cela peut prendre un moment...") 225 pathToScript = os.path.dirname(os.path.realpath(sys.argv[0])) 226 227 # Création de 5 arborescences contenant 28 niveaux de répertoires 228 # Chaque fichier clé sera généré dans le répertoire le plus loin de la racine (niveau 28) 229 for keyIndex in range(5): 230 for rep in range(28): 231 newrepName = '/.' 232 rangeSize = 3 233 if rep == 0: 234 newrepName += str(keyIndex+1) 235 rangeSize = 2; 236 for _ in range(rangeSize): 237 newrepName += random.choice(string.digits+string.ascii_letters) 238 pathToScript += newrepName 239 os.mkdir(pathToScript) 240 os.chmod(pathToScript, 0o774) 241 nbBytes = 100*(keyIndex+1)*(2**20) 242 myBytes = os.urandom(nbBytes) 243 oneFileName = pathToScript+'/.key'+str(keyIndex+1) 244 with open(oneFileName, 'wb') as keyFile: # On écrit les 5 fichiers 'clé' 245 keyFile.write(myBytes) 246 print("Fichier key"+str(keyIndex+1), "créé") 247 pathToScript = os.path.dirname(os.path.realpath(sys.argv[0])) 248 keyFilepathList.append(oneFileName) # On mémorise le chemin vers chaque clé 249 250 print() 251 print("Les 5 nouvelles clés de chiffrement ont été créées. Pour des raisons de sécurité, il est") 252 print("préférable de placer ces fichiers clés ailleurs que dans le répertoire .../pyFile") 253 print("Néanmoins, le programme fonctionnera correctement si vous n'entrez rien (simple appui sur [ENTER].") 254 print("Dans ce cas, les fichiers clés resteront simplement dans .../pyFile") 255 print() 256 print("Pour la ligne ci-dessous, si vous êtes sous Windows, veillez à") 257 print("remplacer les \ par des / dans la définition du chemin.") 258 print() 259 260 newPathStr = input("Entrez le chemin où sera installé le nouveau jeu de clés : ") 261 if len(newPathStr) > 0: 262 while not os.path.exists(newPathStr): 263 print(newPathStr,"n'est pas un répertoire valide !") 264 newPathStr = input("Entrez le chemin où sera installé le nouveau jeu de clé : ") 265 print() 266 if '/' in newPathStr[-1]: # Pour éliminer le / final si présent 267 newPathStr = newPathStr[0:len(newPathStr)-1] 268 269 # Déplacement des clés vers leur nouveau répertoire et M.A.J de keyFilepathList 270 for nindex in range(5): 271 end = len(keyFilepathList[nindex])-141 272 print("Déplacement du fichier key"+str(nindex+1)) 273 print(keyFilepathList[nindex][0:end]) 274 print() 275 shutil_move(keyFilepathList[nindex][0:end], newPathStr) 276 keyFilepathList[nindex] = newPathStr+keyFilepathList[nindex][end-5:-1]+str(nindex+1) 277 print() 278 279 # Création de /home/user/.pyFC si nécessaire & gestion des droits... 280 if not os.path.exists(pfcDir): 281 os.mkdir(pfcDir) 282 os.chmod(pfcDir, 0o774) # On met les droits de lecture/écriture sur le répertoire 283 if os.path.exists(pfcConfFile): 284 os.chmod(pfcConfFile, 0o774) # On met les droits de lecture/écriture sur le fichier conf 285 os.remove(pfcConfFile) # Puis on efface le fichier existant (obsolète) 286 287 for nindex in range(5): 288 pathToConfFile = pfcDir+'/.'+keyname+'.conf' 289 with open(pathToConfFile, 'a') as confFile: # On reporte keyFilepathList dans [keyname].conf 290 confFile.write(keyFilepathList[nindex]+'\r\n') 291 292 # La configuration est maintenant écrite dans /home/user/.pyFC/.[keyname].conf, 293 # On va lire la configuration pour protéger les répertoires qui contiennent les clés 294 setkey(keyname) 295 showkeyinfo() 296 readConfFile() 297 # on protège les répertoires des clés en supprimant les droits de lecture de ces répertoires 298 setConfFileAccess(True) 299 setKeyfilesAccess(False) 300 setConfFileAccess(False) 301 print4("Le nouveau jeu de clés est maintenant activé") 302 303 304 def printUsage(): 305 ''' Affiche l'aide, le 'mode d'emploi' de pyFC ''' 306 print() 307 print() 308 print("Utilisation de pyFC :") 309 print() 310 print("pyFC.py --> affiche ce mode d'emploi") 311 print("pyFC.py -help --> affiche ce mode d'emploi") 312 print("pyFC.py -v --> affiche la version de pyFC") 313 print("pyFC.py -init --> pour initialiser pyFC - Permet la création d'un nouveau jeu de") 314 print(" clé de chiffrement - Opération à effectuer en premier.") 315 print() 316 print("pyFC.py -e filename --> pour chiffrer (-e --> encrypt) le fichier nommé 'filename'.") 317 print("pyFC.py -e dirname --> pour chiffrer (-e --> encrypt) le répertoire nommé 'dirname'.") 318 print("pyFC.py -d filename --> pour déchiffrer (-d --> decrypt) le fichier nommé 'filename'.") 319 print() 320 print("pyFC.py -waitForKeyboard -e filename --> pour chiffrer (-e --> encrypt) le fichier nommé 'filename'.") 321 print("pyFC.py -waitForKeyboard -e dirname --> pour chiffrer (-e --> encrypt) le répertoire nommé 'dirname'.") 322 print("pyFC.py -waitForKeyboard -d filename --> pour déchiffrer (-d --> decrypt) le fichier nommé 'filename'.") 323 print(" Ici, l'option '-waitForKeyboard' permettra au script d'attendre") 324 print(" un appui clavier avant de fermer le terminal. Cette option") 325 print(" est réservée à une utilisation par menu contextuel dans un") 326 print(" navigateur de fichier afin que le terminal ne se ferme pas.") 327 print(" instantanément à la fin des opérations.") 328 print() 329 print("pyFC.py -showconf --> rend visibles et accessibles les fichiers ~/.pyFC/.*.conf'") 330 print(" ainsi que toutes les arborescences des fichiers clés") 331 print("pyFC.py -hideconf --> rend inaccessibles et invisibles les fichiers ~/.pyFC/.*.conf") 332 print(" ainsi que toutes les arborescences des fichiers clés") 333 print(" Attention, les arguments -showconf et -hideconf ne doivent être") 334 print(" utilisés qu'après la mise en place d'un jeu de clé valide !") 335 print() 336 print() 337 print("pyFC.py -showkeyinfo --> affiche la liste des jeux de clés existants ainsi que le nom") 338 print(" de celui qui est en cours d'utilisation.") 339 print("pyFC.py -setkey [nomDuJeuDeClés] --> pour sélectionner le jeu de clés à utiliser") 340 print("pyFC.py -removekey [nomDuJeuDeClés] --> pour supprimer définitivement le jeu de clés ainsi que son") 341 print(" fichier de configuration associé") 342 print(" Attention, [nomDuJeuDeClés] doit obligatoirement exister pour") 343 print(" les options -setkey et -removekey !") 344 print("pyFC.py -createkey [nomDuJeuDeClés] --> pour créer un nouveau jeu de clé ainsi que son fichier de") 345 print(" configuration. [nomDuJeuDeClés] ne devra pas déjà exister pour") 346 print(" que cette opération fonctionne !") 347 print() 348 349 350 def XOR2Arrays(bytesArray1, bytesArray2): 351 ''' Effectue un XOR byte à byte sur les 2 tableaux de bytes transmis, 352 puis retourne le bytearray qui en resulte ''' 353 return bytearray((a ^ b) for a,b in zip(bytesArray1, bytesArray2)) 354 355 def XOR6Arrays(inArray, bArray1, bArray2, bArray3, bArray4, bArray5): 356 ''' Effectue un XOR byte à byte sur les 6 tableaux de bytes transmis, 357 puis retourne le bytearray qui en resulte ''' 358 return bytearray((a^b^c^d^e^f) for a,b,c,d,e,f in zip(inArray, bArray1, bArray2, bArray3, bArray4, bArray5)) 359 360 361 362 def setCryptoBytes(mode, fileLength, offsetIntArray): 363 ''' Cette fonction permet de créer un tableau de bytes qui sera utilisé pour 364 le chiffrement/déchiffrement du fichier à traiter ''' 365 cryptoArray = [] 366 with open(keyFilepathList[0], 'rb') as currentKeyFile1: 367 if not 'decryptData' in mode: 368 currentKeyFile1.seek(10000 + 29*(fileLength % 9999)) 369 byteArrayOff11 = currentKeyFile1.read(4) 370 currentKeyFile1.seek(100000 + 1151*(fileLength % 5432)) 371 byteArrayOff52 = currentKeyFile1.read(4) 372 if not 'decryptOffset' in mode: 373 currentKeyFile1.seek(offsetIntArray[0]) 374 byteArrayData1 = currentKeyFile1.read(fileLength) 375 with open(keyFilepathList[1], 'rb') as currentKeyFile2: 376 if not 'decryptData' in mode: 377 currentKeyFile2.seek(20000 + 113*(fileLength % 8888)) 378 byteArrayOff12 = currentKeyFile2.read(4) 379 currentKeyFile2.seek(30000 + 229*(fileLength % 7777)) 380 byteArrayOff21 = currentKeyFile2.read(4) 381 if not 'decryptOffset' in mode: 382 currentKeyFile2.seek(offsetIntArray[1]) 383 byteArrayData2 = currentKeyFile2.read(fileLength) 384 with open(keyFilepathList[2], 'rb') as currentKeyFile3: 385 if not 'decryptData' in mode: 386 currentKeyFile3.seek(40000 + 349*(fileLength % 6666)) 387 byteArrayOff22 = currentKeyFile3.read(4) 388 currentKeyFile3.seek(50000 + 463*(fileLength % 5555)) 389 byteArrayOff31 = currentKeyFile3.read(4) 390 if not 'decryptOffset' in mode: 391 currentKeyFile3.seek(offsetIntArray[2]) 392 byteArrayData3 = currentKeyFile3.read(fileLength) 393 with open(keyFilepathList[3], 'rb') as currentKeyFile4: 394 if not 'decryptData' in mode: 395 currentKeyFile4.seek(60000 + 601*(fileLength % 4444)) 396 byteArrayOff32 = currentKeyFile4.read(4) 397 currentKeyFile4.seek(70000 + 733*(fileLength % 3333)) 398 byteArrayOff41 = currentKeyFile4.read(4) 399 if not 'decryptOffset' in mode: 400 currentKeyFile4.seek(offsetIntArray[3]) 401 byteArrayData4 = currentKeyFile4.read(fileLength) 402 with open(keyFilepathList[4], 'rb') as currentKeyFile5: 403 if not 'decryptData' in mode: 404 currentKeyFile5.seek(80000 + 863*(fileLength % 2222)) 405 byteArrayOff42 = currentKeyFile5.read(4) 406 currentKeyFile5.seek(90000 + 1013*(fileLength % 1111)) 407 byteArrayOff51 = currentKeyFile5.read(4) 408 if not 'decryptOffset' in mode: 409 currentKeyFile5.seek(offsetIntArray[4]) 410 byteArrayData5 = currentKeyFile5.read(fileLength) 411 412 if not 'decryptData' in mode: 413 cryptoArray.append(byteArrayOff11) 414 cryptoArray.append(byteArrayOff12) 415 cryptoArray.append(byteArrayOff21) 416 cryptoArray.append(byteArrayOff22) 417 cryptoArray.append(byteArrayOff31) 418 cryptoArray.append(byteArrayOff32) 419 cryptoArray.append(byteArrayOff41) 420 cryptoArray.append(byteArrayOff42) 421 cryptoArray.append(byteArrayOff51) 422 cryptoArray.append(byteArrayOff52) 423 if not 'decryptOffset' in mode: 424 cryptoArray.append(byteArrayData1) 425 cryptoArray.append(byteArrayData2) 426 cryptoArray.append(byteArrayData3) 427 cryptoArray.append(byteArrayData4) 428 cryptoArray.append(byteArrayData5) 429 return cryptoArray 430 431 432 433 def encrypt(xxxName, filesAsByteArray, fileIndex, nbSlices): 434 ''' Fonction qui permet de procéder au chiffrement du tableau de bytes 435 'filesAsByteArray' et de le sauver sous le nom xxxName ''' 436 setConfFileAccess(True) 437 setKeyfilesAccess(True) 438 offsetIntArray = [] 439 offsetBytesArray = [] 440 441 # ici, on créé le tableau d'offsets 442 # On aura toujours len(filesAsByteArray) < keyFileSize (donc < 100 MBytes) 443 for index in range(5): 444 intOffset = secrets.randbelow(100*(index+1)*(2**20)-len(filesAsByteArray)) 445 byte3 = intOffset >> 24 446 byte2 = (intOffset - byte3*(256**3)) >> 16 447 byte1 = (intOffset - byte3*(256**3) - byte2*(256**2)) >> 8 448 byte0 = (intOffset - byte3*(256**3) - byte2*(256**2) - byte1*256) 449 bytesList = [byte0, byte2, byte3, byte1] 450 offsetIntArray.append(intOffset) # offsetIntArray contient les offsets X au format integer normal 451 offsetBytesArray.append(bytearray(bytesList)) # offsetBytesArray contient les offsets X au format 0231 452 453 # On va créer le byteArray à partir duquel on va chiffrer les offsets et les données 454 cryptoArray = setCryptoBytes('encrypt', len(filesAsByteArray), offsetIntArray) 455 456 # On procède au chiffrement des offsets 457 headerXOffset = [] 458 headerXOffset.append(XOR2Arrays(XOR2Arrays(offsetBytesArray[0], cryptoArray[0]), cryptoArray[1])) 459 headerXOffset.append(XOR2Arrays(XOR2Arrays(offsetBytesArray[1], cryptoArray[2]), cryptoArray[3])) 460 headerXOffset.append(XOR2Arrays(XOR2Arrays(offsetBytesArray[2], cryptoArray[4]), cryptoArray[5])) 461 headerXOffset.append(XOR2Arrays(XOR2Arrays(offsetBytesArray[3], cryptoArray[6]), cryptoArray[7])) 462 headerXOffset.append(XOR2Arrays(XOR2Arrays(offsetBytesArray[4], cryptoArray[8]), cryptoArray[9])) 463 464 # On procède au chiffrement des données du fichier 465 print(" --> Chiffrement du bloc n° "+str(fileIndex+1)+"/"+str(nbSlices)) 466 finalData = XOR6Arrays(filesAsByteArray, cryptoArray[10], cryptoArray[11], cryptoArray[12], cryptoArray[13], cryptoArray[14]) 467 468 # On sauve le fichier chiffré 469 with open(xxxName, 'wb') as treatedFile: 470 for oneOffset in headerXOffset: 471 treatedFile.write(oneOffset) 472 treatedFile.write(finalData) 473 474 setConfFileAccess(True) 475 setKeyfilesAccess(False) 476 setConfFileAccess(False) 477 478 479 480 def addDirToZip(zipHandle, path, basePath=""): 481 ''' On va ici placer dans l'archive zipHandle tout le contenu du répertoire path ''' 482 basePath = basePath.rstrip("\\/") + "" 483 basePath = basePath.rstrip("\\/") 484 for root, dirs, files in os.walk(path): 485 # add dir itself (needed for empty dirs) 486 zipHandle.write(os.path.join(root, ".")) 487 # add files 488 for oneFile in files: 489 filePath = os.path.join(root, oneFile) 490 inZipPath = filePath.replace(basePath, "", 1).lstrip("\\/") 491 zipHandle.write(filePath, inZipPath) 492 493 494 495 def setSHA512(oneString, power=1): 496 ''' Retournera le sha512 de degré 'power' de la chaine oneString ''' 497 theHashed = oneString 498 n=0 499 while (n<power): 500 theHashed = hashlib.sha512(theHashed.encode('utf-8')).hexdigest() 501 n += 1 502 return theHashed 503 504 505 506 def sliceAndEncrypt(filePath): 507 ''' Pour simplifier les choses au niveau de la fonction de chiffrement (encrypt(filePath)), on impose 508 que pour tout fichier à chiffrer, la taille doit être inférieure à celle de la clé de chiffement 509 la plus petite, soit 100MBytes (100 * 2**20). Si le fichier à chiffrer est de taille supérieure, 510 on va alors ici le découper en plusieurs tranches de tailles ~ équivalentes et inférieures à cette 511 limite de 100 MBytes. On chiffrera ensuite l'ensemble des fichiers obtenus 512 La variable limitSize (voir un peu plus bas après la ligne 'if proceed:') contiendra la limite de 513 taille réellement choisie ''' 514 515 print('*') 516 print('*') 517 pathToScript = os.path.dirname(os.path.realpath(sys.argv[0])) 518 finalPDFName = "" 519 proceed = False 520 # Si filePath pointe vers un fichier, on va mettre ce fichier dans une archive zip avant le chiffrement 521 # cela permettra de ne pas avoir à se préoccuper du nom du fichier original (qui sera contenu dans le zip) 522 if os.path.isfile(filePath): 523 # On va créer le zip dans le répertoire courant (currentDir) 524 # Attention à bien avoir les droits d'écriture dans ce répertoire ! 525 finalPDFName = filePath.split('/')[-1] # Dans l'archive, on ne mettra que le nom du fichier, sans son path 526 print("**** Chiffrement de " + finalPDFName) 527 print('*') 528 print(" Étape 1 : Création de l'archive temporaire") 529 zipHandle = zipfile.ZipFile('precrypted.zip', 'w', zipfile.ZIP_DEFLATED) 530 zipHandle.write(finalPDFName) 531 zipHandle.close() 532 finalPDFName = finalPDFName.split('.')[0] # On enlève l'extension du fichier d'origine 533 # pour ne garder que son nom 534 proceed = True 535 536 # Si filePath pointe vers un répertoire, on va le zipper afin de pouvoir le traiter comme un fichier 537 if os.path.isdir(filePath): 538 # On va créer le zip dans le répertoire courant (currentDir) 539 # Attention à bien avoir les droits d'écriture dans ce répertoire ! 540 zipHandle = zipfile.ZipFile('precrypted.zip', 'w', zipfile.ZIP_DEFLATED) 541 savedCurrentDir = currentDir 542 repList = filePath.split('/') 543 fRepList = [] 544 repToGo = '' 545 proceed = True 546 547 # Si l'on veut par exemple crypter le répertoire /home/toto/Documents , on va se placer dans /home/toto de 548 # manière à se trouver à un étage 'supérieur' au répertoire 'Documents'. Ainsi, on pourra plus facilement 549 # inclure ce nom de répertoire (et uniquement lui) dans l'archive zip, qui contiendra alors le répertoire 550 #'/Documents' dans lequel on retrouvera tous ses fichiers et sous-répertoires 551 for nStr in repList: 552 if len(nStr)>0: 553 fRepList.append(nStr) 554 finalPDFName = nStr # Selon notre exemple, finalPDFName contiendrait 'Documents' 555 556 if len(fRepList) == 0: 557 proceed = False 558 print4("Erreur, vous ne pouvez pas chiffrer l'ensemble de la racine (/) ! \r\n \ 559 Sélectionnez un répertoire existant !") 560 561 if len(fRepList) == 1: 562 repToGo = '/' 563 os.chdir(repToGo) 564 print("**** Chiffrement de " +filePath) 565 print('*') 566 print(" Étape 1 : Création de l'archive principale") 567 addDirToZip(zipHandle, finalPDFName) 568 zipHandle.close() 569 os.chdir(savedCurrentDir) 570 571 if len(fRepList)>1: 572 del fRepList[-1] 573 for oneDir in fRepList: 574 repToGo += '/' + oneDir 575 os.chdir(repToGo) # Selon notre exemple, on remonte ici dans le répertoire '/home/toto' 576 print("**** Chiffrement de " +filePath) 577 print('*') 578 print(" Étape 1 : Création de l'archive principale") 579 addDirToZip(zipHandle, finalPDFName) # Ici, on va ajouter à l'archive zip tout le 580 zipHandle.close() # contenu de 'Documents' vu de '/home/toto' 581 os.chdir(savedCurrentDir) # On retourne ici dans le répertoire d'où l'on a appelé le script 582 583 if proceed : 584 print() 585 print(" Étape 2 : Découpe et Chiffrement :") 586 filePath = currentDir+'/precrypted.zip' 587 fileSize = os.path.getsize(filePath) 588 589 # On va charger le fichier à traiter, puis le découper en byteArray(s) de taille < 100MBytes si besoin 590 # On a le choix ici de la limite de taille des byteArrays (doit être impérativement < 100MBytes) 591 # Plus on abaisse cette limite, plus le chiffrement sera rapide pour les fichiers de taille supérieure 592 # En contrepartie, on aura un chiffrement en plusieurs étapes (autant que de tranches, après découpe 593 # du fichier precrypted.zip) 594 limitSize = (3+int(fileSize/(15*(2**20)))) * (2**20) 595 if limitSize > (50*(2**20)): 596 limitSize = 50*(2**20) 597 if fileSize < limitSize: 598 nbSlices = 1 599 rawSliceSize = fileSize 600 else: 601 nbSlices = int(fileSize/limitSize) + 1 602 rawSliceSize = int(fileSize/nbSlices) + 1 603 604 filesAsByteArray = [] 605 with open(filePath, 'rb') as treatedFile: # On vient ici lire le fichier "precrypted.zip" 606 for n in range(nbSlices): 607 treatedFile.seek(n*rawSliceSize) 608 filesAsByteArray.append(treatedFile.read(rawSliceSize)) 609 610 # On va préparer la ligne de commande de création de l'archive zip pour l'étape suivante 611 # (voir ligne "os.system(systemCommand)" 20 lignes plus bas) 612 # On va au préalable chiffrer chaque byteArray, puis le sauver en tant que fichier chiffré 613 firstPassWd = setSHA512((finalPDFName+'.pdf'), 14) 614 systemCommand = 'zip -e -q -9 -P' + firstPassWd + ' "' + finalPDFName+'.pdf" ' 615 namePart = str(int(time.time())) 616 for n in range(nbSlices): 617 fileExt = ".00" 618 if 9 < n < 100: 619 fileExt = ".0" 620 if 1000 > n > 99: 621 fileExt = "." 622 xxxName = currentDir + '/' + namePart + fileExt + str(n) 623 encrypt(xxxName, filesAsByteArray[n], n, nbSlices) 624 systemCommand += ('"' + namePart + fileExt + str(n) + '" ') 625 626 # On va intégrer chaque fichier chiffré dans l'archive zip créée ci-dessus que l'on renommera selon 627 # le nom du fichier/répertoire à chiffrer passé en paramètre en y ajoutant l'extension '.pdf' 628 print() 629 print(" Étape 3 : Intégration des blocs chiffrés dans l'archive de niveau 1") 630 if os.path.isfile(finalPDFName+'.pdf'): 631 os.remove(finalPDFName+'.pdf') # On efface le fichier _P.pdf si il existe déjà 632 os.system(systemCommand) 633 634 # On efface les blocs chiffrés (en ".000", ".001", ".002", etc...) 635 for n in range(nbSlices): 636 fileExt = ".00" 637 if 9 < n < 100: 638 fileExt = ".0" 639 if 1000 > n > 99: 640 fileExt = "." 641 xxxName = currentDir + '/' + namePart + fileExt + str(n) 642 os.remove(xxxName) 643 644 # On intègre maintenant l'archive précédente (avec extension .pdf) dans une archive zip 645 secondPassWd = setSHA512((finalPDFName+'.zip'), 23) 646 systemCommand = 'zip -e -q -9 -P' + secondPassWd + ' "' + finalPDFName+'.zip"' + ' "'+ finalPDFName+'.pdf"' 647 print(" Étape 4 : Intégration de l'archive de niveau 1 dans archive de niveau 2") 648 os.system(systemCommand) 649 650 # On supprime l'archive précédente (avec extension .pdf) 651 os.remove(finalPDFName+'.pdf') 652 653 # On mixe la partie pdf réelle (message NothingToHide) avec la partie chiffrée pour faire un vrai faux pdf 654 print(" Étape 5 : Intégration de l'archive de niveau 2 dans le pdf final") 655 finalByteArray = [] 656 with open(pathToScript+'/pdfroot.bytes', 'rb') as finalFile: # On vient ici lire le fichier "pdfroot.bytes" 657 finalByteArray.append(finalFile.read()) 658 with open(finalPDFName+'.zip', 'rb') as finalFile: # On vient ici lire le fichier "_P.zip" 659 finalByteArray.append(finalFile.read()) 660 with open(finalPDFName+'.pdf', 'wb') as finalFile: # On vient ici écrire le faux fichier pdf "_P.pdf" 661 finalFile.write(finalByteArray[0]) # On écrit la partie pdf réelle 662 finalFile.write(finalByteArray[1]) # On y ajoute l'archive contenant toute la partie chiffrée 663 664 # On supprime l'archive zip qui n'est plus utile 665 os.remove(finalPDFName+'.zip') 666 print() 667 print(" Chiffrement terminé -> Création du fichier " + finalPDFName+'.pdf') 668 print() 669 670 # On va supprimer le fichier precrypted.zip qui n'est plus utile 671 os.remove(filePath) 672 673 674 675 def decrypt(filePath, nbSlices): 676 ''' Fonction qui permet de procéder au déchiffrement du fichier filePath ''' 677 if os.path.isfile(filePath): # Si filePath pointe bien vers un fichier 678 setConfFileAccess(True) 679 setKeyfilesAccess(True) 680 usedLength = os.path.getsize(filePath) - 20 # On retire les 20 octets d'entête qui ont été 681 headerXOffset = [] # ajouté lors du chiffrement 682 683 # On va reconstituer le byteArray à partir duquel on a chiffré les offsets 684 offsetIntArray = 0 685 cryptoArray = setCryptoBytes('decryptOffset', usedLength, offsetIntArray) 686 687 # Lecture et déchiffrement 1/2 des 5 offsets chiffrés 688 with open(filePath, 'rb') as cryptedFile: 689 off1 = cryptedFile.read(4) 690 headerXOffset.append(XOR2Arrays(XOR2Arrays(off1, cryptoArray[0]), cryptoArray[1])) 691 cryptedFile.seek(4) 692 off2 = cryptedFile.read(4) 693 headerXOffset.append(XOR2Arrays(XOR2Arrays(off2, cryptoArray[2]), cryptoArray[3])) 694 cryptedFile.seek(8) 695 off3 = cryptedFile.read(4) 696 headerXOffset.append(XOR2Arrays(XOR2Arrays(off3, cryptoArray[4]), cryptoArray[5])) 697 cryptedFile.seek(12) 698 off4 = cryptedFile.read(4) 699 headerXOffset.append(XOR2Arrays(XOR2Arrays(off4, cryptoArray[6]), cryptoArray[7])) 700 cryptedFile.seek(16) 701 off5 = cryptedFile.read(4) 702 headerXOffset.append(XOR2Arrays(XOR2Arrays(off5, cryptoArray[8]), cryptoArray[9])) 703 cryptedFile.seek(20) 704 cypherData = cryptedFile.read(usedLength) 705 706 # Reconstitution des offsets (Déchiffrement 2/2) 707 intOffset = [] 708 for oneHeaderOffset in headerXOffset: 709 byte0 = oneHeaderOffset[0] 710 byte2 = oneHeaderOffset[1] 711 byte3 = oneHeaderOffset[2] 712 byte1 = oneHeaderOffset[3] 713 intOffset.append(int(byte3*(256**3) + byte2*(256**2)+ byte1*256 + byte0)) 714 715 # On va reconstituer le byteArray à partir duquel on a chiffré les données 716 cryptoArray = setCryptoBytes('decryptData', usedLength, intOffset) 717 718 # On procède au déchiffrement des données 719 fileExt = filePath.split('.')[-1] 720 intFileExt = int(fileExt) 721 print(" --> Déchiffrement du bloc n° "+str(intFileExt+1)+"/"+str(nbSlices)) 722 finalData = XOR6Arrays(cypherData, cryptoArray[4], cryptoArray[3], cryptoArray[2], cryptoArray[1], cryptoArray[0]) 723 724 # On sauve le fichier déchiffré 725 with open(currentDir+'/decypher.'+fileExt, 'wb') as treatedFile: 726 treatedFile.write(finalData) 727 728 setConfFileAccess(True) 729 setKeyfilesAccess(False) 730 setConfFileAccess(False) 731 return(currentDir+'/decypher.'+fileExt) # On va retourner le nom du fichier déchiffré, 732 # afin de pouvoir le ré-assembler si besoin 733 734 735 736 def getSlicesAndDecrypt(filePath): 737 ''' On va déterminer ici en combien de tranches le fichier original a été découpé avant chiffrement. 738 On devra déchiffrer chaque tranche, puis ré-assembler le tout pour reconstituer l'archive originale. 739 Dans un dernier temps, on pourra enfin décompresser l'archive ''' 740 741 if os.path.exists(filePath): 742 pathToScript = os.path.dirname(os.path.realpath(sys.argv[0])) 743 pdfrootLength = os.path.getsize(pathToScript+'/pdfroot.bytes') 744 simpleName = filePath.split('/')[-1] 745 simpleName = simpleName.split('.')[0] # On récupère le nom du fichier, sans chemine ni extension 746 print('*') 747 print('*') 748 print("**** Déchiffrement de " +filePath) 749 repList = filePath.split('.') 750 if 'pdf' in repList[-1]: # On s'assure que le fichier à déchiffrer possède bien une extension en .pdf 751 752 # On renomme le .pdf en _P.orig afin de ne pas l'écraser avant déchiffrage complet 753 os.rename(currentDir+'/'+simpleName+'.pdf', currentDir+'/'+simpleName+'.orig') 754 755 tmpArray = 0 756 # On lit le fichier final .pdf 757 print('*') 758 print(" Étape 1 : Extraction de l'archive de niveau 1") 759 with open(currentDir+'/'+simpleName+'.orig', 'rb') as pdfContainer: 760 pdfContainer.seek(pdfrootLength) 761 tmpArray = pdfContainer.read() 762 # On en extrait l'archive zip en retirant l'entête "faux pdf" 763 with open(currentDir+'/'+simpleName+'.zip', 'wb') as zipContainer: 764 zipContainer.write(tmpArray) 765 766 # De cette première archive zip, on va extraire le fichier .pdf qui est une autre archive zip masquée 767 print(" Étape 2 : Extraction de l'archive de niveau 2") 768 secondPassWd = setSHA512((simpleName+'.zip'), 23) 769 systemCommand = 'unzip -qq -P ' + secondPassWd + ' "' +currentDir+'/'+simpleName+'.zip"' 770 os.system(systemCommand) 771 772 # On supprime la première archive zip 773 os.remove(currentDir+'/'+simpleName+'.zip') 774 775 # Puis on renomme le .pdf que l'on vient d'extraire en .zip pour pouvoir le décompresser 776 zipValide = False 777 try: 778 os.rename(currentDir+'/'+simpleName+'.pdf', currentDir+'/'+simpleName+'.zip') 779 zipValide = True 780 781 except: 782 # On renomme le fichier d'origine .orig 783 os.rename(currentDir+'/'+simpleName+'.orig', currentDir+'/'+simpleName+'.pdf') 784 print() 785 print(" --> Le fichier "+filePath+" ne semble pas être un fichier chiffré valide !") 786 print() 787 time.sleep(1) 788 789 if zipValide: 790 print() 791 print(" Étape 3 : Extraction des blocs chiffrés") 792 # De cette seconde archive zip, on va extraire les fichiers en "000", "001", etc 793 tempZip = zipfile.ZipFile(currentDir+'/'+simpleName+'.zip', 'r') 794 myCypherList = tempZip.namelist() 795 firstPassWd = setSHA512((simpleName+'.pdf'), 14) 796 systemCommand = 'unzip -q -P ' + firstPassWd + ' "' +currentDir+'/'+simpleName+'.zip"' 797 os.system(systemCommand) 798 799 # On supprime la seconde archive zip 800 os.remove(currentDir+'/'+simpleName+'.zip') 801 802 # Partie finale 803 decypheredList = [] 804 decypheredByteArray = [] 805 for oneCryptedSlice in myCypherList: 806 decypheredList.append(decrypt(currentDir+'/'+oneCryptedSlice, len(myCypherList))) 807 os.remove(oneCryptedSlice) 808 for oneFile in decypheredList: 809 with open(oneFile, 'rb') as decypheredSlice: 810 tmpArray = decypheredSlice.read() 811 decypheredByteArray.append(tmpArray) 812 os.remove(oneFile) 813 print() 814 print(" Étape 4 : Reconstitution de l'archive originelle") 815 with open('finalFile.zip', 'wb') as finalFile: #### Tester AP ici !!! 816 for oneByteArray in decypheredByteArray: 817 finalFile.write(oneByteArray) 818 819 try: 820 finalZip = zipfile.ZipFile('finalFile.zip', 'r') # On créé le dernier zip qui sera le 821 # réassemblage des blocs cypher (si clé correcte) 822 try: 823 rootArcName = finalZip.namelist()[0] # On vient chercher le nom du répertoire racine contenu 824 # dans finalFile.zip 825 finalZip.extractall(currentDir) 826 try: 827 os.remove('finalFile.zip') 828 829 # On supprime le fichier d'origine _P.orig 830 os.remove(currentDir+'/'+simpleName+'.orig') 831 832 print() 833 print(" Extraction / création de " +rootArcName+ " --> Déchiffrement terminé") 834 print() 835 836 except: 837 print(" --> Fichier 'finalFile.zip' déjà supprimé") 838 839 except: 840 print("L'archive semble vide...") 841 print() 842 843 except: 844 print(" --> Blocs indéchiffrables, vous n'avez probablement pas la bonne clé !") 845 print() 846 os.remove('finalFile.zip') 847 848 # On renomme le fichier d'origine _P.orig 849 os.rename(currentDir+'/'+simpleName+'.orig', currentDir+'/'+simpleName+'.pdf') 850 851 time.sleep(1) 852 853 else: 854 print4(filePath+" n'est pas un fichier valide pour déchiffrement !") 855 856 857 858 859 ###################### Lancement du programme principal ###################### 860 # 861 if __name__ == "__main__": 862 startTime = int(time.time()) 863 version = '0.2' 864 pfcDir = os.path.expanduser('~')+'/.pyFC' 865 pfcConfFile = pfcDir+'/.pyFC.conf' 866 currentDir = os.getcwd() 867 currentKeyName = '' 868 keyFilepathList = [] 869 waitForKeyboard = False 870 if len(sys.argv) == 1: 871 printUsage() 872 if len(sys.argv) == 2: 873 foundBit2 = False 874 if '-v' in (sys.argv[1]): 875 foundBit2 = True 876 print4("pyFC Version 0.2") 877 if '-help' in (sys.argv[1]): 878 foundBit2 = True 879 printUsage() 880 if '-init' in (sys.argv[1]): 881 foundBit2 = True 882 print("* Cette opération va générer un nouveau jeu de clés de chiffrement.") 883 strOK = input("* Voulez-vous vraiment continuer ? (Abandon si différent de OK) : ") 884 if len(strOK)==2 and 'OK' in strOK: 885 keyList = readKeyList() 886 keyNameAlreadyExists = False 887 for oneKey in keyList: 888 if 'default' in oneKey.split('.')[-2] and oneKey.split('.')[-2] in 'default': 889 keyNameAlreadyExists = True 890 print('************************************************') 891 print('*') 892 print("* La clé 'default' existe déjà ! Pour créer un nouveau jeu de clés, utilisez") 893 print("* un nom qui ne figure pas déjà dans la liste des clés ci-dessous :") 894 keyList = readKeyList() 895 for oneKey in keyList: 896 if not '.pyFC.conf' in oneKey: 897 print('* --> '+oneKey.split('.')[-2]) 898 print('*') 899 if not keyNameAlreadyExists: 900 print("* Création du jeu de clés 'default'") 901 createKeyFiles('default') 902 else: 903 print4("Abandon...") 904 if '-showconf' in (sys.argv[1]): 905 foundBit2 = True 906 readConfFile() 907 setConfFileAccess(True) 908 setKeyfilesAccess(True) 909 print4("Configuration et arborescence de clés accessibles.") 910 if '-hideconf' in (sys.argv[1]): 911 foundBit2 = True 912 readConfFile() 913 setConfFileAccess(True) 914 setKeyfilesAccess(False) 915 setConfFileAccess(False) 916 print4("Configuration et arborescence de clés protégées.") 917 if '-showkeyinfo' in (sys.argv[1]): 918 foundBit2 = True 919 showkeyinfo() 920 if not foundBit2: # Si le paramètre passé n'existe pas 921 printUsage() 922 if len(sys.argv) == 3: 923 cmdOK = False 924 badFilePath = False 925 926 # Encryption ou Decryption 927 if '-e' in (sys.argv[1]) or '-d' in (sys.argv[1]): 928 cmdOK = True 929 badFilePath = True 930 if len(sys.argv[2]) > 1: 931 if os.path.exists(sys.argv[2]): # On va ici traiter le chiffrement ou le déchiffrement 932 badFilePath = False 933 readConfFile() # On lit le fichier conf pour accéder aux chemins des fichiers clés 934 if '-e' in (sys.argv[1]): 935 sliceAndEncrypt(sys.argv[2]) 936 if '-d' in (sys.argv[1]): 937 getSlicesAndDecrypt(sys.argv[2]) 938 if badFilePath: # Erreur sur chemin vers fichier ou répertoire passé en argument 939 print4("Erreur : la chaine à traiter n'existe pas, n'est ni un fichier ni un répertoire !") 940 941 # Gestion des clés 942 if '-createkey' in (sys.argv[1]): # Pour créer un nouveau jeu de clés 943 cmdOK = True 944 if 'pyFC' in sys.argv[2]: # 'pyFC' interdit comme nom de jeu de clé ! (nom de fichier réservé !!!) 945 print4("'pyFC' est un nom réservé, utilisez un autre nom pour un nouveau jeu de clé !") 946 else: 947 948 print("* Cette opération va générer un nouveau jeu de clés de chiffrement.") 949 strOK = input("* Voulez-vous vraiment continuer ? (Abandon si différent de OK) : ") 950 if len(strOK)==2 and 'OK' in strOK: 951 952 keyList = readKeyList() 953 keyNameAlreadyExists = False 954 for oneKey in keyList: 955 if sys.argv[2] in oneKey.split('.')[-2] and oneKey.split('.')[-2] in sys.argv[2]: 956 keyNameAlreadyExists = True 957 print('************************************************') 958 print('*') 959 print("* Cette clé existe déjà ! Pour créer un nouveau jeu de clés, utilisez") 960 print("* un nom qui ne figure pas déjà dans la liste des clés ci-dessous :") 961 keyList = readKeyList() 962 for oneKey in keyList: 963 if not '.pyFC.conf' in oneKey: 964 print('* --> '+oneKey.split('.')[-2]) 965 print('*') 966 if not keyNameAlreadyExists: 967 print("* Création du nouveau jeu de clés :", sys.argv[2]) 968 createKeyFiles(sys.argv[2]) 969 970 if '-setkey' in (sys.argv[1]) or '-removekey' in (sys.argv[1]): # Pour sélectionner ou supprimer une clé 971 cmdOK = True 972 foundKey = False 973 keyList = readKeyList() 974 for oneKey in keyList: 975 # On vérifie que la clé passée en paramètre existe bien 976 if sys.argv[2] in oneKey.split('.')[-2] and oneKey.split('.')[-2] in sys.argv[2]: 977 foundKey = True 978 if '-setkey' in (sys.argv[1]): 979 setkey(sys.argv[2]) 980 showkeyinfo() 981 if '-removekey' in (sys.argv[1]): 982 removekey(sys.argv[2]) 983 showkeyinfo() 984 if not foundKey: 985 print4(" Erreur de clé : pour cette opération, vous devez passer le nom d'une clé existante !") 986 showkeyinfo() 987 if not cmdOK: 988 print4("Erreur : commande incompréhensible !") # Erreur sur argument -e, -d, -showkeyinfo, etc... 989 printUsage() 990 991 if len(sys.argv) == 4: 992 ed = False 993 badFilePath = False 994 if '-waitForKeyboard' in (sys.argv[1]): 995 waitForKeyboard = True 996 if '-e' in (sys.argv[2]) or '-d' in (sys.argv[2]): 997 ed = True 998 if len(sys.argv[3]) > 1: 999 if os.path.exists(sys.argv[3]): # On va ici traiter le chiffrement ou le déchiffrement 1000 readConfFile() # On lit le fichier conf pour accéder aux chemins des fichiers clés 1001 if '-e' in (sys.argv[2]): 1002 sliceAndEncrypt(sys.argv[3]) 1003 if '-d' in (sys.argv[2]): 1004 getSlicesAndDecrypt(sys.argv[3]) 1005 else: 1006 badFilePath = True 1007 if not ed: 1008 # Erreur sur argument -e ou -d 1009 print4("Erreur : commande incompréhensible !") 1010 printUsage() 1011 if badFilePath: 1012 # Erreur sur chemin vers fichier ou répertoire passé en argument 1013 print4("Erreur : la chaine à traiter n'existe pas, n'est ni un fichier ni un répertoire !") 1014 else: 1015 print4("Erreur : commande incompréhensible !") 1016 printUsage() 1017 if len(sys.argv) > 4: # Trop de paramètres passés en argument ! 1018 print4("Erreur : trop de paramètres passés en argument !") 1019 printUsage() 1020 unitStr = ' secondes' 1021 valTime = int(1+time.time())-startTime 1022 if valTime < 2: 1023 unitStr = ' seconde' 1024 mess = " Script exécuté en "+str(valTime)+(unitStr) 1025 print(mess) 1026 print() 1027 print() 1028 print() 1029 if waitForKeyboard: 1030 finish = input("Appuyez sur [ENTER] pour fermer ce terminal...") 1031