Comment enregistrer en toute sécurité le nom d'utilisateur / mot de passe (local)?

106

Je crée une application Windows, à laquelle vous devez d'abord vous connecter.
Les détails du compte se composent du nom d'utilisateur et du mot de passe, et ils doivent être enregistrés localement.
C'est juste une question de sécurité, donc les autres personnes utilisant le même ordinateur ne peuvent pas voir les données personnelles de tout le monde.
Quelle est la meilleure / la plus sûre façon de sauvegarder ces données?

Je ne veux pas utiliser de base de données, j'ai donc essayé certaines choses avec les fichiers de ressources.
Mais comme je suis un peu nouveau avec ça, je ne suis pas tout à fait sûr de ce que je fais et où je devrais chercher une solution.

Robin
la source
6
Tout d'abord, ne sauvegardez pas le mot de passe. Hachez-le (éventuellement avec une valeur de sel) et enregistrez-le à la place.
carlosfigueira
"Utilisateurs", vous voulez dire les utilisateurs réguliers de Windows ou autre chose? (Je pense que vous voulez dire que certains d'entre vous possèdent des "utilisateurs" car les utilisateurs réguliers de Windows ne peuvent déjà pas voir les données de l'autre ...)
Alexei Levenkov
J'ai édité votre titre. Veuillez consulter « Les questions devraient-elles inclure des« balises »dans leurs titres? », Où le consensus est «non, elles ne devraient pas».
John Saunders
@John Saunders Très bien, excusez mon ignorance.
Robin
2
une solution finale avec le code source complet?
Kiquenet le

Réponses:

160

Si vous souhaitez simplement vérifier / valider le nom d'utilisateur et le mot de passe saisis , utilisez la classe Rfc2898DerivedBytes (également appelée fonction de dérivation de clé basée sur le mot de passe 2 ou PBKDF2). C'est plus sûr que d'utiliser un cryptage comme Triple DES ou AES car il n'y a pas de moyen pratique de passer du résultat de RFC2898DerivedBytes au mot de passe. Vous ne pouvez passer que d'un mot de passe au résultat. Voir Est-il acceptable d'utiliser le hachage SHA1 du mot de passe comme sel lors de la dérivation de la clé de chiffrement et IV de la chaîne de mot de passe? pour un exemple et une discussion pour .Net ou String crypter / décrypter avec le mot de passe c # Metro Style pour WinRT / Metro.

Si vous stockez le mot de passe pour le réutiliser, par exemple le fournir à un tiers, utilisez l' API de protection des données Windows (DPAPI) . Cela utilise les clés générées et protégées par le système d'exploitation et l' algorithme de cryptage Triple DES pour crypter et décrypter les informations. Cela signifie que votre application n'a pas à se soucier de la génération et de la protection des clés de chiffrement, une préoccupation majeure lors de l'utilisation de la cryptographie.

En C #, utilisez la classe System.Security.Cryptography.ProtectedData . Par exemple, pour crypter une donnée, utilisez ProtectedData.Protect():

// Data to protect. Convert a string to a byte[] using Encoding.UTF8.GetBytes().
byte[] plaintext; 

