web-dev-qa-db-fra.com

SecureString est-il pratique dans une application C #?

N'hésitez pas à me corriger si mes hypothèses sont erronées ici, mais laissez-moi vous expliquer pourquoi je vous le demande.

Extrait de MSDN, un SecureString:

Représente le texte qui doit rester confidentiel. Le texte est crypté pour protéger la confidentialité lors de son utilisation et supprimé de la mémoire de l'ordinateur lorsqu'il n'est plus nécessaire.

Je comprends cela, il est tout à fait logique de stocker un mot de passe ou une autre information privée dans un SecureString sur un System.String, car vous pouvez contrôler comment et quand il est réellement stocké en mémoire, car un System.String:

est à la fois immuable et, lorsqu'il n'est plus nécessaire, programmé par programmation pour la récupération de place; c'est-à-dire que l'instance est en lecture seule après sa création et qu'il est impossible de prédire quand l'instance sera supprimée de la mémoire de l'ordinateur. Par conséquent, si un objet String contient des informations sensibles telles qu'un mot de passe, un numéro de carte de crédit ou des données personnelles, les informations risquent d'être révélées après leur utilisation car votre application ne peut pas supprimer les données de la mémoire de l'ordinateur.

Toutefois, dans le cas d’une application graphique (par exemple, un client ssh), le SecureStringdoit être construit à partir deSystem.String. Tous les contrôles de texte utilisez une chaîne comme type de données sous-jacent}.

Cela signifie donc que chaque fois que l'utilisateur appuie sur une touche, l'ancienne chaîne qui était là est ignorée et une nouvelle chaîne est créée pour représenter la valeur dans la zone de texte, même si un masque de mot de passe est utilisé. Et nous ne pouvons pas contrôler quand ou si l’une de ces valeurs est supprimée de la mémoire.

Il est maintenant temps de vous connecter au serveur. Devine quoi? _ {Vous devez passer une chaîne sur la connexion pour l'authentification}. Convertissons donc notre SecureString en un System.String.... et nous avons maintenant une chaîne sur le tas sans le forcer à passer par le garbage collection (ou écrire les 0 dans son tampon).

Mon point est : peu importe ce que vous faites, quelque part sur la ligne, que SecureString signifie que va soit converti en un System.String, ce qui signifie qu'il existera au moins dans le tas à un moment donné (sans toute garantie de ramassage des ordures).

Mon point n'est pas : s'il existe des moyens de contourner l'envoi d'une chaîne à une connexion ssh ou d'éviter de faire en sorte qu'un contrôle enregistre une chaîne (crée un contrôle personnalisé). Pour cette question, vous pouvez remplacer "connexion ssh" par "formulaire de connexion", "formulaire d'inscription", "formulaire de paiement", "formulaire de nourriture pour nourrir votre chiot, mais pas vos enfants", etc.

  • Donc, à quel moment utiliser un SecureString devient-il réellement pratique?
  • Vaut-il jamais la peine du temps de développement supplémentaire pour éradiquer complètement l'utilisation d'un objet System.String?
  • Le point entier de SecureString est-il simplement de réduire la durée pendant laquelle un System.String est sur le tas (ce qui réduit le risque de passer à un fichier d'échange physique)?
  • Si un attaquant a déjà les moyens de procéder à une inspection de tas, alors il est fort probable que soit (A) ait déjà les moyens de lire les frappes au clavier, soit (B) déjà possède physiquement la machine... SecureString l'empêche-t-il d'accéder aux données de toute façon?
  • Est-ce juste "la sécurité par l'obscurité"?

Désolé si je pose des questions trop épaisses, la curiosité vient de prendre le dessus sur moi. N'hésitez pas à répondre à toutes mes questions (ou à me dire que mes hypothèses sont complètement fausses). :)

205
Steven Jeffries

Il existe en fait des utilisations très pratiques de SecureString

Savez-vous combien de fois j'ai vu de tels scénarios? (la réponse est: beaucoup!):

  • Un mot de passe apparaît accidentellement dans un fichier journal.
  • Un mot de passe est affiché quelque part - une fois que l'interface graphique affiche une ligne de commande de l'application en cours d'exécution et que la ligne de commande est composée d'un mot de passe. Oops.
  • Utilisation du profileur de mémoire pour profiler le logiciel avec votre collègue. Votre collègue voit votre mot de passe en mémoire. Cela semble irréel? Pas du tout.
  • Une fois, j’ai utilisé un logiciel RedGate capable de saisir la "valeur" des variables locales en cas d’exception, ce qui est extrêmement utile. Cependant, je peux imaginer qu’il enregistrera accidentellement des «mots de passe de chaîne».
  • Un vidage sur incident comprenant un mot de passe de chaîne.

Savez-vous comment éviter tous ces problèmes? SecureString. Cela permet généralement de ne pas commettre d'erreurs stupides en tant que telles. Comment ça l'évite? En vous assurant que le mot de passe est crypté dans la mémoire non gérée et que vous ne pouvez accéder à la valeur réelle que lorsque vous êtes sûr à 90% de ce que vous faites.

En ce sens, SecureString fonctionne assez facilement:

1) Tout est crypté

2) Appels des utilisateurs AppendChar

