Introduction

En matière de sécurité, la version 2.0 du Framework apporte de nombreuses nouveautés, l'une d'entre elle est la classe SecureString. Cette classe est similaire à la classe System.String, à certaines différences, dont les principales sont que la valeur d'une SecureString est automatiquement cryptée, et qu'une SecureString est mutable, c'est-à-dire que lorsque l'on modifie sa valeur elle est réellement modifiée en mémoire, et non simplement écrite dans un nouvel emplacement comme avec une String classique, mais n'entrons pas plus dans les détails maintenant, nous verrons cela par la suite.

Microsoft .Net 2.0

1. Les faiblesses de System.String

Pour comprendre les apports et l'utilité des SecureString, je pense qu'il faut d'abord voir les défauts et les manques des System.String, en voici une liste non exhaustive :

  • Impossible de les effacer de la mémoire, du moins au moment où on le souhaite et avec certitude.
  • Ces chaînes de caractères restent en clair dans la mémoire, donc potentiellement peu sûres.
  • Elles sont non mutables, c'est-à-dire qu'en cas de modification de la valeur, celle-ci n'est pas écrasée en mémoire par la nouvelle valeur, mais écrite à un autre emplacement en mémoire, c'est-à-dire que l'ancienne et la nouvelle version cohabitent dans la mémoire.
  • Elles ne sont pas fixes en mémoire, c'est-à-dire que le Garbage Collector peut les déplacer en mémoire, et en créer plusieurs copies en mémoire.

Comme vous pouvez le constater, tous cela fait de la System.String un candidat potentiellement "dangereux" (ne tombons quand même pas dans la paranoïa…), surtout lorsque l'on est appelé à employer des données sensibles comme des mots de passes, des chaînes de connexion, ou des numéros de cartes bancaires par exemple. Voyons sans plus tarder en quoi se différencient les SecureString.

2. Présentation de Security.SecureString

Tout d'abord, précisons que la classe SecureString appartient au namespace Security, qui comme son nom l'indique contient un ensemble de classes et d'interfaces pour la gestion de la sécurité.

2.1. Différences et nouveautés

Un objet instancié à partir de la classe SecureString se présente en quelque sorte comme un objet System.String ordinaire. Il contient donc une valeur texte, mais il y a des différences, qui sur le plan de la sécurité, vont changer la donne. Voici sans attendre les principales différences :

  • La valeur d'un objet SecureString est automatiquement cryptée.
  • La valeur peut être modifiée, c'est-à-dire qu'en cas de modification de la valeur, celle-ci est écrasée en mémoire, et non écrite dans un nouvel emplacement comme avec un objet System.String qui souvent fait cohabiter ancienne et nouvelle valeur.
  • Il est possible d'empêcher la modification de la valeur d'une SecureString grâce à la méthode MakeReadOnly(), qui rend toute modification impossible.
  • A la différence d'un objet System.String, un objet SecureString peut être effacé de la mémoire depuis le code de votre application ou par le Garbage Collector.
  • Autre différence notable, la classe SecureString ne possède aucune méthode permettant la comparaison ou la modification de sa valeur, un objet SecureString est donc de ce point de vue mieux protégé.

Comme on le voit, les différences majeures entre un objet SecureString et String sont toutes relatives à la sécurité. Grâce à SecureString, nous avons enfin une solution aux faiblesses au niveau de la sécurité de System.String, dans ces conditions la valeur réelle d'un objet SecureString est beaucoup plus difficile d'accès à une personne tentant d'obtenir des données de manière frauduleuse, certes cela n'est pas impossible mais qu'est-ce qui l'est réellement en informatique ?

Au travers de cela nous pouvons dire que la version 2.0 du Framework .Net nous apporte, par le biais de l'objet SecureString, le candidat idéal pour traiter et utiliser des données sensibles comme peuvent l'être par exemple des mots de passe ou encore des numéros de carte bancaires.

Remarque : pour le cryptage et le décryptage des données, SecureString s'appuie sur la Data Protection API (ou DPAPI) intégrée à Windows.

2.2. Membres de la classe SecureString

Voyons maintenant, comment utiliser concrètement, cette nouvelle classe du namespace Security.

