[ Perl ] Créer sa propre DB de pass cryptée avec Blowfish

Par Deimos le 21-08-2005



Initiation aux modules cryptographiques en PERL



Sommaire :
I. Introduction
II. Installation des modules PERL sous Linux et Windows
II. 1. Sous Linux
II. 2. Sous Windows
III. Coding


I. Introduction


Perl a de nombreux modules, notamment des modules cryptographiques. La quasi-totalité des cryptages à clé privée / publique et des hashages sont disponibles en perl. Ce projet d'application permettra de développer des notions de base en cryptographie et pourra peut être en même temps avoir une utilité durant la vie courante.

En effet, le script que nous allons développer tout au long de cet article vous servira à stocker vots mots de passe dans un fichier texte crypté avec Blowfish mais également à rajouter des couples login/pass dans ce fichier. Le rajout de ces couples aura pour avantage de la possibilité de générer pour le compte en question une chaine pseudo-aléatoire en guise de mot de passe. Ainsi notre script pourra nous afficher le contenu du fichier crypté, sans jamais le modifier et remplacer le texte chiffré par le texte clair, car cette action pourrait laisser des traces sur un éventuel périphérique comme un disque dur, une clé usb, etc.

En ce qui concerne l'algorithme de cryptage à clé privée, le choix est rapidement fait en regardant les cryptages disponibles :

- DES est désormais considéré comme un algorithme "faible", en effet sa clé a pour longueur 56 bits, ce qui est facilement cassable par une attaque brute force avec la technologie moderne.
- TripleDES est donc le chiffrement par bloc qui est venu remplacé le standard DES : il permet de crypter avec une clé de longueur comprise entre 128 et 192 bits.
- AES et Blowfish sont aujourd'hui des chiffrements à clé secrète dits "forts" et l'un d'eux fait donc parfaitement l'affaire pour le genre d'opération que nous souhaitons faire. AES permet une longueur de clé de 128,192 ou 256 bits et Blowfish entre 32 et 448 bits. AES est cependant plus rapide, mais avec un simple fichier texte content des mots de passe qui n'excèdera pas souvent 100Ko, autant prendre Blowfish.

Pour une meilleure portabilité, les modules utilisés fonctionnent sous OS Unix et Windows.



II. Installation des modules PERL sous Linux et Windows

Nous aurons besoin pour la réalisation de notre script, des modules suivants :
- Crypt::CBC pour obtenir la cryptographie par bloc (Cipher Block Chaining Mode)
- String::Random pour pouvoir générer des chaines pseudo-aléatoires
- Term::ReadKey pour masquer la saisie des caractères lors de l'entrée de la clé.

Je ne décrirais pas comment utiliser les modules, car leur utilisation est clairement expliquée sur le site de CPAN, avec exemples.


1. Sous Linux
Si vous êtes l'heureux possésseur d'une distribution Linux, ce sera simple. Premièrement, testez si le module n'est pas déjà présent en effectuant la commande suivante ( en prenant comme exemple le module Crypt::CBC ) :



code_header Code:
spacer $ perl -e 'use Crypt::CBC'



Si le shell ne renvoie rien, le module est installé, si il renvoie par contre une erreur ... il faudra évidemment l'installer.
Allez sur le site de CPAN chercher votre module ( site CPAN ). Téléchargez le .tar.gzip.

Puis faites les commandes suivantes (je prends comme exemple le paquet Crypt::CBC) :


code_header Code:
spacer gunzip -d Crypt-CBC-2.15.tar.gzip
tar -xvj Crypt-CBC-2.15.tar
OU tar -zxvf Crypt-CBC-2.15.tar.gz
cd Crypt-CBC-2.15
perl Makefile.pl
make
make test
su
make install


2. Sous Windows
En supposant que vous disposez d'ActivePerl, et donc de ppm, l'outil permettant d'installer les modules perl sous Windows, il faut suivre la méthode suivante.

En ce qui concerne String::Random et Term::ReadKey, ils sont répertoriés sur les serveurs de .ppd (les packages pour windows) par défaut. Ainsi pour les installer, suivez la démarche suivante :


code_header Code:
spacer C:\>ppm
ppm> search Term
ppm> install TermReadKey
ppm> search String
ppm> install String-Random


Pour le module cryptographique Crypt::CBC, c'est plus complexe, car il y a des lois en vigueur en ce qui concerne la mise en disposition sur internet de ces modules (comme PGP qui est interdit dans certains pays ...).

Ce module Crypt::CBC ne se trouve donc pas dans les serveurs de .ppd présent par défaut dans l'outil ppm. Pour les installer, allez donc voir sur ce serveur : http://theoryx5.uwinnipeg.ca/ppms/.

Comme indiquez sur le site, si vous disposez de la version 8.xx allez sur l'url ci dessus, si vous disposez de la version 6.xx, allez ici : http://theoryx5.uwinnipeg.ca/ppmpackages/.