3) Tout déchiffrer dans la mémoire non gérée et ajouter le caractère

4) Cryptez à nouveau tout dans la mémoire non gérée.

Que se passe-t-il si l'utilisateur a accès à votre ordinateur? Un virus pourrait-il avoir accès à toutes les SecureStrings? Oui. Tout ce que vous avez à faire est de vous connecter à RtlEncryptMemory lors du déchiffrement de la mémoire, vous obtiendrez l'emplacement de l'adresse de la mémoire non chiffrée et vous la lirez. Voila! En fait, vous pourriez créer un virus qui analysera en permanence l'utilisation de SecureString et consignera toutes les activités qu'il contient. Je ne dis pas que ce sera une tâche facile, mais cela peut être fait. Comme vous pouvez le constater, la "puissance" de SecureString a complètement disparu dès qu’un utilisateur/virus se trouve sur votre système.

Vous avez quelques points dans votre post. Bien sûr, si vous utilisez certains des contrôles de l'interface utilisateur qui contiennent un "mot de passe de chaîne" en interne, l'utilisation de la variable SecureString n'est pas très utile. Néanmoins, cela peut quand même vous protéger contre certaines des stupidités que j'ai énumérées ci-dessus. 

De plus, comme d'autres l'ont noté, WPF prend en charge PasswordBox qui utilise SecureString en interne par le biais de sa propriété SecurePassword.

La ligne du bas est; Si vous avez des données sensibles (mots de passe, cartes de crédit, ..), utilisez SecureString. C’est ce que C # Framework suit. Par exemple, la classe NetworkCredential stocke le mot de passe sous la forme SecureString. Si vous regardez this , vous pouvez voir plus de 80 utilisations différentes dans .NET Framework of SecureString.

Il existe de nombreux cas dans lesquels vous devez convertir SecureString en chaîne, car certaines API l’attendent. 

Le problème habituel est soit: 

  1. L'API est GENERIC. Il ne sait pas qu'il y a des données sensibles. 
  2. L'API sait qu'elle traite des données sensibles et utilise "chaîne" - c'est juste une mauvaise conception.

Vous avez soulevé un bon point: que se passe-t-il lorsque SecureString est converti en string? Cela ne peut arriver qu'à cause du premier point. Par exemple. l'API ne sait pas que ce sont des données sensibles. Personnellement, je n'ai pas vu cela se produire. Obtenir des chaînes de SecureString n'est pas si simple. 

Ce n'est pas simple pour une raison simple; Il n’a jamais été prévu de laisser l’utilisateur convertir SecureString en chaîne, comme vous l’avez déclaré: GC entrera en jeu. Si vous vous voyez faire cela, vous devez prendre du recul et vous demander: pourquoi je fais cela, ou ai-je vraiment besoin C'est pourquoi?

Il y a un cas intéressant que j'ai vu. Notamment, la fonction WinApi LogonUser utilise LPTSTR comme mot de passe, ce qui signifie que vous devez appeler SecureStringToGlobalAllocUnicode. Cela vous donne essentiellement un mot de passe non crypté qui vit dans une mémoire non gérée. Vous devez vous en débarrasser dès que vous avez terminé:

// Marshal the SecureString to unmanaged memory.
IntPtr rawPassword = Marshal.SecureStringToGlobalAllocUnicode(password);
try
{
   //...snip...
}
finally 
{
   // Zero-out and free the unmanaged string reference.
   Marshal.ZeroFreeGlobalAllocUnicode(rawPassword);
}

