Contact - A propos
Skip Navigation Links.

Sécuriser le code .NET

Descendre ! 

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

Remonter en haut de la page 

© C-O 2005-2008