Sécuriser le code .NET
Obfusquer du code avec Visual studio 2005
En quoi consiste l'obfuscation de code ?
Il s'agit d'une technique qui permet à un développeur de brouiller son code afin
que d'autres développeurs ne puissent pas voir comment est construit son assemblage.
Mon code est donc à l'abri du reverse ingeniering mais ne puis-je pas tirer d'autres
avantages de l'obfuscation ?
En fait, l'un des gros avantages de cette technique est qu'elle permet également
de gagner considérablement en performance grâce à une réduction de la taille du code. Cette
réduction peut représenter jusqu'à 50 % de l'exécutable !
Comment obfusquer du code ?
Pour obfusquer du code, il existe un outil nommé Dotfuscator que l'on utilise conjointement avec Visual Studio
2005.
Comment Dotfuscator fait-il son affaire ? L'idée est de réduire
au minimum les noms des classes, propriétés et méthodes du programme. Par exemple,
MaNouvelleClasse.MaNouvelleMethode sera réduit à M.M.
Voici un exemple concret:
Code source original avant "Dotfuscation"
private void CalcPayroll(Specialist employeeGroup)
{
while (employeeGroup.HasMore()) {
employee = employeeGroup.GetNext(true); employee.UpdateSalary(); DistributeCheck(employee);
}
}
Code source après "Dotfuscation"
private void a(a b) { while (b.a()) {
a = b.a(true); a.a(); a(a);
} }
Ce code est extrait de la documentation de Dotfuscator.
Sécuriser le code des Windows Forms
Contrôler les entrées utilisateurs
Le contrôle des entrées utilisateurs est fondamental. Pourquoi ? Premièrement, il
est absolument nécessaire de vérifier des données avant de les transmettre à une autre application. Cette vérification doit classiquement se faire sur le
type, la taille et le format des données. Deuxièmement, il est plus sécurisé de
vérifier la validité d'une donnée à partir d'une liste de valeurs attendues. Par
exemple, la fonction suivante teste si le nom d'un fichier entré par l'utilisateur
est un document Office (on ne considère que les extensions .xls, .ppt et .doc).
private bool EstUnFichierOffice(string nomFichier)
{
bool bEstUnFichierOffice = false;
string[] extensions = new String[] { ".doc", ".xls", ".ppt" };
for (int i = 0; i < extensions.Length; i++)
if ( nomFichier.Contains(extensions[i]) )
bEstUnFichierOffice = true;
return bEstUnFichierOffice;
}
J'ai réalisé un projet Windows Forms dans lequel j'utilise également la méthode
EndsWith qui me permet de voir si le nom entré par l'utilisateur
représente un fichier exécutable: VerifierUnTypeDeFichier.zip
Utiliser les expressions régulières
Lorsqu'il s'agit de contrôler des entrées plus complexes, on peut faire appel aux
expressions régulières. L'espace de noms System.Text.RegularExpressions prend en charge les expressions
régulières. Par exemple, la méthode suivante vérifie qu'un code postal se constitue
bien de 5 chiffres:
private bool EsUnCodePostal(string strCodePostal)
{
Regex re = new Regex(@"^\d{5}$");
return re.Match(strCodePostal).Success;
}
Voici ce que donne cette méthode implantée dans un projet Windows Forms:
ExpressionsRegulieres.zip
Utiliser les systèmes cryptographiques
Chiffrement à clé privée
Vous trouverez ici un petit rappel de ce qu'est la cryptographie à clé
privée.
L'espace de noms System.Security.Cryptography permet d'utiliser différents
systèmes cryptographiques: RSA, DES, MD5, Rijndael, ...
Dans notre exemple, nous utilisons le triple DES qui consiste à appliquer trois fois l'algorithme
DES. Voici les méthodes qui permettent de crypter et décrypter les données:
namespace ChiffrementCleSymetrique { public class Cryptographie
{
public byte[] cle; // Clé secrète
public byte[] vecteur; // Vecteur d'initialisation
public byte[] Crypter(string strFlux)
{
// Chaque lettre est traduite par son code ASCII
byte[] bFlux = ASCIIEncoding.ASCII.GetBytes(strFlux);
// Service de cryptage TripleDES.
TripleDESCryptoServiceProvider tDes = new TripleDESCryptoServiceProvider();
// Génération automatique de la clé et du vecteur initialisation
if (cle == null)
{
tDes.GenerateKey();
tDes.GenerateIV();
cle = tDes.Key;
vecteur = tDes.IV;
}
else
{
tDes.Key = cle;
tDes.IV = vecteur;
}
// Utilisation d'un encrypteur pour transformer les données
ICryptoTransform iEncryptor = tDes.CreateEncryptor();
MemoryStream mStream = new MemoryStream();
// Utilisation d'un flux mémoire pour stocker les données cryptées.
CryptoStream cStream = new CryptoStream(mStream, iEncryptor,CryptoStreamMode.Write);
cStream.Write(bFlux, 0, bFlux.Length);
cStream.FlushFinalBlock();
// Créer un flux pour le résultat
mStream.Position = 0;
byte[] bResultat = new byte[(int)mStream.Length];
mStream.Read(bResultat, 0, (int)mStream.Length);
cStream.Close();
return bResultat;
}
public string Decrypter(byte[] bFlux)
{
TripleDESCryptoServiceProvider tDes = new TripleDESCryptoServiceProvider();
tDes.Key = cle;
tDes.IV = vecteur;
// Utilisation d'un décrypteur pour déchiffrer les données.
ICryptoTransform iDecryptor = tDes.CreateDecryptor();
MemoryStream mStream = new MemoryStream();
// Utilisation d'un flux mémoire pour stocker les données décryptées.
CryptoStream cStream = new CryptoStream(mStream, iDecryptor, CryptoStreamMode.Write);
cStream.Write(bFlux, 0, bFlux.Length);
cStream.FlushFinalBlock();
mStream.Position = 0;
byte[] bResultat = new byte[(int)mStream.Length];
mStream.Read(bResultat, 0, (int)mStream.Length);
cStream.Close();
return ASCIIEncoding.ASCII.GetString(bResultat);
}
static void Main(string[] args)
{
Cryptographie crypto = new Cryptographie();
byte[] bMessageCrypte = crypto.Crypter("Message à crypter");
Console.Write("Message crypté: ");
foreach (byte b in bMessageCrypte)
Console.Write(b);
Console.Write("\nMessage Décrypté: ");
string strMessageDecrypte = crypto.Decrypter(bMessageCrypte);
Console.WriteLine(strMessageDecrypte);
}
}
}
On remarquera un problème dont j'ignore la provenance avec l'affichage du caractère
accentué "à".
Chiffrement à clé publique
Notre exemple de chiffrement à clé publique utilise le système RSA. Voici notre méthode de chiffrement:
public string clePublique;
public string clePrivee;
// Une instance de CspParameters contient les paramètres du fournisseur de service de cryptage (CSP)
public CspParameters cspParametre;
public byte[] byteMessageCrypte;
public byte[] Crypter(string sFlux)
{
cspParametre = new CspParameters();
// Indiquer que les clés doivent être sauvegardées dans l'espace de stockage des clés de l'ordinateur
cspParametre.Flags = CspProviderFlags.UseMachineKeyStore;
RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(cspParametre);
byte[] bFlux = rsaProvider.Encrypt(Encoding.Unicode.GetBytes(sFlux), false);
clePublique = rsaProvider.ToXmlString(false);
clePrivee = rsaProvider.ToXmlString(true);
return bFlux;
}
Et notre méthode de déchiffrement:
public string Decrypter(byte[] bFlux)
{
RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(cspParametre);
rsaProvider.FromXmlString(clePrivee);
byte[] bTemp = rsaProvider.Decrypt(bFlux, false);
return ASCIIEncoding.Unicode.GetString(bTemp);
}
J'ai Winformisé ces deux méthodes: ChiffrementRSA.zip
Avec la documentation MSDN en aide, le code ici présenté ne devrait pas poser de
problèmes particuliers. N'hésitez surtout pas à m'envoyer un mail si vous avez des
questions ;)
Dans la série code sécurisé, l'ouvrage Ecrire du code sécurisé vous permettra d'approfondir vos
connaissances sur le sujet et de trouver des conseils indispensables à la construction
d'applications sécurisées.
Dimanche 26 Mars 2006
|