Vous pouvez toujours étendre la classe SecureString avec une méthode d'extension, telle que ToEncryptedString(__SERVER__PUBLIC_KEY), qui vous donne une instance string de SecureString qui est chiffrée à l'aide de la clé publique du serveur. Seul le serveur peut alors le déchiffrer. Problème résolu: Garbage Collection ne verra jamais la chaîne "originale", car vous ne l'exposez jamais dans la mémoire gérée. C'est exactement ce qui est fait dans PSRemotingCryptoHelper (EncryptSecureStringCore(SecureString secureString)).

Et comme quelque chose de très proche: Mono SecureString ne chiffre pas du tout. L'implémentation a été commentée parce que ..attends-la .. "Cela provoque en quelque sorte la rupture du test nunit" , ce qui m'amène à mon dernier point:

SecureString n'est pas pris en charge partout. Si la plate-forme/architecture ne prend pas en charge SecureString, vous obtiendrez une exception. Il existe une liste de plates-formes prises en charge dans la documentation. 

221

Les quelques problèmes dans vos hypothèses. 

Tout d’abord, la classe SecureString n’a pas de constructeur String. Pour en créer un, vous allouez un objet, puis vous ajoutez les caractères. 

Dans le cas d'une interface graphique ou d'une console, vous pouvez très facilement transmettre chaque touche enfoncée à une chaîne sécurisée. 

La classe est conçue de manière à ce que vous ne puissiez pas, par erreur, accéder à la valeur stockée. Cela signifie que vous ne pouvez pas obtenir la variable string directement à partir de celui-ci. 

Ainsi, pour l'utiliser, par exemple, pour l'authentification sur le Web, vous devrez utiliser les classes appropriées, qui sont également sécurisées. 

Dans le framework .NET, quelques classes peuvent utiliser SecureString 

  • Le contrôle PasswordBox de WPF conserve le mot de passe en tant que SecureString en interne.
  • La propriété Password de System.Diagnostics.ProcessInfo est un SecureString.
  • Le constructeur de X509Certificate2 prend un SecureString pour le mot de passe. 

(plus)

En conclusion, la classe SecureString peut être utile, mais requiert une plus grande attention de la part du développeur. 

Tout cela, avec des exemples, est bien décrit dans la documentation MSDN de SecureString