// Generate additional entropy (will be used as the Initialization vector)
byte[] entropy = new byte[20];
using(RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
{
    rng.GetBytes(entropy);
}

byte[] ciphertext = ProtectedData.Protect(plaintext, entropy,
    DataProtectionScope.CurrentUser);

Stockez l'entropie et le texte chiffré en toute sécurité, par exemple dans un fichier ou une clé de registre avec des autorisations définies afin que seul l'utilisateur actuel puisse le lire. Pour accéder aux données d'origine, utilisez ProtectedData.Unprotect():

byte[] plaintext= ProtectedData.Unprotect(ciphertext, entropy,
    DataProtectionScope.CurrentUser);

Notez qu'il existe des considérations de sécurité supplémentaires. Par exemple, évitez de stocker des secrets comme des mots de passe sous forme de fichier string. Les chaînes sont immuables, car elles ne peuvent pas être notifiées en mémoire afin que quelqu'un qui regarde la mémoire de l'application ou une image mémoire puisse voir le mot de passe. Utilisez plutôt SecureString ou un octet [] et n'oubliez pas de les supprimer ou de les remettre à zéro dès que le mot de passe n'est plus nécessaire.

Akton
la source
Salut, j'ai essayé cela, mais j'ai eu une erreur à entropy = rng.GetBytes (20) disant: Impossible de convertir de int en octet []
Robin
@CrispyGMR J'ai corrigé ce morceau de code dans la réponse. Bonne prise.
akton
Merci beaucoup. J'ai utilisé md5 pour le hachage au début, mais j'étais plutôt sceptique à ce sujet. Cela semble beaucoup plus sûr. Encore une question. Je veux enregistrer pas mal de données comme celle-ci dans un fichier texte. Je vois que ce n'est qu'un tas de caractères aléatoires lorsque j'ouvre mon fichier, mais est-il suffisamment sûr pour le faire? Ou recommandez-vous une autre façon de stocker les données?
Robin
2
Semble que la classe est maintenant connue sous le nom de Rfc2898DeriveBytes (minuscules, .net 4.5 et 4.6) et peut être trouvée ici: Namespace: System.Security.Cryptography Assembly: mscorlib (dans mscorlib.dll)
Dashu
2
Très informatif, cependant, je pense que tout l'intérêt de l'utilisation ProtectedDataest de ne pas avoir à m'inquiéter de Stocker l'entropie et le texte chiffré en toute sécurité, ... pour que seul l'utilisateur actuel puisse le lire . Je pense que cela offre de la simplicité dans la mesure où je peux les stocker, mais c'est pratique et seul le CurrentUser peut le déchiffrer. Le entropyparamètre est également facultatif et ressemble à un IV où l'unicité compte plus que le secret. En tant que telle, la valeur pourrait probablement être omise ou codée en dur dans le programme dans les situations où la variation et la mise à jour du texte en clair sont rares.
antak
8

Je l'ai déjà utilisé et je pense que pour m'assurer que les informations d'identification persistent et de la meilleure manière sécurisée

  1. vous pouvez les écrire dans le fichier de configuration de l'application en utilisant la ConfigurationManagerclasse
  2. sécurisation du mot de passe à l'aide de la SecureStringclasse
  3. puis le chiffrer à l'aide des outils de l' Cryptographyespace de noms.

Ce lien sera d'une grande aide j'espère: Cliquez ici

Pradip
la source
4

DPAPI est juste à cet effet. Utilisez DPAPI pour crypter le mot de passe la première fois que l'utilisateur entre, stockez-le dans un emplacement sécurisé (registre de l'utilisateur, répertoire de données d'application de l'utilisateur, sont quelques choix). Chaque fois que l'application est lancée, vérifiez l'emplacement pour voir si votre clé existe, si elle utilise DPAPI pour la déchiffrer et autoriser l'accès, sinon refusez-la.

swiftgp
la source
1

Je voulais crypter et décrypter la chaîne en tant que chaîne lisible.

Voici un exemple rapide très simple dans C # Visual Studio 2019 WinForms basé sur la réponse de @Pradip.

Cliquez avec le bouton droit sur projet> propriétés> paramètres> Créer un paramètre usernameet password.

entrez la description de l'image ici

Vous pouvez désormais exploiter les paramètres que vous venez de créer. Ici, je sauvegarde le usernameet passwordmais je ne crypte que le passwordchamp de valeur respectable du user.configfichier.