Pour savoir quelle version d'ActivePerl vous avez :


code_header Code:
spacer C:\>perl -v


Repérez le nom du module que l'on veut installer, dans ce cas Crypt-CBC, et taper dans l'invite de commandes :


code_header Code:
spacer ppm install http://theoryx5.uwinnipeg.ca/ppms/Crypt-CBC.ppd
ou
ppm install http://theoryx5.uwinnipeg.ca/ppmpackages/Crypt-CBC.ppd



Voilà ... les modules sont installés :)!



III. Let's code
A chaque fois que l'on réalise un projet, il faut définir ce qu'il fait exactement (un peu logique, mais parfois on commence à coder sans savoir ce que l'on va faire par la suite exactement).
Notre script fera exactement ceci :

- Demande de la clé (évidemment, on ne "vérifie" pas si la clé est correcte ou pas, sinon il suffirait de regarder la source du script pour l'obtenir ... On laisse l'utilisateur mettre une clé valide ou pas, il obtiendra ainsi un message en clair ... ou pas)

- Plusieurs fonctions : help pour l'aide, screen pour obtenir le contenu en clair du fichier, et add pour rajouter un compte avec login/pass/description dans le fichier crypté (le script devra donc décrypte le fichier, rajouter la chaine avec login/pass/description puis crypté la nouvelle chaine et l'écrire dans le fichier au dessus de la précédente)

code_header Code:
spacer #!usr/bin/perl

use Crypt::CBC;
use String::Random;
use Term::ReadKey;



Banshee et déclaration des modules, rien de très complexe.


code_header Code:
spacer print "Hi, welcome to passwords' manager coded by Deimos.\n".
"This script encrypts and decrypts passwords' database who\n".
"is encrypted whit Blowfish.\r\n";

print "> Please Enter Blowfish Key";
print "\r\n> ";


Ok, ici non plus, c'est pas dur de comprendre : la fonction print en perl affiche un message dans la console ... \r\n sont respectivement retour chariot et nouvelle ligne

code_header Code:
spacer ReadMode 2;
my $key = c;
chomp $key;



Comme on entre la clé qui va servir à crypter et décrypter la BDD de mots de passe, on prend la précaution de ne pas afficher ce que l'on tape. Le mode 2 de Term::ReadKey nous permet ainsi d'enregistrer ce que l'utilisateur tape dans STDIN, sans l'afficher (comme quand vous vous loggez en root). Au passage, chomp permet de supprimer le retour chariot.

code_header Code:
spacer while($key eq "") {
print "\r\n> ";
$key = <STDIN>;
chomp $key;
}



Cette boucle nous permet au script de redemander à entrer la clé tant que l'utilisateur laisse le champ vide (à l'avenir, faites toujours ça, ça évite les erreurs de frappe de l'utilisateur)

code_header Code:
spacer ReadMode 0;



On remet par défaut le ReadMode : l'utilisateur peut donc à nouveau voir ce qu'il tape ... Vaut mieux :p

code_header Code:
spacer my $blowfish = Crypt::CBC -> new(-key => $key,
-cipher => "Blowfish",
-padding => "null");