Un SecureString est utile si:

  • Vous le construisez caractère par caractère (par exemple à partir d'une entrée de console) ou vous l'obtenez à partir d'une API non gérée

  • Vous l'utilisez en le transmettant à une API non gérée (SecureStringToBSTR).

Si vous le convertissez un jour en une chaîne gérée, vous avez déjoué son objectif.

UPDATE en réponse au commentaire

... ou BSTR comme vous le mentionnez, ce qui ne semble pas plus sûr

Une fois converti en BSTR, le composant non géré qui l'utilise peut mettre à zéro la mémoire. La mémoire non gérée est plus sécurisée dans le sens où elle peut être réinitialisée de cette façon.

Cependant, il existe très peu d'API dans .NET Framework qui prennent en charge SecureString. Vous avez donc raison de dire que sa valeur aujourd'hui est très limitée.

Le principal cas d'utilisation que je verrais se trouve dans une application cliente qui demande à l'utilisateur de saisir un code ou un mot de passe extrêmement sensible. L'entrée utilisateur peut être utilisée caractère par caractère pour créer une chaîne SecureString, puis transmise à une API non gérée, qui met à zéro le BSTR qu'il reçoit après l'avoir utilisée. Toute sauvegarde de mémoire ultérieure ne contiendra pas la chaîne sensible.

Dans une application serveur, il est difficile de voir où cela pourrait être utile.

UPDATE 2

Un exemple d'API .NET qui accepte un SecureString est ce constructeur pour la classe X509Certificate . Si vous utilisez Spelunk avec ILSpy ou similaire, vous constaterez que SecureString est converti en interne en un tampon non géré (Marshal.SecureStringToGlobalAllocUnicode), qui est ensuite mis à zéro lorsque vous avez terminé avec (Marshal.ZeroFreeGlobalAllocUnicode). 

10
Joe

Comme vous l'avez correctement identifié, SecureString offre un avantage spécifique par rapport à string: effacement déterministe. Il y a deux problèmes avec ce fait: 

  1. Comme d'autres l'ont mentionné et comme vous l'avez constaté vous-même, cela ne suffit pas en soi. Vous devez vous assurer que chaque étape du processus (y compris la récupération des entrées, la construction de la chaîne, l'utilisation, la suppression, le transport, etc.) se déroule sans compromettre le but d'utiliser SecureString. Cela signifie que vous devez faire attention à ne jamais créer une string immuable gérée par le GC, ni aucun autre tampon qui stockera les informations sensibles (sinon vous devrez également garder la trace de that). En pratique, cela n’est pas toujours facile à réaliser, car de nombreuses API offrent uniquement un moyen de travailler avec string, pas SecureString. Et même si vous réussissez à tout faire correctement ... 
  2. SecureString protège contre des types d'attaques très spécifiques (et pour certains, ce n'est même pas fiable). Par exemple, SecureString vous permet de réduire la fenêtre temporelle dans laquelle un attaquant peut vider la mémoire de votre processus et extraire avec succès les informations sensibles (encore une fois, comme vous l'avez correctement indiqué), mais en espérant que la fenêtre est trop petite pour lui. prendre un instantané de votre mémoire n'est pas du tout considéré comme une sécurité.

Alors, quand devriez-vous l'utiliser? Ce n’est que lorsque vous travaillez avec quelque chose qui peut vous permettre de travailler avec SecureString pour tous vos besoins et même dans ce cas, vous devez toujours garder à l’esprit que cela n’est sécurisé que dans des circonstances spécifiques.

J'aimerais aborder ce point:

Si un attaquant a déjà les moyens de procéder à une inspection de tas, il est fort probable qu'il (A) ait déjà les moyens de lire des frappes au clavier ou bien (B) déjà possède physiquement la machine... SecureString les empêche-t-il de toute façon d'accéder aux données?

Un attaquant peut ne pas avoir un accès complet à l'ordinateur et à l'application, mais peut également disposer de moyens pour accéder à certaines parties de la mémoire du processus. Cela est généralement dû à des bogues tels que les dépassements de mémoire tampon lorsque des entrées spécialement construites peuvent amener l'application à exposer ou à écraser une partie de la mémoire.

Fuite de mémoire dans le cœur

Prenez Heartbleed par exemple. Des requêtes spécialement conçues peuvent amener le code à exposer des parties aléatoires de la mémoire du processus à l'attaquant. Un attaquant peut extraire des certificats SSL de la mémoire, mais la seule chose dont il a besoin est simplement d’utiliser une requête malformée.

Dans le monde du code géré, les dépassements de tampon deviennent un problème beaucoup moins souvent. Et dans le cas de WinForm, les données sont déjà stockées de manière non sécurisée et vous ne pouvez rien y faire. Cela rend la protection avec SecureString pratiquement inutile.

Cependant, l'interface graphique peut peut être programmée pour utiliser SecureString, et dans ce cas, réduire la fenêtre de disponibilité du mot de passe dans la mémoire peut en valoir la peine. Par exemple, PasswordBox.SecurePassword de WPF est de type SecureString.

3
Athari

Le texte ci-dessous est copié à partir de l'analyseur de code statique HP Fortify

Résumé: La méthode PassString () dans PassGenerator.cs stocke les données sensibles de manière non sécurisée (c'est-à-dire dans une chaîne), permettant d'extraire Les données via l'inspection du tas.

Explication: Des données sensibles (telles que des mots de passe, des numéros de sécurité sociale, des numéros de cartes de crédit, etc.) stockées en mémoire peuvent être perdues si elles le sont Stockées dans un objet String géré. Les objets de type chaîne ne sont pas épinglés. Le ramasse-miettes peut ainsi déplacer ces objets à volonté et laisser plusieurs copies en mémoire. Par défaut, ces objets ne sont pas chiffrés. Ainsi, toute personne pouvant lire la mémoire du processus sera capable de voir le contenu. De plus, si la mémoire du processus est permutée sur le disque, le contenu non chiffré de la chaîne Sera écrit dans un fichier d'échange. Enfin, comme les objets String sont immuables, la suppression de la valeur d’une chaîne de la mémoire ne peut être effectuée que par le récupérateur de mémoire CLR. Le ramasse-miettes n'est pas requis pour s'exécuter à moins que le CLR ne manque de mémoire, il n'y a donc aucune aucune garantie quant au moment où le ramassage des ordures aura lieu. En cas de blocage de l'application, un vidage de mémoire de l'application Pourrait révéler des données sensibles.

Recommandations: .__ Au lieu de stocker des données sensibles dans des objets tels que Strings, stockez-les dans un objet SecureString. Chaque objet stocke son contenu dans un format crypté En mémoire à tout moment.

2
vikrantx

Il y a quelque temps, je devais créer une interface c # contre une passerelle de paiement par carte de crédit Java, qui nécessitait un cryptage des clés de communication sécurisées compatibles. Comme l'implémentation Java était assez spécifique et que je devais travailler avec les données protégées d'une manière donnée. 

J'ai trouvé cette conception assez facile à utiliser et plus simple que de travailler avec SecureString… pour ceux qui aiment utiliser… sentez-vous gratuitement, sans restrictions légales :-). Notez que ces classes sont internes, vous devrez peut-être les rendre publiques.

namespace Cardinity.Infrastructure
{
    using System.Security.Cryptography;
    using System;
    enum EncryptionMethods
    {
        None=0,
        HMACSHA1,
        HMACSHA256,
        HMACSHA384,
        HMACSHA512,
        HMACMD5
    }


internal class Protected
{
    private  Byte[] salt = Guid.NewGuid().ToByteArray();

    protected byte[] Protect(byte[] data)
    {
        try
        {
            return ProtectedData.Protect(data, salt, DataProtectionScope.CurrentUser);
        }
        catch (CryptographicException)//no reason for hackers to know it failed
        {
#if DEBUG
            throw;
#else
            return null;
#endif
        }
    }

    protected byte[] Unprotect(byte[] data)
    {
        try
        {
            return ProtectedData.Unprotect(data, salt, DataProtectionScope.CurrentUser);
        }
        catch (CryptographicException)//no reason for hackers to know it failed
        {
#if DEBUG
            throw;
#else
            return null;
#endif
        }
    }
}


    internal class SecretKeySpec:Protected,IDisposable
    {
        readonly EncryptionMethods _method;

        private byte[] _secretKey;
        public SecretKeySpec(byte[] secretKey, EncryptionMethods encryptionMethod)
        {
            _secretKey = Protect(secretKey);
            _method = encryptionMethod;
        }

        public EncryptionMethods Method => _method;
        public byte[] SecretKey => Unprotect( _secretKey);

        public void Dispose()
        {
            if (_secretKey == null)
                return;
            //overwrite array memory
            for (int i = 0; i < _secretKey.Length; i++)
            {
                _secretKey[i] = 0;
            }

            //set-null
            _secretKey = null;
        }
        ~SecretKeySpec()
        {
            Dispose();
        }
    }

    internal class Mac : Protected,IDisposable
    {
        byte[] rawHmac;
        HMAC mac;
        public Mac(SecretKeySpec key, string data)
        {

            switch (key.Method)
            {
                case EncryptionMethods.HMACMD5:
                    mac = new HMACMD5(key.SecretKey);
                    break;
                case EncryptionMethods.HMACSHA512:
                    mac = new HMACSHA512(key.SecretKey);
                    break;
                case EncryptionMethods.HMACSHA384:
                    mac = new HMACSHA384(key.SecretKey);
                    break;
                case EncryptionMethods.HMACSHA256:
                    mac = new HMACSHA256(key.SecretKey);

                break;
                case EncryptionMethods.HMACSHA1:
                    mac = new HMACSHA1(key.SecretKey);
                    break;

                default:                    
                    throw new NotSupportedException("not supported HMAC");
            }
            rawHmac = Protect( mac.ComputeHash(Cardinity.ENCODING.GetBytes(data)));            

        }

        public string AsBase64()
        {
            return System.Convert.ToBase64String(Unprotect(rawHmac));
        }

        public void Dispose()
        {
            if (rawHmac != null)
            {
                //overwrite memory address
                for (int i = 0; i < rawHmac.Length; i++)
                {
                    rawHmac[i] = 0;
                }

                //release memory now
                rawHmac = null;

            }
            mac?.Dispose();
            mac = null;

        }
        ~Mac()
        {
            Dispose();
        }
    }
}

Microsoft déconseille d'utiliser SecureString pour les codes plus récents.

De la documentation de SecureString Class :

Important

Nous vous déconseillons d'utiliser la classe SecureString pour les nouveaux développements. Pour plus d'informations, voir SecureString ne doit pas être utilisé

Qui recommande:

N'utilisez pas SecureString pour le nouveau code. Lors du portage de code vers .NET Core, considérez que le contenu de la matrice n'est pas chiffré en mémoire.

L'approche générale consistant à traiter les informations d'identification consiste à les éviter et à recourir à d'autres moyens pour s'authentifier, tels que les certificats ou l'authentification Windows. sur GitHub.

0
SᴇM