Méthode Description
AppendChar Ajoute un caractère à la fin de la SecureString courante.
Clear Efface la valeur de la SecureString courante.
Copy Copie l'instance de la SecureString courante.
Dispose Libère toutes les ressources utilisées par la SecureString courante.
Equals Détermine si deux instances d'objet sont égales.
GetType Récupère le type de l'instance de l'objet courant.
InsertAt Insert un caractère dans la SecureString à l'index spécifié.
IsReadOnly Booléen qui détermine si la valeur de la SecureString est en lecture seule.
MakeReadOnly Met la valeur de SecureString en lecture seule.
ReferenceEquals Détermine si les instances d'objet spécifiées sont les mêmes.
RemoveAt Supprime un caractère dans la SecureString à l'index spécifié.
SetAt Remplace un caractère dans la SecureString à l'index spécifié.
ToString Retourne une chaîne qui représente l'objet courant.

3. Utilisation de la classe SecureString

Dans cette section, vous allez voir quelles sont les principales fonctionnalités de l'objet SecureString, ainsi que comment les mettre en application.

3.1. Principales actions sur les SecureString

Mise en lecture seule :

Il est possible de faire en sorte qu'une SecureString ne soit accessible qu'en lecture seule, pour cela il suffit simplement d'utiliser la méthode MakeReadOnly(), après cela il n'est plus possible de modifier ce marquage Read-Only, la valeur est devenue immutable. En cas de tentative de modification une InvalideOperationException est levée. Voici un code exemple.

SecureString en lecture seule
Sélectionnez

Dim maSecureString as New SecureString
maSecureString.MakeReadOnly()

Détecter si une SecureString est Read-Only :

Pour détecter si une SecureString est Read-Only, il suffit d'utiliser la méthode IsReadOnly() qui renvoie un booléen (True ou False) en fonction bien évidemment de l'état du Read-Only de l'instance concernée.

Récupérer la longueur d'une SecureString :

Il existe la propriété Length qui permet de récupèrer la longueur de la SecureString courante, elle représente donc le nombre de caractères qui composent la valeur de cet objet.

Copier une instance de SecureString :

La méthode Copy() permet de copier une instance de SecureString déjà en mémoire, il faut cependant faire attention au cas où la valeur de l'instance d'origine est Read-Only(), sa copie, elle, ne le sera pas.

Ajout de caractères à une SecureString :

La méthode AppendChar() permet d'ajouter des caractères à une SecureString, ces caractères seront alors ajouter en fin de chaîne et automatiquement cryptés, par l'intermédiaire de la DPAPI dont nous avons parlé un peu plus tôt.

Ajouter des caractères
Sélectionnez

Dim maSecureString as New SecureString
maSecureString.AppendChar("e")

Effacer le contenu d'une SecureString :

La méthode Clear() nous permet d'effacer en mémoire la valeur de l'instance de SecureString concernée. Cette méthode efface physiquement cette valeur dans la mémoire, il s'agît donc d'une méthode très sûre, et qui doit être utilisée aussi souvent que possible pour éviter de laisser le contenu d'une SecureString en mémoire, même si celui-ci est crypté, les "bonnes pratiques" recommande de l'effacer.

Supprimer une instance de SecureString :

La classe SecureString implémente l'interface IDisposable, il est donc possible en appelant la méthode Dispose() de libérer les ressources utilisées, notamment en supprimant totalement de la mémoire l'objet SecureString, cette méthode écrit des zéros binaires sur toute la zone mémoire contenant la valeur de cet objet.

D'autres méthodes sont intéressantes, comme par exemple InsertAt() et RemoveAt() qui permettent respectivement d'ajouter et de supprimer un caractère à un index donné dans une SecureString ; je vous renvoie à la documentation pour ces méthodes qui sont très simples d'usage.

3.2. Cryptage de la chaîne de caractères

Le cryptage de la valeur d'une SecureString est automatique. Dès que des caractères sont placés dans une SecureString, ils sont alors immédiatement cryptés. Cela signifie que pour crypter une chaîne de caractères dans une SecureString il suffit donc de faire ceci :

Cryptage d'une SecureString
Sélectionnez

Dim maSecureString As New SecureString

For Each unChar As Char In "ma chaîne à placer dans une SecureString"
	maSecureString.AppendChar(unChar)
Next