On déclare $blowfish, qui est donc notre chiffrement Blowfish qui a pour clé $key (ce que l'utilisateur a entré précédemment). -padding => "null" signifie que le bourrage s'effectue avec les caractères \0 (NULL). Effectivement, comme le Blowfish est un chiffrement par blocs de 68 bits, donc 8 octets (et 8 caractères ...), il est obligatoire d'avoir une chaine qui a un nombre de charactères au multiple de 8. Crypt::CBC nous fait automatiquement ce bourrage, à l'inverse de Crypt::Blowfish.

code_header Code:
spacer print "\r\n> Please enter a command - use 'help' to get the ".
"list of available commands\r\n"



No comment.

code_header Code:
spacer while(1) {



On ouvre la boucle a l'infini, pour que l'utilisateur puisse faire plus d'une commande. En effet si on ne met pas la boucle, le script exécute la commande puis quitte, vu qu'il n'y a que des if comme structures de controle.
L'utilisateur peut évidemment toujours quitter le programme, en faisant Ctrl+C.

code_header Code:
spacer print "> ";
$comm = <STDIN>;
chomp $comm;



A chaque fois que l'utilisateur aura entré une commande, le script bouclera et atterrira de nouveau sur ce bout de code.

code_header Code:
spacer if($comm =~ /^HELP/i) {
print "\r\nList of available commands :\r\n".
"HELP - Screen this plaintext\r\n".
"SCREEN - Decrypt & Screen the DB of passwords\r\n".
"ADD - Allow to add passwords on the DB with\r\n".
" possibility of pseudo-random strings gen\r\n";
}



On arrive dans l'interprétation des commandes entrées par l'utilisateur. Ici c'est une expression régulière toute basique, on vérifie que la chaine commence par "help", et c'est insensible à la casse.

code_header Code:
spacer elsif($comm =~ /^SCREEN/i) {
open(OUTFILE,"pass.txt");
my @out = <OUTFILE>;
close(OUTFILE);
my $ciphertext = "@out";

my $plaintext = $blowfish -> decrypt($ciphertext);
print $plaintext;
}



Ici, on récupère dans $ciphertext le contenu crypté du fichier, puis on le décrypte ... et on l'affiche
code_header Code:
spacer elsif($comm =~ /^ADD/i) {
print "> Enter username : ";
my $username = <STDIN>;
chomp $username;

print "> Would you like generate pseudo-random password ? [y\\n]";
my $answer = <STDIN>;
chomp $answer;



On demande le login du compte à rajouter a l'user, puis on lui demande si il veut rentrer lui même son mot de passe, ou si il désire avoir un mot de pase généré pseudo-aléatoirement. Si l'utilisateur répond "y", nous allons rentrer dans une deuxième condition, et le script demandera si le pass peut contenir ou pas des charactères spéciaux. Encore une fois, on rentre dans une (troisième) condition. Ce qui nous donne :

code_header Code:
spacer if($answer =~ /^y$/i) {
my $random = new String::Random;
$size = $random -> randregex("1[0-5]");

print "> Can the password contain special ".
"characters ? [y\\n]";
my $answer2 = <STDIN>;
chomp $answer2;

unless($answer2 =~ /^y|n$/i) {
print "> Can the password contain ".
"special characters ? [y\\n]";
$answer2 = <STDIN>;
chomp $answer2;
}

if($answer2 =~ /^y$/i) {
$pass = $random -> randregex(".");
}

elsif($answer2 =~ /^n$/i) {
$pass = $random -> randregex("[a-zA-Z0-9]");
}
}

elsif($answer =~ /^n$/i) {
print "> Enter a password for your new account : ";
ReadMode 2;
$pass = <STDIN>;
ReadMode 0;
chomp $pass;
print "\r\n";
}




code_header Code:
spacer print "> Enter description for your new account (optional) : ";
my $desc = <STDIN>;
chomp $desc;



L'utilisateur entre une description pour son compte.


code_header Code:
spacer open(OUTFILE,"pass.txt");
@out = <OUTFILE>;
close(OUTFILE);
$ciphertext = "@out";
my $addtext = "$username - $pass\r\n$desc\r\n\r\n";
my $plaintext = $blowfish -> decrypt($ciphertext);
my $newtext = $plaintext . $addtext;



On récupère dans $ciphertext le contenu crypté courrant, on le décrypte et on le concatène avec la chaine contenant le login, le pass et la description du compte à rajouter !


code_header Code:
spacer my $newciphertext = $blowfish -> encrypt($newtext);
open(INFILE,">pass.txt");
print INFILE $newciphertext;
close(INFILE);



Pour finir, on crypte le tout, et on écrit ça dans le fichier pass.txt.



Création de pass.txt

Avant d'utiliser votre script, créer le fichier pass.txt avec un contenu déjà crypté avec Blowfish est votre clé secrète. Pourquoi ? Tout simplement car lorsque vous utilisez le script, il va essayer de décrypter le contenu de pass.txt, et si il ne trouve pas a l'intérieur une chaine cryptée avec Blowfish initialisée avec IV, ça va planter ...

Vous devez donc au préalable créez un fichier pass.txt, même vide, déjà crypté, par exemple avec ce script :


code_header Code:
spacer >use Crypt::CBC;

#page 10 RFC 792 Internet Control Message Protocol
$key = "This checksum may be replaced in the future.";

my $blowfish = Crypt::CBC -> new(-key => $key,
-cipher => "Blowfish",
-padding => "null");

$text = "Welcome to passwords' database ;)\r\n\r\n";

$cipher = $blowfish -> encrypt($text);
$plaintext = $blowfish -> decrypt($cipher);

open(FILE,">pass.txt");
print FILE $cipher;
close(FILE);



Vous entrez évidemment dans $key la clé qui servira toujours pour chiffrer et déchiffrer le fichier. Ici, c'est un bout du RFC ICMP (page 10) Moqueur !



Evolution du script

Bien évidemment, c'est possible de rajouter quelques options, comme une commande SEARCH qui va cherche dans toutes les descriptions et sortir le bon couple login/pass en clair, ou encore CHANGE KEY qui permettrait de changer la clé, donc de décrypter et recrypter avec une autre clé ... Mais pour cette option, il faudra bien sur mettre une protection par mot de passe, comme la comparaison avec un digest, SHA1 ... pour que l'on ne puisse pas voir le mot de passe en regardant la source !

Sur ce, bon coding, Ciao
Deimos