Exemple de chaîne cryptée dans le user.configfichier.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <userSettings>
        <secure_password_store.Properties.Settings>
            <setting name="username" serializeAs="String">
                <value>admin</value>
            </setting>
            <setting name="password" serializeAs="String">
                <value>AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAQpgaPYIUq064U3o6xXkQOQAAAAACAAAAAAAQZgAAAAEAACAAAABlQQ8OcONYBr9qUhH7NeKF8bZB6uCJa5uKhk97NdH93AAAAAAOgAAAAAIAACAAAAC7yQicDYV5DiNp0fHXVEDZ7IhOXOrsRUbcY0ziYYTlKSAAAACVDQ+ICHWooDDaUywJeUOV9sRg5c8q6/vizdq8WtPVbkAAAADciZskoSw3g6N9EpX/8FOv+FeExZFxsm03i8vYdDHUVmJvX33K03rqiYF2qzpYCaldQnRxFH9wH2ZEHeSRPeiG</value>
            </setting>
        </secure_password_store.Properties.Settings>
    </userSettings>
</configuration>

entrez la description de l'image ici

Code complet

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Security;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace secure_password_store
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Exit_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }

        private void Login_Click(object sender, EventArgs e)
        {
            if (checkBox1.Checked == true)
            {
                Properties.Settings.Default.username = textBox1.Text;
                Properties.Settings.Default.password = EncryptString(ToSecureString(textBox2.Text));
                Properties.Settings.Default.Save();
            }
            else if (checkBox1.Checked == false)
            {
                Properties.Settings.Default.username = "";
                Properties.Settings.Default.password = "";
                Properties.Settings.Default.Save();
            }
            MessageBox.Show("{\"data\": \"some data\"}","Login Message Alert",MessageBoxButtons.OK, MessageBoxIcon.Information);
        }
        private void DecryptString_Click(object sender, EventArgs e)
        {
            SecureString password = DecryptString(Properties.Settings.Default.password);
            string readable = ToInsecureString(password);
            textBox4.AppendText(readable + Environment.NewLine);
        }
        private void Form_Load(object sender, EventArgs e)
        {
            //textBox1.Text = "UserName";
            //textBox2.Text = "Password";
            if (Properties.Settings.Default.username != string.Empty)
            {
                textBox1.Text = Properties.Settings.Default.username;
                checkBox1.Checked = true;
                SecureString password = DecryptString(Properties.Settings.Default.password);
                string readable = ToInsecureString(password);
                textBox2.Text = readable;
            }
            groupBox1.Select();
        }


        static byte[] entropy = Encoding.Unicode.GetBytes("SaLtY bOy 6970 ePiC");

        public static string EncryptString(SecureString input)
        {
            byte[] encryptedData = ProtectedData.Protect(Encoding.Unicode.GetBytes(ToInsecureString(input)),entropy,DataProtectionScope.CurrentUser);
            return Convert.ToBase64String(encryptedData);
        }

        public static SecureString DecryptString(string encryptedData)
        {
            try
            {
                byte[] decryptedData = ProtectedData.Unprotect(Convert.FromBase64String(encryptedData),entropy,DataProtectionScope.CurrentUser);
                return ToSecureString(Encoding.Unicode.GetString(decryptedData));
            }
            catch
            {
                return new SecureString();
            }
        }

        public static SecureString ToSecureString(string input)
        {
            SecureString secure = new SecureString();
            foreach (char c in input)
            {
                secure.AppendChar(c);
            }
            secure.MakeReadOnly();
            return secure;
        }

        public static string ToInsecureString(SecureString input)
        {
            string returnValue = string.Empty;
            IntPtr ptr = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(input);
            try
            {
                returnValue = System.Runtime.InteropServices.Marshal.PtrToStringBSTR(ptr);
            }
            finally
            {
                System.Runtime.InteropServices.Marshal.ZeroFreeBSTR(ptr);
            }
            return returnValue;
        }

        private void EncryptString_Click(object sender, EventArgs e)
        {
            Properties.Settings.Default.password = EncryptString(ToSecureString(textBox2.Text));
            textBox3.AppendText(Properties.Settings.Default.password.ToString() + Environment.NewLine);
        }
    }
}
Jonas
la source