Vous voyez qu'il faut utiliser la méthode AppendChar() pour ajouter un à un les caractères constituant la chaîne. Ils sont alors cryptés au fur et à mesure de leur placement dans la SecureString. Il n'est pas possible d'assigner directement une chaîne de caractères complète à une SecureString.

3.3. Décryptage de la chaîne de caractères

Pour décrypter une SecureString, vous allez voir que les choses ne sont pas aussi évidentes que l'on pourrait le croire, cependant rien d'insurmontable. La classe SecureString ne possède pas une méthode qui permette de décrypter la chaîne, et cela pour une raison évidente de sécurité que je vais essayer de vous expliquer.

Un des principaux avantages des SecureString, outre le cryptage automatique des chaînes, est de pouvoir l'effacer de la mémoire, elle-même, ou son contenu, avec certitude, sans pour autant avoir à attendre que cela soit effectué par le Garbage Collector. De plus, nous savons aussi, que celui-ci ne pourra pas copier plusieurs fois la valeur de la SecureString à des endroits différents de la mémoire, ou même, faire cohabiter des "versions" successives de la valeur prise par cette SecureString, ce qui, je vous le rappelle, peut être le cas avec les System.String.

Tout cela pour vous dire que la classe SecureString ne possède pas de méthode décryptage, pour la simple et bonne raison que si l'on devait placer la valeur décryptée dans un objet de type System.String se trouvant lui-même dans un emplacement mémoire managée et sous contrôle du Garbage Collector, nous perdrions alors un pan fondamental du secret de la valeur cryptée. Pour éviter ces "désagréments", nous allons donc utiliser la classe Marshal du namespace System.Runtime.InteropServices qui va nous permettre d'allouer un emplacement mémoire non managé pour y placer la valeur décryptée de notre SecureString. Ainsi, nous pourrons remettre à zéro cet emplacement mémoire après l'avoir utilisé, ce qui est très important au plan de la sécurité. Bien entendu, je simplifie au maximum tous ces processus dans mon explication pour rester clair et compréhensible par le plus grand nombre, pour plus de détails, regardez le code qui suit, et qui vous montre comment procéder :

Décryptage d'une SecureString
Sélectionnez

' Récupération du pointeur 
Dim monPointeur As IntPtr = Marshal.SecureStringToBSTR(maSecrureString)

' Vérification de la validité du mot de passe
If Marshal.PtrToStringUni(monPointeur) = "developpez" Then ...

Vous voyez donc que l'on utilise principalement deux méthodes de la classe Marshal : SecureStringToBSTR et PtrToStringUni, voyons leur utilité un peu plus en détails :

  • SecureStringToBSTR : cette méthode permet d'allouer une BSTR (ou Basic String) dans un espace mémoire non managé, et d'y placer le contenu décrypté d'une SecrureString.
  • PtrToStringUni : cette méthode permet d'allouer la mémoire non managée pour une SystemString managée et y place le nombre de caractères spécifiés venant d'une chaîne de caractères Unicode non managée.

Même si le principe ne vous paraît pas évident à première vue, ceci devient jeu d'enfants dans la réalité du développement. Un exemple valant souvent autant, si ce n'est mieux, que de longs discours, alors voyons sans plus tarder une petite application que j'ai réalisé pour illustrer mes propos.

4. Mini-application d'exemple

4.1. Application Windows-Form

Cette application utilise les classes System.String et System.Security.SecureString, au-delà du code exemple qu'elle vous fournit, il faut aussi regarder de plus près son comportement en mode "débug". En plaçant un point d'arrêt sur le test de validité du mot de passe dans le cas de l'utilisation d'une SecureString, puis en procédant en mode "pas à pas", on peut voir ce que contient réellement la TextBox (ici des étoiles), ou encore voir que le contenu de la SecureString n'est pas disponible, seule la longueur de la chaîne est affichée. Ici à aucun moment, on ne voit apparaître le contenu de la SecureString, ce qui est totalement différent dans le cas d'une System.String où l'on peut voir son contenu en clair. Les quelques captures d'écran suivante démontre ces dires :

Mini application exemple


Une SecureString crypte sa valeur
La valeur n'est pas visible


On utilise un pointeur.
Décryptage de la valeur de la SecureString.


