I. Introduction

Dans cet article nous allons nous pencher sur la sécurisation des données privées au sens large du terme.

De tout temps la manipulation des données afin d'en préserver le secret ou l'intégrité fut un enjeu majeur de l'information. Que ce soit la permutation de Jules César, l'encre invisible ou le sceau royal, il fut et demeure nécessaire de pouvoir identifier ou chiffrer des données. Le Framework .NET met à notre disposition un certain nombre de classes pour simplifier le travail, encore faut-il avoir les idées claires quant aux concepts mis en œuvre.

Cet article sera écrit en VB.NET 2003.

Bonne lecture.

II. Concepts

En informatique, il est toujours assez complexe de déterminer ce qui relève de quoi. En l'occurrence, la meilleure façon de sécuriser des données consiste évidemment à ne pas les divulguer. Certes, ce n'est pas toujours très commode mais cet aspect n'est pas à perdre de vue. Pourquoi ?

La protection des données repose généralement sur plusieurs couches, la première devant être la sécurisation du poste de travail. En effet il ne sert à rien de chiffrer 140 fois un fichier si les clés sont facilement accessibles. Nous verrons cela plus en détail lors de l'étude de la sécurisation des clés, mais si vous n'avez aucune notion d'administration du poste de travail, je vous encourage vivement à vous pencher sur le sujet.

II-A. Chiffrement

Souvent jargonné sous le terme "Cryptage", le chiffrement est le principe le plus connu même si ce n'est pas toujours le mieux compris. Le principe consiste à transformer les données avec des méthodes plus ou moins complexes afin d'obtenir des données "protégées". Cette opération doit pouvoir être accomplie en sens inverse, le déchiffrement, pour pouvoir récupérer les données d'origines.

Il existe de très nombreuses techniques car le principe est vieux comme le monde.

II-A-1. Algorithme secret

Bien qu'ils aient presque complètement disparus, je vous en parle car il s'agit d'une tentation fréquente lorsqu'on commence à se pencher sur les joies de la cryptographie. A la base, même une compression est un encodage et on peut facilement croire qu'un encodage par un algorithme qu'on est le seul à connaître est particulièrement sûr. Malheureusement, le Reverse Engineering étant ce qu'il est, cette approche est erronée. Il est toujours assez simple de retrouver les opérations d'un algorithme et dès lors de casser la sécurité. Ne vous lancez pas dans ce genre d'aventure, il n'y a pas de plus mauvaise sécurité qu'une sécurité illusoire.

II-A-2. Chiffrement à clé

Tous les algorithmes modernes utilisent maintenant des chiffrements à clés. Il en existe de plusieurs sortes, regroupés en trois grandes familles.

II-A-2-a. Chiffrement à clé symétrique

Dans ce type de chiffrement, la même clé sert au codage et au décodage des données. Il en existe plusieurs (Rijndael, RC2, DES, Triple DES, etc…) mais tous reposent sur le même principe. Pour donner un exemple trivial, un chiffrement à clé symétrique peut être la multiplication par deux de chaque code ASCII. Deux est alors la clé.

II-A-2-b. Chiffrement à clé asymétrique

Beaucoup plus complexe, ce chiffrement utilise une paire de clé publique/privée. La clé publique sert au chiffrement et la clé privée au déchiffrement. Le plus connu d'entre eux est l'algorithme RSA (RIVEST, SHAMIR, ADLEMAN).

Les chiffrements à clé symétrique ou asymétrique ne sont pas plus sûrs selon la nature de la clé. Cette différence est due aux problèmes de communication de clé. En effet, dans une approche du chiffrement à des fins de communication, je dois à un moment ou un autre fournir la clé de déchiffrement dans le cas d'une clé symétrique. La clé est alors vulnérable. Dans le cas d'un chiffrement asymétrique, il suffit de communiquer la clé publique aux personnes souhaitant m'envoyer des données, pour qu'ils puissent chiffrer les données avec cette clé. Je pourrais les déchiffrer à l'aide de ma clé privée qui, elle, n'est pas transmise.

Gardez à l'esprit que les chiffrements asymétriques sont plus lourds et donc beaucoup plus long, on les réserve généralement pour le traitement de petit volume de données.

II-A-2-c. Chiffrement à sens unique

Cette famille un peu particulière nommée "hachage" ne permet pas le déchiffrement. Comme on l'utilise à des fins d'identification, nous la verrons un peu plus loin.

II-A-2-d. Techniques dérivées

Dans la longue lutte entre chiffreurs et casseurs, il apparaît bien régulièrement de nouvelles techniques de dérivation pour brouiller les pistes. On utilise par exemple le salage pour contrer les attaques par dictionnaire, les clés composites ou les clés dérivées pour protéger la clé, etc…

II-B. Identification

Bien que l'identification soit un vaste domaine, elle fait partie intégrante du monde de la cryptographie informatique. En effet, l'identification permet de pouvoir reconnaître une source de données chiffrées ou non comme sûre sans avoir à la déchiffrer au préalable. Par ailleurs l'identification doit servir à diminuer les attaques par leurre. Bien que généralement non suffisant, la base de l'identification repose généralement sur le hachage.

II-B-1. Hachage

