C++ : WinTD 11 Cryptage de fichiers

Transcription

C++ : WinTD 11 Cryptage de fichiers
Centre Informatique pour les Lettres
et les Sciences Humaines
C++ : WinTD 11
Cryptage de fichiers
1 - Le cahier des charges ........................................................................................................2
2 - Quelques indices ...............................................................................................................2
Principes généraux ................................................................................................... 2
La fonction OnButtonChanger() .............................................................................. 3
3 - Exercice complémentaire ...................................................................................................4
Document du 20/09/02 - Retrouvez la version la plus récente sur http://www.up.univ-mrs.fr/wcpp/wintd.htm
C++ - WinTD 11
Cryptage de fichiers
2/4
Au cours du TD 5, nous avons mis au point un programme capable d'effectuer un cryptage de
texte par substitution de caractères. Depuis lors, la panoplie de techniques dont nous
disposons s'est considérablement accrue, et le TD 11 va être une occasion de réviser certaines
des notions introduites dans les Leçons 6 à 10, tout en produisant une version améliorée de
notre logiciel de cryptage. Par ailleurs,
Le TD 12 utilise des données produites par exécution du programme qui fait l'objet du TD 11.
Il faut donc que vous parveniez, d'une façon ou d'une autre, à obtenir un programme en état
de marche…
1 - Le cahier des charges
Par rapport aux fonctionnalités offertes par le programme du TD 5, notre projet actuel ne se
distingue guère que par quelques exigences supplémentaires :
- Le texte devant être crypté (ou décrypté) n'est pas saisi par l'utilisateur dans une EditBox,
mais est contenu dans un fichier que l'utilisateur doit pouvoir désigner. De même, le texte
crypté (ou décrypté) ne doit pas être affiché dans une EditBox, mais enregistré dans un fichier
dont l'utilisateur doit pouvoir choisir le nom et l'emplacement.
- Le code secret qui est utilisé pour le cryptage et le décryptage ne doit pas être toujours le
même, mais doit pouvoir être modifié par l'utilisateur.
- Le programme doit être capable de traiter tous les signes qui peuvent être rencontrés dans un
fichier texte (le programme du TD 5 ne prenait en compte que les lettres majuscules).
2 - Quelques indices
Les indications données ici ne sont que des suggestions fournies dans l'espoir d'aider ceux
d'entre vous qui éprouveraient quelques difficultés à réaliser ce projet. Il serait sans doute
préférable que vous parveniez à créer sans aide un programme (même s'il est un peu bancal)
qui effectue le traitement demandé, plutôt que de rester dépendant des indices donnés ici
(même s'ils peuvent vous permettre d'écrire tout de suite un programme plus "propre").
Principes généraux
L'interface de ma version du "crypteur de fichier" propose quatre boutons :
L'interface de ma version du "Crypteur de fichier"
Le bouton "Ouvrir…" déclenche l'apparition d'un dialogue d'ouverture de fichier (cf. l'usage
d'un CFileDialog dans le TD 8). Ce dialogue permet à l'utilisateur de désigner le fichier qui
doit être traité. Le fichier en question est associé à une variable de type ifstream, nommée
m_fichierSource, qui est un des membres de CMonDialogue.
Le bouton "Changer" est associé à la seule fonction du programme dont l'écriture présente une
certaine difficulté. Le rôle de cette fonction est d'attribuer des valeurs aux éléments de deux
tableaux de 256 unsigned char (respectivement nommés code et decode), qui jouent ici un rôle
analogue à celui joué, dans le TD 5, par les CString m_alphabetNormal et
m_alphabetSecret. Cette fonction est donc appelée depuis OnInitDialog(), de façon à ce
que le programme dispose immédiatement de valeurs pouvant être utilisées pour effectuer un
J-L Péris - 20/09/02
C++ - WinTD 11
Cryptage de fichiers
3/4
cryptage. Ces valeurs sont obtenues par un tirage aléatoire, et l'utilisateur peut donc changer
le code secret utilisé en cliquant sur le bouton1.
Dans ma version du programme, l'utilisateur dispose d'une évocation visuelle du tirage
effectué : les premiers caractères visibles du tableau de codage sont affichés à gauche du
bouton "Changer". Cette fonctionnalité est tout à fait anecdotique, et votre version du
programme peut parfaitement s'en dispenser.
L'analyse et le codage de cette fonction sont détaillés ci-dessous.
Les boutons "Crypter" et "Décrypter" réalisent essentiellement les mêmes opérations :
- Il faut tout d'abord ouvrir un dialogue de sauvegarde (cf. l'usage d'un CFileDialog dans le
TD 9). Le fichier spécifié par l'utilisateur est associé à une variable de type ofstream,
nommée m_fichierProduit, qui est un des membres de CMonDialogue.
- Il faut ensuite lire le fichierSource, traiter chacun de ses caractères (codage ou décodage)
et écrire le résultat dans le fichierProduit. La logique de lecture/écriture peut s'inspirer
d'une des deux méthodes utilisées dans le TD 9. La logique du codage/décodage, pour sa
part, découle directement de la façon dont sont conçus les tableaux code et decode. En cas
de besoin, le paragraphe suivant devrait donc vous aider à mieux la percevoir.
La fonction OnButtonChanger()
Une des contraintes imposées par le cahier des charges est que notre programme doit être
capable de crypter tous les caractères susceptibles d'apparaître dans un fichier texte. Certains
octets ne correspondent normalement à aucun caractère ayant une représentation visuelle, et
ces octets ne devraient donc a priori jamais figurer dans un fichier texte. Toutefois, la prise en
compte de cette réflexion ne conduit qu'à compliquer l'écriture du programme et à le rendre
vulnérable aux anomalies qui pourraient apparaître dans un fichier de données. Il est donc
largement préférable de considérer que les 256 valeurs possibles pour un char sont
susceptibles d'être rencontrées dans le fichierSource.
Du point de vue de l'écriture du programme, la prise en compte de toutes les valeurs possibles
pour un char représente en fait une simplification. En effet, lorsqu'on se limite à un sousensemble des valeurs possibles (comme nous l'avons fait dans le TD 5), la traduction d'un
caractère nécessite de rechercher ce caractère dans l'un des tableaux, de façon à utiliser
comme traduction le caractère qui occupe la même position dans l'autre tableau. Lorsque tous
les caractères sont présents dans les tableaux, la phase de recherche devient inutile : la
position de la traduction d'un caractère est donnée directement par la valeur du caractère luimême. Ainsi, par exemple, le cryptage d'un 'A' sera réalisé à l'aide du caractère occupant la
position 'A' dans le tableau code. Le décryptage nécessite donc que ce caractère corresponde à
la position du 'A' dans le tableau decode. Ainsi, si le 'A' doit être remplacé par un '#' lors du
cryptage, il faut que l'élément d'indice 'A' dans le tableau code contienne un "#".
Réciproquement, pour que le '#' soit remplacé par un 'A' lors du décryptage, il faut évidemment
que l'élément d'indice '#' du tableau decode contienne un 'A'.
Le fait que (dans l'environnement où nous opérons) la valeur 'A' peut également être
représentée par 65 alors que la valeur '#' peut également être représentée par 35 est sans
intérêt pratique, mais permet de comprendre pourquoi on peut légitimement parler de
l'élément d'un tableau dont l'indice est 'A' ou de celui dont l'indice est '#'.
Comme dans le TD 5, il nous faut toutefois prendre garde à un détail essentiel : pour Visual
C++, le type char est un type signé, ce qui signifie que les différents patterns de bits possibles
pour un octet seront interprétés comme des valeurs de l'intervalle [-128, 127]. S'il est stocké
dans une variable de type char, le caractère 'é', qui est représenté en mémoire par
sera interprété comme valant -23, et non 233. Cette nuance n'a d'ordinaire
aucune importance, mais nous envisageons ici de considérer les caractères comme
représentant des index permettant d'accéder aux éléments de tableaux. Or il est clair qu'un
index négatif ne permet normalement pas d'accéder à un élément d'un tableau.
1 Bien entendu, une fois que le code a été changé, le programme n'est plus capable de décrypter les fichiers sur lequel
l'ancien code a été appliqué ! C'est ce qui fait tout le sel du TP 12…
J-L Péris - 20/09/02
C++ - WinTD 11
Cryptage de fichiers
4/4
Rappel : en dehors du contexte de l'application de l'opérateur sizeof, le nom d'un tableau
est équivalent à l'adresse de son premier élément. Les crochets ne sont qu'une façon de
déréférencer ce pointeur après lui avoir ajouté l'indice qu'ils encadrent. Si cet indice est
négatif, on accède donc à une zone de mémoire dont l'adresse est inférieure à celle du premier
élément du tableau.
Lorsqu'un caractère est utilisé comme index pour accéder à un élément d'un tableau, ce
caractère doit être stocké dans une variable de type unsigned char.
Une fois ces principes posés, l'écriture d'une fonction capable de placer des valeurs utilisables
dans les tableaux code et decode n'est plus qu'une application de la méthode de tirage sans
remise présentée dans l'Annexe 6.
Le cas du caractère nul mérite sans doute d'être traité de façon un peu particulière, car
certains logiciels risquent de l'interpréter comme marquant la fin du texte, même s'ils le
rencontrent bien avant la fin du fichier. Il est donc plus prudent de ne pas l'introduire dans
la version crypté d'un fichier où il n'apparaît pas. Une façon très simple de garantir ce
résultat est de décider que la valeur 0 est son propre cryptage (et, donc, son propre
décryptage).
void CMonDialogue::OnButtonChangerCode()
{
int i;
unsigned char urne[256]; //création d'une urne pour le tirage sans remise
for(i=0 ; i < 256 ; ++i) //remplissage de l'urne
urne[i] = i;
code[0] = 0; //Il vaut mieux éviter que la valeur 0 soit introduite dans
decode[0] = 0; //les fichiers si elle ne figure pas dans l'original
unsigned char unCaractere;
unsigned char sonCode;
int tirage;
for(unCaractere = 255 ; unCaractere > 0 ; -- unCaractere)
{//la décrémentation de unCaractere provoque le "rétrécissement" de l'urne
tirage = 1+(rand()*unCaractere)/RAND_MAX; //tirage dans ]0, unCaractere]
sonCode = urne[tirage]; //sonCode est la valeur désignée par le tirage
code[unCaractere] = sonCode;
decode[sonCode] = unCaractere;
urne[tirage] = urne[unCaractere];//remplacement de la valeur tirée par la
}
// valeur qui va être exclue du prochain tirage
}
3 - Exercice complémentaire
Comment pourrait-on s'y prendre pour que les boutons "Crypter" et "Décrypter" soient
remplacés par un unique bouton "Traiter" ?
Poser cette question revient à demander comment le programme peut, en examinant le
contenu du fichierSource, déterminer s'il s'agit d'un texte en clair ou d'un texte crypté…
J-L Péris - 20/09/02