Ce n'est pas acceptable lorsque l'on a des impératifs de confidentialité.
La valeur de la SystemString apparaît en clair :( ...


SecureString est utilisable pour les applications Windows-Forms mais évidemment aussi pour les applications Web-Forms et console, n'oubliez pas nous utilisons .Net :).

Le code source complet de cette application est disponible ci-dessous mais aussi si nécessaire en téléchargement sous la forme d'une solution Visual Studio 2005 (*.sln) dans la section "Ressources" de cet article. Je ne m'étendrais pas plus sur cette application, et les commentaires sont je pense suffisants.

Mini-application
Sélectionnez

'**************************************************************
'
' Mini application montrant l'utilisation de la nouvelle classe
' SecureString du Framework .Net 2.0
' Ce code est libre de droit, vous pouvez donc l'utiliser comme
' bon vous semble. Ce code est donné à titre d'exemple, et par
' conséquent n'offre aucune garantie, de quelque nature que ce
' soit.
'
'**************************************************************
Option Explicit On
Option Strict On

Imports System.Security
Imports System.Runtime.InteropServices

Public Class Form1


#Region "System.Security.SecureString"

    ' Instanciation d'un objet SecureString
    Private maSecrureString As New SecureString

    ' Code permettant de ne pas afficher le mot de passe
    ' et qui remplace les caractères par des étoiles
    Private Sub tbSecureString_KeyPress(ByVal sender As Object, ByVal e As _
                                        System.Windows.Forms.KeyPressEventArgs) _
                                        Handles tbSecureString.KeyPress

        ' Attention : ce code ne fonctionne que si le mot de passe est saisi en une seule
        ' et unique fois. Si l'utilisateur l'efface ou le modifie de nouveau aprés la saisie
        ' les modifications ne sont pas répercutées en mémoire. Il faudra donc prévoir, et gérer
        ' ces situations si cela est nécessaire, sinon le mot de passe ne sera validé uniquement
        ' s'il a été saisi "d'un seul trait".

        ' Ajoute chaque nouveaux caractères à la SecureString
        maSecrureString.AppendChar(e.KeyChar)
        ' Met une étoile dans la TextBox pour remplacer le caractère intercepté
        e.KeyChar = Char.Parse("*")

    End Sub

    Private Sub btSecureString_Click(ByVal sender As System.Object, ByVal e As _
                                     System.EventArgs) Handles btSecureString.Click
                                     
        ' Récupération du pointeur 
        Dim monPointeur As IntPtr = Marshal.SecureStringToBSTR(maSecrureString)

        ' Vérification de la validité du mot de passe
        If Marshal.PtrToStringUni(monPointeur) = "developpez" Then

            ' Efface en mémoire le contenu crypté de la SecureString
            maSecrureString.Clear()

            ' Efface les étoiles qui sont dans la TextBox
            Me.tbSecureString.Text = Nothing

            MessageBox.Show("Mot de passe valide")

        Else

            ' Efface en mémoire le contenu crypté de la SecureString
            maSecrureString.Clear()

            ' Efface les étoiles qui sont dans la TextBox
            Me.tbSecureString.Text = Nothing

            MessageBox.Show("Mot de passe non valide")

        End If

    End Sub

#End Region


#Region "System.String"

    Private Sub btSystemString_Click(ByVal sender As System.Object, ByVal e As _
                                     System.EventArgs) Handles btSystemString.Click

        ' Vérification de la validité du mot de passe
        If Me.tbSystemString.Text = "motdepasse" Then

            ' Efface les étoiles qui sont dans la TextBox
            Me.tbSystemString.Text = Nothing

            MessageBox.Show("Mot de passe valide")
        Else
            MessageBox.Show("Mot de passe non valide")
        End If

    End Sub

#End Region

End Class

Ressources

Conclusion

Comme vous l'avez vu au cours de cet article, la classe SecureString permet de combler une lacune de la classe String standard, en effet, celle ci stocke sa valeur en clair dans la mémoire. SecureString, quand à elle, crypte automatiquement son contenu, plus exactement elle le crypte caractère à caractère. Les autres points forts de cette classe sont nombreux, j'espère vous les avoir montré, et surtout que la prochaine fois vous aurez le réflexe d'utiliser cet objet, qui, il est vrai, n'est malheureusement pas encore trés connu, mais maintenant vous n'avez plus d'excuse !



Un très grand merci à Freegreg pour la relecture de cet article.