• pyFC - Nouvelle version 0.2 - "Nothing to Hide"

     

     

    J'ai publié assez récemment un article sur une première mouture de mon script pyFC.py (voir ici) dédié au chiffrement des données. J'ai dans cette nouvelle version 0.2 corrigé quelques défauts de jeunesse, notamment le fait que le chiffrement pouvait produire en sortie plusieurs fichiers, ce qui n'est pas forcément facile pour s'y retrouver. De plus, les noms de ces fichiers étaient définis à partir du 'Unix timestamp' relevé au moment du chiffrement, ce qui n'aide pas non plus à savoir ce que contiennent les blocs chiffrés. Dans cette nouvelle version, le fichier final produit n'est plus un ensemble de blocs chiffrés, mais un seul "vrai-faux" fichier pdf qui portera le nom soit du fichier, soit du répertoire que l'on vient de traiter.  

    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.

    Copie d'écran pyFC -createkey

    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

    Copie d'écran de l'opération pyFC -setkey

     

    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

    Copie d'écran opération pyFC -removekey

     

    Enfin, vous pouvez vérifier la configuration des clés avec : 

     you@yourComputer:~/pyFC$ python3 pyFC.py -showkeyinfo

    Copie d'écran opération pyFC -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"

    Copie d'écran pyFC -e

     

    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

     Copie d'écran pyFC -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 
    

    Tags Tags : , , , , , ,
  • Commentaires

    Aucun commentaire pour le moment

    Suivre le flux RSS des commentaires


    Ajouter un commentaire

    Nom / Pseudo :

    E-mail (facultatif) :

    Site Web (facultatif) :

    Commentaire :