Le hachage (de l'anglais hash) est un chiffrement à sens unique, c'est-à-dire qu'il n'est pas théoriquement possible de remonter à la valeur d'origine en connaissant la valeur hachée. Il existe de nombreux algorithmes (MD5, SHA1, SHA256,…). Dans tous les cas, le hachage donne une chaîne résultante de longueur connue que l'on appelle taille de hachage. L'intérêt du hachage est donc évidemment de faire de l'identification ou du contrôle d'intégrité de données. On peut aussi l'utiliser à des fins de comparaison. Ainsi, il est beaucoup plus sûr de faire une comparaison de valeurs hachées que de leurs homologues en clair.

II-B-2. Limites du hachage

Quoique très fiable, le hachage a des limites. La première de ces limites est une utilisation correcte, car il est assez facile de reconnaître une valeur hachée et il peut être assez facile de deviner ce qui a été haché. Dès lors, le leurre redevient possible.

Le deuxième danger consiste à stocker des valeurs critiques hachées trop facilement accessible. Ainsi, si vous hachez des mots de passe et que vous laissez les valeurs obtenues accessibles, vous rendez vos mots de passe vulnérables à une attaque par dictionnaire, voire par force brute.

II-B-3. Hachage avec une clé

Pour contourner la possibilité de corruption du hachage, on utilise généralement un hachage avec une clé, c'est-à-dire qu'on ajoute une valeur secrète à la valeur à hacher ce qui rend théoriquement impossible une mystification.

II-C. Génération de clé

Il s'agit là du pivot de la cryptographie, sans de bonnes clés pas de cryptographie valable. Là encore, il existe plusieurs techniques de génération de clé.

II-C-1. Aléatoire

Les précurseurs de clé sont générés de façon aléatoire. Il faut s'assurer que la fonction aléatoire le soit bien, ce qui exclu Rand par exemple, et privilégier les appels à des fonctions ad hoc. L'avantage de la génération aléatoire est qu'elle permet de générer des clés très robustes quand elle est bien utilisée.

II-C-2. Dérivée

Très populaire puisqu'elle permet de ne pas stocker la clé, la génération de clé dérivée est cependant périlleuse car elle n'est fiable que si les précurseurs (généralement des mots de passe) sont bien choisis, ce qui va nous permettre de gloser quelque peu sur les mots de passe.

II-C-2-a. Mot de passe

L'éternel problème du mot de passe repose sur la continuelle lutte entre mémorisation et robustesse. Un mot de passe fiable fait généralement une vingtaine de caractère utilisant des lettres sensibles à la casse, des chiffres et des caractères de ponctuation, de plus il est de bon ton qu'il ne contienne aucun mot ou suite de caractères susceptible de le rendre vulnérable à une attaque par dictionnaire. Autant dire tout de suite que pour mémoriser "dAT9{j*c3-{p5KvADyX(rb" il faut un peu d'entraînement. Cependant une clé dérivée de "médor" présente assez peu de résistance aussi faut il trouver un compromis selon la sensibilité des données protégées et leur durée de vie.

II-C-3. Composite

Il existe un grand nombre de techniques de génération de clé. On les nomme clés composites car leurs précurseurs viennent de sources différentes. Les plus utilisées sont des clés dérivées où le précurseur est un mot de passe prétraité avec une autre donnée. Attention cependant, il existe des générateurs un peu exotiques. J'ai vu ainsi un programme utiliser des paramètres hardware dans les précurseurs d'une clé de chiffrement de données. C'est uniquement le jour du changement de poste de travail que l'utilisateur c'est rendu compte qu'il ne pouvait plus déchiffrer ses données (ce qui prouve au moins qu'elles étaient bien sécurisées).

II-D. Evaluation des risques et des besoins

La première étape lorsqu'on se lance dans la sécurisation des données consiste à évaluer ce dont on a besoin, et donc, d'avoir une idée des techniques d'attaques.

II-D-1. Boucher les failles

Avant toute attaque assez volumineuse, les attaquants vont toujours commencer par chercher les failles éventuelles. Bien qu'elles soient connues, on les retrouve très fréquemment.

Ne stocker jamais un mot de passe ou une clé en clair dans le code.

De manière générale éviter de stocker quoi que ce soit dans le code car il s'agit d'un endroit particulièrement vulnérable. Lorsque des valeurs sensibles doivent y être, elles doivent être hachées. Méfiez vous aussi des chaînes de connexions qui peuvent contenir des informations sensibles en clair.

Ne hacher jamais le seul contenu d'un fichier si vous mettez la valeur hachée dans le fichier

Une des plus redoutables fausses bonnes idées consiste à hacher le contenu du fichier, puis à ajouter la valeur obtenue en fin de fichier. La première chose que va essayer un pirate qui trouve une valeur hachée en fin de fichier, est une combinaison courante (nom de fichier + contenu, contenu seul, etc…) pour savoir à quoi correspond le hachage.

N'écrivez jamais des données déchiffrées sur le disque

Même temporairement. Votre chiffre sera d'autant plus vulnérable que le pirate pourra accéder à la fois aux représentations claire et chiffrée. Il y aura de toute façon une faille grave puisqu'il suffirait de faire planter l'application au moment ou le fichier en clair existe. Par ailleurs des données même temporairement déchiffrées sur un disque peuvent rester accessibles alors que le fichier est détruit.

N'utilisez jamais de méthode de cryptage cassée

Il ne sert à rien d'implémenter une méthode utilisant le chiffre de beaufort par exemple car certains logiciels de décryptage le casseront en quelques minutes. Partez du principe que l'attaquant sait ce qu'il fait. Toutes les méthodes de chiffrement vulnérables aux attaques par "mot probable" par exemple doivent être bannies.

N'utilisez pas de clé faible

Lorsqu'une clé est générée aléatoirement, il peut exister un risque de créer une clé faible. Une clé faible est une clé présentant un élément de symétrie. Par exemple, si pour une clé 128 bits, les 64 premiers bits ont la même valeur que les 64 derniers, vous avez une clé faible.

II-D-2. Comprendre l'attaquant

Pour vous défendre correctement, vous devez appréhender les techniques de l'attaquant. La cryptographie est une chaîne dont la sécurité est régie par le maillon le plus faible. A part quelques boutonneux généralement peu dangereux, les attaquants qui cherchent à casser un chiffre sont des gens expérimentés qui vont toujours chercher le point de vulnérabilité de votre défense.

La lutte peut revêtir deux aspects, la bataille des clés ou l'attaque du chiffre.

Attaquer un chiffre donné par les algorithmes standards de chiffrement, pour peu qu'il soit correctement utilisé, est une entreprise colossale, demandant du temps, des ressources importantes et une solide connaissance des mécanismes de chiffrement. C'est donc généralement autour de la clé que se joue la sécurité.

Une chose est sûre pour le défenseur, vous devez mettre les clés le plus à l'abri possible. Une chose est certaine pour l'attaquant, si votre code sait déchiffrer, la clé sera à un moment ou à un autre relativement accessible.

Comme vous l'avez compris, la clé ne doit pas être présente dans le code. Ou elle doit être dérivée d'une donnée secrète, ou elle doit exister dans une ressource externe. Si l'application n'est pas censée être installée sur n'importe quel poste, vous pouvez utiliser des fichiers ou le registre (avec des ACL correctes), sinon, seule la dérivation est véritablement fiable.

Dans le pire des cas, l'attaquant peut installer une version fonctionnelle de l'application sur un poste où il possède les droits administrateurs.

II-D-3. Limitations

  • Utiliser le hachage, les signatures numériques ou des certificats à des fins d'identification. Une identification réussie ne doit pas vous dispenser d'un contrôle de validité des données entrantes, ceci vous permettra d'éviter qu'une faille se transforme en désastre.
  • Le chiffrement est une opération lourde, veillez à ne pas l'utiliser pour protéger des données non sensibles.
  • La mise en place d'une cryptographie nécessitant des ressources externes à l'application demande d'utiliser une stratégie de déploiement spécifique. Celle-ci doit alors tenir compte des règles d'administration définies par le client.
  • Dans le cadre des applications censées chiffrer / déchiffrer, on utilise généralement des clés dérivées puisqu'il n'est pas nécessaire de les stocker. Cependant si votre application utilise une gestion d'accès spécifique (à chaque utilisateur un mot de passe) les données chiffrées par un utilisateur ne pourront être lue par un autre ce qui n'est pas forcément bienvenu. Les applications à mot de passe commun contournent cela mais au prix d'une plus grande vulnérabilité puisque le mot de passe doit être communiqué.

Utiliser le hachage, les signatures numériques ou des certificats à des fins d'identification. Une identification réussie ne doit pas vous dispenser d'un contrôle de validité des données entrantes, ceci vous permettra d'éviter qu'une faille se transforme en désastre.

III. L'espace de nom Security.Cryptography

Cet espace de nom contient l'ensemble des classes nécessaires à la gestion de la cryptographie. On retrouve généralement toujours la même construction hiérarchique, à savoir :

<Famille Algorithmes>

<Algorithmes>

<Fournisseur ou Classe>

Une représentation incomplète peut être exprimée par l'arborescence suivante :

Image non disponible

Dans la suite de cet article, nous allons voir quelques exemples des pratiques les plus courantes de manipulation des classes de cet Espace de nom.

IV. Exemples

IV-A. Chiffrement symétrique de fichier

Cette première application (WinForms) permet de crypter des fichiers avec l'algorithme symétrique Triple DES (Data Encryption Standard). Celui-ci utilise une clé de 168 bits (ou 192 bits), qui sont en fait trois clés de 56 bits et un vecteur d'initialisation. Ce vecteur à pour but d'empêcher l'identification de blocs chiffrés similaires pour un texte clair similaire.

Généralement on utilise le service cryptographique le plus proche possible des données à chiffrer afin de diminuer les vulnérabilités de la clé.

Il est nécessaire d'avoir la clé et le vecteur pour déchiffrer, ils seront donc créés une fois puis stockés dans le registre.

Nous allons aussi utiliser des évènements personnalisés pour suivre la progression de l'opération. Je vais donc créer une bibliothèque de classe (crypto.vb) :

Tout d'abord je dérive une classe pour mon argument d'évènement :

 
Sélectionnez
Public Class ChiffreEventArgs

    Inherits System.EventArgs
    Private m_Realize, m_Total As Long

    Public Sub New(ByVal ValRealise As Long, ByVal ValTotal As Long)
        Me.m_Realize = ValRealise
        Me.m_Total = ValTotal
    End Sub

    Public ReadOnly Property Total() As Long
        Get
            Return Me.m_Total
        End Get
    End Property

    Public ReadOnly Property Realise() As Long
        Get
            Return Me.m_Realize
        End Get
    End Property

End Class

Cet argument renverra le nombre d'octets à convertir et le nombre d'octets convertis. Maintenant nous allons créer une classe de chiffrement / déchiffrement de fichier selon l'algorithme Triple DES. Mon entête de classe sera :

 
Sélectionnez
Imports Microsoft.Win32
Imports System.IO
Imports System.Security
Imports System.Security.Permissions
Imports System.Security.Cryptography

<Assembly: RegistryPermission(SecurityAction.RequestMinimum, All:="HKEY_CURRENT_USER\SOFTWARE\Secure")> 
<Assembly: FileIOPermission(SecurityAction.RequestMinimum, Unrestricted:=True)> 

Comme nous l'avons vu dans la sécurité d'accès au code, je peux demander la vérification des autorisations au niveau assembly afin d'interdire l'appel à ma bibliothèque en cas d'autorisations insuffisantes.

Le code de la classe sera :

 
Sélectionnez
Public Class CryptFile
    'Déclaration des délégués pour le suivi de l'opération asynchrone
    Delegate Sub StartChiffreEventHandler(ByVal sender As Object, ByVal e As ChiffreEventArgs)
    Delegate Sub EndChiffreEventHandler(ByVal sender As Object, ByVal e As ChiffreEventArgs)
    Delegate Sub ProgressChiffreEventHandler(ByVal sender As Object, ByVal e As ChiffreEventArgs)
    'Déclaration des évènements pour le suivi de l'opération asynchrone
    Public Event StartChiffreEvent As StartChiffreEventHandler
    Public Event EndChiffreEvent As EndChiffreEventHandler
    Public Event ProgressChiffreEvent As ProgressChiffreEventHandler

    Private RegApp As RegistryKey
    Private STDES As TripleDESCryptoServiceProvider
    Public Sub New()
        
'vérification du registre
        RegApp = Registry.CurrentUser.OpenSubKey("Software\\Secure\\chPers", True)
        STDES = New TripleDESCryptoServiceProvider
        If RegApp Is Nothing Then 'les clés de registre n'existent pas
            With STDES 'je génére une clé de chiffrement et un vecteur
                .GenerateIV()
                .GenerateKey()
            End With
            'je les mets dans le registre
            RegApp = Registry.CurrentUser.CreateSubKey("Software\\Secure\\chPers")
            RegApp.SetValue("cle", Convert.ToBase64String(STDES.Key))
            RegApp.SetValue("vecteur", Convert.ToBase64String(STDES.IV))
        Else 'les clés de registre existent
            With STDES 'je récupère les valeurs du registre
                .Key = Convert.FromBase64String(CType(RegApp.GetValue("cle"), String))
                .IV = Convert.FromBase64String(CType(RegApp.GetValue("vecteur"), String))
            End With
        End If

    End Sub

    Public Sub chiffrement(ByVal Fichier As String)

        If File.Exists(Fichier) Then
            Dim FichierSource As New FileStream(Fichier, FileMode.Open, FileAccess.Read)
            Dim FichierCible As New FileStream(Path.ChangeExtension(Fichier, "cry"), _
 FileMode.Create, FileAccess.Write)
            Dim ChiffreFlux As New CryptoStream(FichierCible, STDES.CreateEncryptor, CryptoStreamMode.Write)
            FichierCible.SetLength(0)
            Dim bin(100) As Byte
            Dim LongTotale As Long = FichierSource.Length
            RaiseEvent StartChiffreEvent(Me, New ChiffreEventArgs(0, LongTotale))
            Dim LongEffectue As Long = 0
            Dim BlocLen As Integer
            'traitement
            While LongEffectue < LongTotale
                BlocLen = FichierSource.Read(bin, 0, 100)
                ChiffreFlux.Write(bin, 0, BlocLen)
                LongEffectue += BlocLen
                RaiseEvent ProgressChiffreEvent(Me, New ChiffreEventArgs(LongEffectue, LongTotale))
            End While
            RaiseEvent EndChiffreEvent(Me, New ChiffreEventArgs(LongEffectue, LongTotale))
            ChiffreFlux.Close()
            FichierSource.Close()
            FichierCible.Close()
        Else
            Throw New FileNotFoundException("Le fichier n'as pas été trouvé", Fichier)
        End If

    End Sub

    Public Sub Dechiffrement(ByVal Fichier As String, ByVal ExtensionCible As String)

        If File.Exists(Fichier) Then
            Dim FichierSource As New FileStream(Fichier, FileMode.Open, FileAccess.Read)
            Dim FichierCible As New FileStream(Path.ChangeExtension(Fichier, ExtensionCible), _
 FileMode.Create, FileAccess.Write)
            Dim ChiffreFlux As New CryptoStream(FichierCible, STDES.CreateDecryptor, CryptoStreamMode.Write)
            FichierCible.SetLength(0)
            Dim bin(100) As Byte
            Dim LongTotale As Long = FichierSource.Length
            RaiseEvent StartChiffreEvent(Me, New ChiffreEventArgs(0, LongTotale))
            Dim LongEffectue As Long = 0
            Dim BlocLen As Integer
            'traitement
            While LongEffectue < LongTotale
                BlocLen = FichierSource.Read(bin, 0, 100)
                ChiffreFlux.Write(bin, 0, BlocLen)
                LongEffectue += BlocLen
                RaiseEvent ProgressChiffreEvent(Me, New ChiffreEventArgs(LongEffectue, LongTotale))
            End While
            RaiseEvent EndChiffreEvent(Me, New ChiffreEventArgs(LongEffectue, LongTotale))
            ChiffreFlux.Close()
            FichierSource.Close()
            FichierCible.Close()
        Else
            Throw New FileNotFoundException("Le fichier n'as pas été trouvé", Fichier)
        End If

    End Sub

End Class

Pour exécuter un chiffrement symétrique Triple DES je crée une instance du fournisseur TripleDESCryptoServiceProvider. Si les valeurs n'existent pas déjà dans le registre, je crée une clé de 192 bits et un vecteur. Notez que le générateur de nombre aléatoire utilisé pour générer la clé interdit la création d'une clé faible.

Une fois que mon fournisseur possède une clé et un vecteur, il peut fournir ces mécanismes de chiffrement / déchiffrement. Pour sécuriser l'opération, on chiffre généralement un flux. Pour cela, on utilise une instance de CryptoStream qui attend une référence au flux sortant, un appel au service de chiffrement et le mode d'action. Ensuite l'opération elle-même est très simple.

Ce code chiffre donc le fichier spécifié. Ce chiffrement est robuste, le seul point faible de l'application étant la capacité d'accéder à la clé de registre pour un attaquant. Si les ACL sont correctement définies, il est quasiment impossible de briser ce chiffrement, surtout si je régénère une clé et un vecteur périodiquement.

Le déchiffrement prendrait exactement la même forme si ce n'est que je devrais créer CryptoStream avec le code :

 
Sélectionnez
Dim ChiffreFlux As New CryptoStream(FichierClair, STDES.CreateDecryptor, CryptoStreamMode.Write)

Et que je dois passer l'extension du fichier de sorti.

Un code de consommation de ma classe pourrait être :

 
Sélectionnez
Private Sub cmdCryptFile_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
 Handles cmdCryptFile.Click
    Dim MonFichier As String
    With Me.OpenFileDialog1
        .CheckFileExists = True
        .DefaultExt = "doc"
        .Filter = "Documents Word (*.doc)|*.doc|Fichiers texte (*.txt)|*.txt|Tous les fichiers (*.*)|*.*"
        .FilterIndex = 1
        .Multiselect = False
        .Title = "Selection du fichier à chiffrer"
        If .ShowDialog(Me) = DialogResult.OK Then
            MonFichier = .FileName
        End If
    End With
    Try
        Dim objChiffre As New Crypto.CryptFile
        AddHandler objChiffre.StartChiffreEvent, AddressOf Operation_StartChiffreEvent
        AddHandler objChiffre.ProgressChiffreEvent, AddressOf Operation_ProgressChiffreEvent
        AddHandler objChiffre.EndChiffreEvent, AddressOf Operation_EndChiffreEvent
        objChiffre.chiffrement(MonFichier)
    Catch ex As Exception
        MsgBox("Erreur lors de l'opération", MsgBoxStyle.Critical And MsgBoxStyle.OKOnly, "ERREUR")
    End Try

End Sub  

Private Sub Operation_StartChiffreEvent(ByVal sender As Object, ByVal e As Crypto.ChiffreEventArgs)
    With Me.lblChiffrement
        .Text = "Opération en cours..."
        .Visible = True
    End With
    Me.pgbChiffrement.Visible = True
End Sub

Private Sub Operation_ProgressChiffreEvent(ByVal sender As Object, ByVal e As Crypto.ChiffreEventArgs)
    Me.pgbChiffrement.Value = CInt(e.Realise / e.Total * 100)
End Sub

Private Sub Operation_EndChiffreEvent(ByVal sender As Object, ByVal e As Crypto.ChiffreEventArgs)
    Me.lblChiffrement.Visible = False
    Me.pgbChiffrement.Visible = False
End Sub

IV-B. Chiffrement Asymétrique de clé

En l'état je peux donc chiffrer et déchiffrer des fichiers sur mon poste. Par contre je ne peux pas fournir ces fichiers à l'extérieur sans fournir la clé, qui du coup devient plus vulnérable.

Supposons que nous voulions communiquer ces fichiers à neo.51 par mail. Je dois trouver un moyen sécurisé de lui transmettre la clé. Pour cela, nous allons utiliser un cryptage asymétrique.

Il est assez facile de créer des clés asymétriques dans .NET mais il faut se méfier. Si la clé publique est faite pour être communiquée, la clé privée, elle, ne doit jamais l'être. Or le fournisseur RSA permet d'exporter assez simplement des informations de clé pouvant contenir la clé privée. Ces méthodes, parfois appelées « clés mobiles » doivent être utilisées en toute connaissance de cause. En effet, les clés mobiles sont extrêmement vulnérables si elles sont trop facilement accessibles. Généralement, on ne les utilise que pour garder les valeurs de clés en cas d'incident sur le poste de travail. Les clés mobiles ne devraient jamais être utilisées au sein d'une application.

Lorsqu'on crée une paire de clé, on utilise généralement un « magasin » (KeyContainer) qui gère le stockage et la protection des clés sur le poste de travail.

Je vais simuler tout cela au sein d'un même exemple. Tout d'abord, je vais ajouter un constructeur à la classe CryptFile que nous avons vu précédemment, afin de pouvoir l'utiliser avec un couple clé, vecteur importé.

 
Sélectionnez
Public Sub New(ByVal Cle() As Byte, ByVal Vecteur() As Byte)
    'création d'un service avec des valeurs externes
    STDES = New TripleDESCryptoServiceProvider
    With STDES
        .Key = Cle
        .IV = Vecteur
    End With
End Sub

Ensuite, nous allons créer une classe gérant le chiffrement asymétrique.

 
Sélectionnez
Public Class RSAPaire
    Private m_Magasin As String
    Private m_RSA As RSACryptoServiceProvider

    Public Sub New(ByVal NomMagasin As String, ByVal CreerMagasin As Boolean)

        m_Magasin = NomMagasin
        If CreerMagasin Then CreateMagasin() Else RecupMagasin()

    End Sub

    Public Sub New(ByVal ImportString As String)
        m_RSA = New System.Security.Cryptography.RSACryptoServiceProvider
        m_RSA.FromXmlString(ImportString)
    End Sub

    Private Sub CreateMagasin()
        Dim cspParam As CspParameters = New CspParameters
        cspParam.KeyContainerName = m_Magasin
        m_RSA = New System.Security.Cryptography.RSACryptoServiceProvider(cspParam)
        m_RSA.PersistKeyInCsp = True

    End Sub

    Private Sub RecupMagasin()
        Dim cspParam As CspParameters = New CspParameters
        cspParam.KeyContainerName = m_Magasin
        m_RSA = New System.Security.Cryptography.RSACryptoServiceProvider(cspParam)
    End Sub

    Public Function ExportPublicKey() As String
        Return m_RSA.ToXmlString(False)
    End Function

    Public Function CryptData(ByVal DonneeClaire() As Byte) As Byte()
        Return m_RSA.Encrypt(DonneeClaire, False)
    End Function

    Public Function DecryptData(ByVal DonneeChiffree() As Byte) As Byte()
        Return m_RSA.Decrypt(DonneeChiffree, False)
    End Function

End Class

Ce constructeur est particulier dans le sens qu'il permet au fournisseur RSACryptoServiceProvider de chiffrer mais pas de déchiffrer.

Reprenons donc, je veux échanger la recette de la garbure avec Neo.51 afin qu'il ne se nourrisse pas uniquement de hamburger ce qui n'est pas bon pour lui. Cette recette sera cryptée afin que Maïté ne nous tombe pas dessus quand elle verra que je ne mets pas de confit.

Pour que Neo.51 puisse déchiffrer le fichier je dois lui faire parvenir la clé et le vecteur Triple DES. Il doit donc créer un magasin et en exporter la clé publique. En effet, j'ai besoin de sa clé publique pour chiffrer les informations. Si je chiffre avec la mienne il ne pourra pas déchiffrer puisqu'il n'a pas ma clé privée.

On peut simuler cela avec le code suivant :

 
Sélectionnez
    Private Sub cmdExportPubKey_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
 Handles cmdExportPubKey.Click
        Dim objRsa As New Crypto.RSAPaire("neo", True)
        Me.txtPublicKey.Text = objRsa.ExportPublicKey
    End Sub

Il lui suffit alors de m'envoyer la chaîne obtenue dans un mail.

Je vais donc utiliser cette chaîne pour chiffrer mes informations de clé symétrique :

 
Sélectionnez
Private Sub cmdRSACrypt_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
 Handles cmdRSACrypt.Click

    Dim objRsa As New Crypto.RSAPaire(Me.txtPublicKey.Text)
    Dim RegApp As RegistryKey = Registry.CurrentUser.OpenSubKey("Software\\Secure\\chPers", True)
    Dim CryptCleSym() As Byte
    Dim CryptVecSym() As Byte
    CryptCleSym = objRsa.CryptData(Convert.FromBase64String(CType(RegApp.GetValue("cle"), String)))
    CryptVecSym = objRsa.CryptData(Convert.FromBase64String(CType(RegApp.GetValue("vecteur"), String)))
    Me.txtCleSymCry.Text = Convert.ToBase64String(CryptCleSym)
    Me.txtVecSymCry.Text = Convert.ToBase64String(CryptVecSym)

End Sub

Je possède donc maintenant deux chaînes chiffrées correspondant respectivement à la clé et au vecteur Triple DES. Je peux les mettre à mon tour dans un mail avec le fichier word précédemment chiffré. Pour déchiffrer le fichier, Neo.51 devra seulement déchiffrer les informations de clé avec sa clé privé, puis le fichier. Par exemple :

 
Sélectionnez
Private Sub cmdDecryptKeyAndFile_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
 Handles cmdDecryptKeyAndFile.Click
    Dim objRsa As New Crypto.RSAPaire("neo", False)
    Dim DecryptCleSym() As Byte
    Dim DecryptVecSym() As Byte
    DecryptCleSym = objRsa.DecryptData(Convert.FromBase64String(Me.txtCleSymCry.Text))
    DecryptVecSym = objRsa.DecryptData(Convert.FromBase64String(Me.txtVecSymCry.Text))
    Dim MonFichier As String
    With Me.OpenFileDialog1
        .CheckFileExists = True
        .DefaultExt = "cry"
        .Filter = "Fichiers chiffrés (*.cry)|*.cry"
        .FilterIndex = 1
        .Multiselect = False
        .Title = "Selection du fichier à déchiffrer"
        If .ShowDialog(Me) = DialogResult.OK Then
            MonFichier = .FileName
        End If
    End With
    Try
        Dim objChiffre As New Crypto.CryptFile(DecryptCleSym, DecryptVecSym)
        AddHandler objChiffre.StartChiffreEvent, AddressOf Operation_StartChiffreEvent
        AddHandler objChiffre.ProgressChiffreEvent, AddressOf Operation_ProgressChiffreEvent
        AddHandler objChiffre.EndChiffreEvent, AddressOf Operation_EndChiffreEvent
        objChiffre.Dechiffrement(MonFichier, "doc")
    Catch ex As Exception
        MsgBox("Erreur lors de l'opération", MsgBoxStyle.Critical And MsgBoxStyle.OKOnly, "ERREUR")
    End Try

End Sub

Cette technique est correctement sécurisée puisqu'à aucun moment la clé privée n'a été communiquée. Cependant, il ne faut pas perdre de vue quelques considérations annexes. Une des nombreuses légendes du Web veut que les chiffrements asymétriques soient la panacée universelle, toujours bien plus sûrs que leurs homologues symétriques. Non seulement il n'en est rien, mais cela peut être fallacieux. Les clés asymétriques telle que la paire RSA de l'exemple peuvent toujours être attaquées par reconstruction. Ces attaques sont d'autant plus efficaces que l'on peut récupérer des informations de la clé publique pour conduire l'attaque. Ainsi dans mon exemple, la clé RSA est plus vulnérable que le fichier crypté car elle n'est pas assez longue. Rassurez vous quand même, l'attaque d'une clé RSA de 1024 bits ne se fait pas en dix minutes.

Je vous ai montré ici un exemple d'implémentation particulier, mais sachez que le Framework expose des classes RSAOAEPKeyExchangeFormatter et RSAOAEPKeyExchangeDeformatter qui gèrent parfaitement ce type de fonctionnement.

IV-C. Le hachage

Comme nous l'avons vu dans la partie théorique, le hachage est un chiffrement en sens unique utilisé principalement à des fins d'authentification. Le premier exemple est le simple hachage de mot de passe.

Dans le plus simple des cas, imaginons une boite de dialogue pour entrer dans mon application, avec un mot de passe commun pour tous les utilisateurs :

 
Sélectionnez
    Private Sub cmdOK_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdOK.Click
        Dim SHA As New SHA256Managed
        Dim UniEnc As New System.Text.UnicodeEncoding
        Dim bitPass() As Byte = UniEnc.GetBytes(Me.txtPassword.Text)
        If Convert.ToBase64String(SHA.ComputeHash(bitPass)) = "ew9LkdvpuYaurqPCDV3rWGZ1vTaD3GfN6b441cA0TNQ=" Then
            Me.DialogResult = DialogResult.OK
        Else
            Me.DialogResult = DialogResult.Cancel
        End If
    End Sub

Notez que je laisse sous forme de chaîne dans le code source la valeur hachée de comparaison. Cela est sans importance puisqu'on ne peut pas remonter de la valeur hachée vers la valeur claire. Cependant la sécurité ne repose que sur la robustesse du mot de passe. Ce genre d'application est assez rare pour des raisons évidentes, on trouve plus souvent des applications multi utilisateur, chacun gérant son mot de passe.

Le stockage de ces mots de passe est généralement une base de données ou un fichier, comme c'est le cas pour l'authentification par formulaire ASP.NET.

Prenons un exemple comme celui-ci. Les données de connexion se trouvent rangée sous la forme :

 
Sélectionnez
<credentials>
  	<User Name="Jean-Marc" Password="ew9LkdvpuYaurqPCDV3rWGZ1vTaD3GfN6b441cA0TNQ=" /> 
  	<User Name="Morpheus" Password="ew9LkdvpuYaurqPCDV3rWGZ1vTaD3GfN6b441cA0TNQ=" /> 
  	<User Name="Neo.51" Password="ew9LkdvpuYaurqPCDV3rWGZ1vTaD3GfN6b441cA0TNQ=" /></ credentials >

Autant le dire clairement, si vous hachez simplement les mots de passe tel que nous venons de le faire et s'il y a de nombreux utilisateurs enregistrés, vous êtes probablement loin de la sécurité. L'efficacité des attaques par dictionnaire n'est plus à démontrer, pas plus que la faiblesse des mots de passe usuellement choisis. Une méthode pour résoudre cela consiste à forcer le choix d'un mot de passe robuste.

Par exemple, le code suivant force la saisie d'un mot de passe robuste :

 
Sélectionnez
<Flags()> Private Enum Conforme
                  HasLower = 1
                  HasUpper = 2
                  HasNumber = 4
                  HasOther = 8
        End Enum 

Private Sub txtNewPass_Validating(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) _
 Handles txtNewPass.Validating
    If Me.txtNewPass.Text.Length < 12 Then
        e.Cancel = True
        Me.ErrorProvider1.SetError(Me.txtNewPass, "Le mot de passe doit contenir au moins 12 caractères")
    End If
    Dim Verif As New Conforme
    Dim tabChar() As Char = Me.txtNewPass.Text.ToCharArray
    For cmpt As Integer = tabChar.GetLowerBound(0) To tabChar.GetUpperBound(0)
        Select Case True

            Case Char.IsPunctuation(tabChar(cmpt)) OrElse Char.IsSymbol(tabChar(cmpt))
                Verif = Verif Or Conforme.HasOther

            Case Char.IsLower(tabChar(cmpt))
                Verif = Verif Or Conforme.HasLower

            Case Char.IsUpper(tabChar(cmpt))
                Verif = Verif Or Conforme.HasUpper

            Case Char.IsNumber(tabChar(cmpt))
                Verif = Verif Or Conforme.HasNumber

        End Select
    Next
    If Verif < 15 Then
        e.Cancel = True 
        Me.ErrorProvider1.SetError(Me.txtNewPass, _ 
"Le mot de passe doit contenir au moins une majuscule, une minuscule un chiffre et un symbole")
    End If
End Sub

Dans ce type de contrainte, veillez à ne pas forcer le changement de mot de passe trop fréquemment ; la sécurité doit être présente et non pesante.

IV-C-1. Salage

Une autre technique pour se protéger contre les attaques par dictionnaire consiste à utiliser le salage (parfois nommé "hachage avec clé"). Le salage est l'adjonction d'un terme au terme à hacher pour obtenir un hachage résistant. Selon les cas, le sel (c'est-à-dire le terme ajouté) doit être secret ou non. Dans le cas de notre fichier, il doit être original puisqu'il ne peut être secret stricto sensu.

Pour la suite de notre exemple, imaginons le fichier App.config suivant :

 
Sélectionnez
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
      <sectionGroup name="SecurityGroup">
         <section name="AccessSection"
            type="System.Configuration.NameValueSectionHandler" />
         <section name="IntegriteSection"
            type="System.Configuration.SingleTagSectionHandler" />
         <section name="SaltSection"
            type="System.Configuration.SingleTagSectionHandler" />
      </sectionGroup>
   </configSections>
   <SecurityGroup>
      <AccessSection>
         <add key="bidou" value="pua2jaEGDfiz/1Co3uDWSigjl/jzwMVtU9LJzYED+O4=" />
         <add key="neo.51" value="1pxvek/zYndThdRUtZDtWtta9qIZzf1ELgsLb6qE+k8=" />
         <add key="morpheus" value="lHUgdYfqWnThea9P3uBkaE8q7TuQ1MC1g4rJH/fHRLQ=" />
      </AccessSection>
      <IntegriteSection Setting1 ="V+HG0hhMdCIctvSFD5kqfE1vCZX6FjprtHuoxoTEppo=" />
      <SaltSection Setting1 ="VV0+*àBD" Setting2 ="c)4Dé}yB" Setting3 ="w5jnpFgP" />
   </SecurityGroup>
</configuration>

Un exemple de vérification de mot de passe pourrait être par exemple :

 
Sélectionnez
Private Sub cmdOK_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdOK.Click
    Dim Salage As IDictionary
    Dim collSec As NameValueCollection
    Dim SHA As New SHA256Managed
    Dim UniEnc As New System.Text.UnicodeEncoding

    collSec = CType(Configuration.ConfigurationSettings.GetConfig("SecurityGroup/AccessSection"), NameValueCollection)
    Salage = CType(Configuration.ConfigurationSettings.GetConfig("SecurityGroup/SaltSection"), IDictionary)
    For Elem As Integer = 0 To collSec.Count - 1
        If collSec.GetKey(Elem).CompareTo(Me.txtUserName.Text) = 0 Then
            Dim bitPass() As Byte = _ 
UniEnc.GetBytes(Me.txtPassword.Text.Insert(3, Salage.Item("Setting" & (1 + Elem).ToString).ToString))
            If Convert.ToBase64String(SHA.ComputeHash(bitPass)).CompareTo(collSec.Get(Elem)) = 0 Then
                Me.DialogResult = DialogResult.OK
                Exit Sub
            End If
        End If
    Next
    Me.DialogResult = DialogResult.Cancel

End Sub

Dans ce cas, j'ai placé mes termes de sel dans le fichier de configuration. Cependant j'ai un peu complexifié l'opération en insérant le sel dans le mot de passe. Ne sous estimez pas l'efficacité du salage. Beaucoup de pirates en herbe se livrent à des attaques par dictionnaire sans même étudier le fonctionnement, le salage rendra le dictionnaire inopérant.

Même si dans mon exemple cela reste très simple, le salage permet d'augmenter fortement la sécurité. Par exemple je pourrais multiplier les leurres en ajoutant dans le fichier des valeurs de salage que je n'utilise pas ; je pourrais également utiliser une composition de valeurs pour saler, etc…

En l'état, les hachages ne sont pas spécialement vulnérables, si ce n'est que rien n'empêche de mettre une autre valeur hachée à la place d'un hachage de mot de passe existant ce qui permettrait alors de franchir la sécurité (à condition de saler correctement).

IV-C-2. Contrôle d'intégrité

La problématique du contrôle d'intégrité des fichiers est toujours bien plus complexe qu'il y parait à première vue. D'abord parce que si les données hachées sont susceptibles d'être modifiées, le hachage doit être relativement facile d'accès, ensuite parce qu'un des pièges les plus classique consiste à hacher des données prévisibles ce qui permet à un hacker de retrouver la logique du contrôle et donc de pouvoir modifier le fichier à sa guise.

Ne vous lancez pas non plus dans une surenchère du chiffrement, chiffrer le hachage ne change généralement rien dans une optique d'application installable partout car la clé de chiffrement sera aussi accessible que le hachage.

Continuons avec mon fichier de configuration précédent. Je veux donc lui donner une identification afin qu'il ne soit pas possible de faire fonctionner l'application avec une version du fichier modifiée par ailleurs.

Le principe est toujours le même. On récupère un certain nombre de données que l'on mixe ensemble avant de procéder au hachage. On obtient alors une valeur, appelée parfois "empreinte de sécurité" ou "signature numérique" que l'on compare avec une même valeur précédemment stockée. Il existe deux écoles pour le stockage de l'empreinte.

  • Le stockage interne : C'est-à-dire dans le fichier marqué.
  • Le stockage externe : C'est-à-dire dans le registre ou dans un autre fichier.

Le stockage interne : C'est-à-dire dans le fichier marqué.

Les deux méthodes sont sensiblement équivalentes, elles permettent l'utilisation de leurre (par exemple stocker plusieurs valeurs dans le registre alors qu'une seule est utilisée). Regardons un exemple de contrôle en stockage interne, valeur que vous voyez dans le fichier de configuration dans la section "IntegriteSection".

 
Sélectionnez
Private Sub frmPass_Activated(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Activated
    Dim Salage As IDictionary
    Dim collSec As NameValueCollection
    Dim SHA As New SHA256Managed
    Dim UniEnc As New System.Text.UnicodeEncoding
    Dim TabString() As String
    Dim Integrite As String

    collSec = CType(Configuration.ConfigurationSettings.GetConfig("SecurityGroup/AccessSection"), NameValueCollection)
    Salage = CType(Configuration.ConfigurationSettings.GetConfig("SecurityGroup/SaltSection"), IDictionary)
    ReDim TabString(collSec.Count * 3)
    For cmpt As Integer = 0 To collSec.Count - 1
        TabString(cmpt * 3) = Salage.Item("Setting" & (1 + cmpt).ToString).ToString
        TabString(cmpt * 3 + 1) = collSec.GetValues(cmpt).GetValue(0).ToString
        TabString(cmpt * 3 + 2) = collSec.GetKey(cmpt)
    Next
    TabString(TabString.GetUpperBound(0)) = System.Reflection.Assembly.GetExecutingAssembly.ImageRuntimeVersion
    Integrite = CType(Configuration.ConfigurationSettings.GetConfig("SecurityGroup/IntegriteSection"), _
 IDictionary).Item("Setting1").ToString
    Dim bitPass() As Byte = UniEnc.GetBytes(String.Join(Me.Height.ToString, TabString))
    If Convert.ToBase64String(SHA.ComputeHash(bitPass)).CompareTo(Integrite) <> 0 Then
        MsgBox("Echec du controle d'intégrité", MsgBoxStyle.Critical And MsgBoxStyle.OKOnly)
        Me.DialogResult = DialogResult.Cancel
    End If
End Sub

J'ai utilisé ici une empreinte assez simple, il demeure évidemment possible de faire beaucoup plus complexe.

Attention toutefois à ce que vous utilisez, car selon les cas vous pouvez vous piéger vous-même. Par exemple, j'ai une fois intégré dans l'empreinte la date de dernière modification du fichier. Comme je faisais un stockage interne, j'enregistrais le fichier par la suite ce qui fait que mon contrôle d'intégrité plantait systématiquement.

IV-C-3. Signature numérique

La signature numérique consiste à identifier des données et leur provenance. Fondamentalement, cela consiste à hacher des données puis à signer ce hachage avec une clé privé asymétrique. On peut identifier ces données grâce à la valeur du hachage, et vérifier leur authenticité par contrôle de la signature à l'aide de la clé publique.

Par exemple, nous allons ajouter deux propriétés et deux fonctions à notre classe RSAPaire comme suit :

 
Sélectionnez
Public ReadOnly Property ValeurHachage() As Byte()
    Get
        Return Me.m_ValHash
    End Get
End Property

Public ReadOnly Property Signature() As Byte()
    Get
        Return Me.m_ValSign
    End Get
End Property

Public Sub CreeSignature(ByVal Fichier As String)
    Dim RSASignature As New RSAPKCS1SignatureFormatter(m_RSA)
    Dim Flux As New FileStream(Fichier, FileMode.Open, FileAccess.Read)
    Dim SHA As New SHA1CryptoServiceProvider
    Me.m_ValHash = SHA.ComputeHash(Flux)
    With RSASignature
        .SetHashAlgorithm("SHA1")
        Me.m_ValSign = .CreateSignature(Me.m_ValHash)
    End With
End Sub

Public Function VerifSignature(ByVal ValeurHachee As String, ByVal LaSignature As String) As Boolean
    Dim RSASignature As New RSAPKCS1SignatureDeformatter(m_RSA)
    RSASignature.SetHashAlgorithm("SHA1")
    Return RSASignature.VerifySignature(Convert.FromBase64String(ValeurHachee), _
 Convert.FromBase64String(LaSignature))
End Function

Les deux propriétés ont peu d'intérêt, penchons nous sur les fonctions.

Dans mon exemple, j'attends un nom de fichier que je vais pouvoir hacher. C'est ce hachage que je vais signer à l'aide de l'objet RSAPKCS1SignatureFormatter.

La fonction VerifSignature est quand à elle le simple pendant de la fonction VerifySignature de l'objet RSAPKCS1SignatureDeformatter.

Maintenant je vais appeler ma méthode de chiffrement afin de crypter le fichier et de récupérer une signature.

 
Sélectionnez
'creation de la signature
Dim objRsa1 As New Crypto.RSAPaire("neo", True)
objRsa1.CreeSignature(FichierCrypte)
Me.txtHashFile.Text = Convert.ToBase64String(objRsa1.ValeurHachage)
Me.txtNumSign.Text = Convert.ToBase64String(objRsa1.Signature)
'verification de la signature
Dim PubKey As String = objRsa1.ExportPublicKey
Dim objrsa2 As New Crypto.RSAPaire(PubKey)
If objrsa2.VerifSignature(Me.txtHashFile.Text, Me.txtNumSign.Text) Then
    MsgBox("Signature valide")
Else
    MsgBox("Signature non valide")
End If

Dans la première partie, je crée un objet RSA en récupérant les clés de mon magasin comme nous l'avons vu précédemment, je transmets alors le nom du fichier chiffré et je récupère les deux valeurs obtenues, c'est-à-dire le hachage et le hachage signé. La vérification quant à elle a lieu grâce à un objet RSAPaire qui ne contient que la clé publique.

In fine j'obtiens donc un fichier chiffré, une valeur de hachage, une signature numérique. Le fait que le fichier soit chiffré n'a aucune importance sur l'authentification. Celui qui réceptionne ces trois éléments et qui connaît ma clé publique peut vérifier que :

  • Le hachage est bien correspondant à la signature
  • Le hachage est bien correspondant au fichier

Le hachage est bien correspondant à la signature

Donc le fichier est bien authentique.

Cette technique est extrêmement efficace, car tant que la clé privée est bien protégée, l'intégrité et l'authentification ne font aucun doute. Cette technique d'identification peut être utilisée dans de nombreux scénarios.

IV-D. Clé dérivée

Le principe de la clé dérivée repose donc sur la génération d'une clé à partir d'un précurseur qui lui est censé être secret. Prenons l'exemple le plus courant où le précurseur est un mot de passe. Dans mon exemple, l'application utilise des Datasets chiffrés (sous forme XML) et nécessite donc un mot de passe pour pouvoir les lire. On manipule les clés dérivées dans le Framework à l'aide de la classe PasswordDeriveBytes.

Pour dériver la clé, c'est-à-dire pour fournir une clé en partant d'un mot de passe, cette classe attend :

  • Un nombre d'itération
  • Un algorithme de hachage
  • Une valeur de salage
  • Le mot de passe

Un nombre d'itération

Le principe est assez simple, le mot de passe est ajouté au sel, et l'ensemble est haché le nombre de fois précisé selon l'algorithme défini.

Trois remarques cependant.

Il faut que la valeur de hachage soit suffisamment longue pour pouvoir alimenter une clé de la bonne dimension (dans notre cas 192 bits soit 24 caractères).

Par ailleurs il faut un mot de passe robuste afin de se garantir contre les attaques par dictionnaire puisque le sel est fourni par l'application.

On pourrait durcir la protection en allongeant la taille du sel et/ou en demandant un deuxième mot de passe comme valeur de salage.

En l'état mon code serait :

 
Sélectionnez
Private Sub cmdAccCryptData_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
 Handles cmdAccCryptData.Click

    Dim CryptXML As New FileStream(Application.StartupPath & "\biblio.cry", FileMode.Open)
    Dim pdb As New Security.Cryptography.PasswordDeriveBytes(Me.txtPass.Text, _
 New Byte(7) {21, 10, 16, 21, 32, 2, 78, 9})
    With pdb
        .HashName = "SHA256"
        .IterationCount = 722
    End With
    Dim Prov3DES As New TripleDESCryptoServiceProvider
    With Prov3DES
        .Key = pdb.GetBytes(24)
        .IV = pdb.GetBytes(8)
    End With
    Dim CryptFlux As New CryptoStream(CryptXML, Prov3DES.CreateDecryptor, CryptoStreamMode.Read)
    Dim MonDs As New DataSet
    MonDs.ReadXml(CryptFlux, XmlReadMode.ReadSchema)
    Me.DataGrid1.DataSource = MonDs

End Sub

L'avantage évident de cette méthode est que la clé n'est stockée nulle part.

IV-E. Clé composite

Pour finir, nous allons voir une technique chère aux auteurs de shareware, la clé composite. Ce type de clé utilise des précurseurs de deux sources, généralement l'éditeur et la machine. Il existe de nombreux schémas possibles, je vais prendre un grand classique comme exemple. Cependant il faut généralement faire preuve de plus d'imagination pour créer un système résistant.

Dans cette implémentation, le Shareware possède un bouton "enregistrer"qui génère une clé à envoyer à l'éditeur, celui-ci renvoyant une clé à donner au logiciel pour activer les fonctionnalités manquantes. Le programme contient donc une clé publique dont l'éditeur possède la clé privée pour sécuriser le transfert.

La clé publique peut être stockée n'importe où car sa découverte n'a pas grande importance.

Pour extraire des informations de la machine, on utilise l'interop avec WMI, c'est-à-dire les classes de l'espace de nom Management.

Créons une classe qui va nous permettre de manipuler la clé composite.

 
Sélectionnez
Public Class ExtractInfo
    Private m_info1, m_info2, m_info3 As String

    Public Sub New()

        m_info1 = GetMacAddress()
        m_info2 = GetDiskSerial()
        m_info3 = GetProcessorRef()

    End Sub

    Private Function GetMacAddress() As String

        Dim NetClass As New System.Management.ManagementClass("Win32_NetworkAdapterConfiguration")
        Dim Nets As ManagementObjectCollection = NetClass.GetInstances
        Dim Net As ManagementObject
        For Each Net In Nets
            GetMacAddress = Net("MacAddress").ToString
            Exit For
        Next Net

    End Function

    Private Function GetDiskSerial() As String

        Dim diskClass As New System.Management.ManagementClass("Win32_LogicalDisk")
        Dim disks As System.Management.ManagementObjectCollection = diskClass.GetInstances()
        Dim disk As System.Management.ManagementObject
        For Each disk In disks
            If CStr(disk("Name")) = "C:" Then
                GetDiskSerial = disk("VolumeSerialNumber").ToString
            End If
        Next disk

    End Function

    Private Function GetProcessorRef() As String

        Dim ProcClass As New System.Management.ManagementClass("Win32_Processor")
        Dim Procs As ManagementObjectCollection = ProcClass.GetInstances
        Dim Proc As ManagementObject
        For Each Proc In Procs
            GetProcessorRef = Proc("ProcessorId").ToString
            Exit For
        Next Proc

    End Function

    Public Function EmissionCle(ByVal Pbk As String) As Byte()
        Dim mRSA As New RSACryptoServiceProvider
        mRSA.FromXmlString(Pbk)
        Dim UEnc As New System.Text.UnicodeEncoding
        Dim strTest As String = Me.m_info1 & "|" & Me.m_info2 & "|" & Me.m_info3
        Dim TabByte() As Byte = UEnc.GetBytes(strTest)
        Return mRSA.Encrypt(TabByte, False)
    End Function

    Public Function VerifyKey(ByVal Test As String) As Boolean

        Dim cmpt As Integer
        Dim TabByte(23), rndByte() As Byte, Verif() As Byte
        Dim dEnc As New System.Text.ASCIIEncoding
        dEnc.GetBytes(m_info1.Substring(4, 2)).CopyTo(TabByte, 0)
        ReDim Verif(1)
        dEnc.GetBytes(m_info1.Substring(6, 2)).CopyTo(Verif, 0)
        dEnc.GetBytes(m_info2.Substring(0, 5)).CopyTo(TabByte, 2)
        cmpt = 1 + m_info2.Length - 6
        ReDim Preserve Verif(cmpt)
        dEnc.GetBytes(m_info2.Substring(6)).CopyTo(Verif, 2)
        dEnc.GetBytes(m_info3.Substring(0, 5)).CopyTo(TabByte, 8)
        ReDim Preserve Verif(cmpt + m_info3.Length - 6)
        dEnc.GetBytes(m_info3.Substring(6)).CopyTo(Verif, cmpt + 1)
        rndByte = Convert.FromBase64String( _ 
Registry.CurrentUser.OpenSubKey("Software\\Secure\\Share").GetValue("part").ToString)
        rndByte.CopyTo(TabByte, 14)
        dEnc.GetBytes(m_info1.Substring(8, 2)).CopyTo(TabByte, 22)
        Dim m3DES As New TripleDESCryptoServiceProvider
        m3DES.Key = TabByte
        m3DES.IV = New Byte(7) {Verif(0), Verif(1), Verif(2), Verif(3), Verif(4), Verif(5), Verif(6), Verif(7)}
        Dim ms As New IO.MemoryStream
        Dim CryptFlux As New CryptoStream(ms, m3DES.CreateEncryptor, CryptoStreamMode.Write)
        CryptFlux.Write(System.Text.UnicodeEncoding.Unicode.GetBytes(Test), 0, Test.Length)
        Dim PassByte As Byte() = ms.ToArray
        If String.Compare(Registry.CurrentUser.OpenSubKey("Software\\Secure\\Share") _ 
.GetValue("ver").ToString, Convert.ToBase64String(PassByte)) <> 0 Then
            Return False
        Else
            Return True
        End If

    End Function

End Class

Dans cet exemple, je récupère l'adresse MAC, le numéro de série du disque C et la référence du processeur. J'ai implémenté par ailleurs deux fonctions, une qui génère une chaîne cryptée avec les informations et une autre qui vérifie la clé finale, autrement dit qui vérifie que le logiciel est bien enregistré.

La création de la chaîne cryptée pour l'envoie vers l'éditeur n'est pas bien complexe, elle reprend ce que nous avons vu pour le chiffrement asymétrique.

Une fois cette chaîne arrivée, l'éditeur va donc à son tour retourner une chaîne à l'utilisateur. Que contient elle ?

Elle contient en fait deux choses :

  • Le mot de vérification codé
  • Le complément de la clé Triple DES

Le mot de vérification codé

Ces deux valeurs seront stockées dans le registre lors de l'enregistrement.

En regardant maintenant le code de vérification, on comprend mieux comment est composée une clé composite. Dans cet exemple, la clé TripleDES est composé d'un tableau de 24 byte définit par :

  • 2 octets donnés par l'adresse MAC
  • 10 octets donnés par l'éditeur
  • 5 octets donnés par la référence processeur
  • 5 octets donnés par le numéro de série du disque C
  • 2 octets donnés par l'adresse MAC

2 octets donnés par l'adresse MAC

Le vecteur quand à lui est uniquement composé par des paramètres de la machine.

Dans cet exemple, la solution est assez vulnérable, mais je pourrais très facilement la durcir en :

V. Conclusion

Voila, nous avons vu les grandes familles de gestion des données secrètes. N'oubliez pas que votre plus grand ennemi demeure dans les chaînes de caractères, et qu'une bonne défense doit être rigoureuse. Ne vous alarmez pas trop, la plupart des attaquants sont assez mauvais et utilisent des logiciels d'attaque qu'ils n'ont pas écrit. Ceux-là n'ont aucune chance de percer vos défenses si vous ne commettez pas d'erreur.