Eklablog Tous les blogs
Editer l'article Suivre ce blog Administration + Créer mon blog
MENU

Blog traitant d'informatique, de programmation, de python, de golang, de linux...

Publicité

pyFC - Nouvelle version 0.2 - "Nothing to Hide"

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 
Publicité
Retour à l'accueil
Partager cet article
Repost0
Pour être informé des derniers articles, inscrivez vous :
Commenter cet article