Wie speichere ich Benutzername / Passwort (lokal) sicher?

106

Ich mache eine Windows-Anwendung, bei der Sie sich zuerst anmelden müssen.
Die Kontodaten bestehen aus Benutzername und Passwort und müssen lokal gespeichert werden.
Es ist nur eine Frage der Sicherheit, sodass andere Personen, die denselben Computer verwenden, nicht alle persönlichen Daten sehen können.
Was ist der beste / sicherste Weg, um diese Daten zu speichern?

Ich möchte keine Datenbank verwenden, deshalb habe ich einige Dinge mit Ressourcendateien versucht.
Aber da ich damit ein bisschen neu bin, bin ich mir nicht ganz sicher, was ich tue und wo ich nach einer Lösung suchen sollte.

Robin
quelle
6
Speichern Sie zunächst nicht das Passwort. Hash es (möglicherweise mit einem Salzwert) und speichere das stattdessen.
Carlosfigueira
"Benutzer" meinen Sie normale Windows-Benutzer oder etwas anderes? (Ich denke, Sie meinen, einige von Ihnen besitzen "Benutzer", da normale Windows-Benutzer die Daten des anderen bereits nicht sehen können ...)
Alexei Levenkov
Ich habe Ihren Titel bearbeitet. Weitere Informationen finden Sie unter " Sollten Fragen" Tags "in ihren Titeln enthalten? ", Wo der Konsens "Nein, sollten sie nicht" lautet.
John Saunders
@ John Saunders Okay, entschuldigen Sie meine Unwissenheit.
Robin
2
Gibt es eine endgültige Lösung mit vollständigem Quellcode?
Kiquenet

Antworten:

160

Wenn Sie nur den eingegebenen Benutzernamen und das eingegebene Kennwort überprüfen / validieren möchten , verwenden Sie die Klasse Rfc2898DerivedBytes (auch als kennwortbasierte Schlüsselableitungsfunktion 2 oder PBKDF2 bezeichnet). Dies ist sicherer als die Verwendung von Verschlüsselung wie Triple DES oder AES, da es keinen praktischen Weg gibt, vom Ergebnis von RFC2898DerivedBytes zum Kennwort zurückzukehren. Sie können nur von einem Passwort zum Ergebnis wechseln. Siehe Ist es in Ordnung, den SHA1-Passwort-Hash als Salt zu verwenden, wenn Verschlüsselungsschlüssel und IV aus der Passwortzeichenfolge abgeleitet werden? Ein Beispiel und eine Diskussion für .Net oder String zum Ver- und Entschlüsseln mit dem Kennwort c # Metro Style für WinRT / Metro.

Wenn Sie das Kennwort zur Wiederverwendung speichern, z. B. zur Weitergabe an Dritte, verwenden Sie die Windows-Datenschutz-API (DPAPI) . Hierbei werden vom Betriebssystem generierte und geschützte Schlüssel sowie der Triple DES- Verschlüsselungsalgorithmus zum Ver- und Entschlüsseln von Informationen verwendet. Dies bedeutet, dass sich Ihre Anwendung nicht um das Generieren und Schützen der Verschlüsselungsschlüssel kümmern muss. Dies ist ein wichtiges Anliegen bei der Verwendung von Kryptografie.

Verwenden Sie in C # die Klasse System.Security.Cryptography.ProtectedData . Verwenden Sie zum Verschlüsseln von Daten beispielsweise Folgendes 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);

Speichern Sie die Entropie und den Chiffretext sicher, z. B. in einer Datei oder einem Registrierungsschlüssel mit festgelegten Berechtigungen, sodass nur der aktuelle Benutzer sie lesen kann. Verwenden Sie Folgendes, um Zugriff auf die Originaldaten zu erhalten ProtectedData.Unprotect():

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

Beachten Sie, dass es zusätzliche Sicherheitsaspekte gibt. Vermeiden Sie beispielsweise das Speichern von Geheimnissen wie Passwörtern als string. Zeichenfolgen sind unveränderlich, da sie im Speicher nicht benachrichtigt werden können, sodass jemand, der sich den Speicher der Anwendung oder einen Speicherauszug ansieht, möglicherweise das Kennwort sieht. Verwenden Sie stattdessen SecureString oder ein Byte [] und denken Sie daran, diese zu entsorgen oder auf Null zu setzen, sobald das Kennwort nicht mehr benötigt wird.

akton
quelle
Hallo, ich habe es versucht, aber ich habe einen Fehler bei entropy = rng.GetBytes (20) erhalten, der besagt: Kann nicht von int in byte konvertieren []
Robin
@CrispyGMR Ich habe diesen Code in der Antwort behoben. Guter Fang.
Akton
Vielen Dank. Ich habe md5 zuerst zum Hashing verwendet, war aber etwas skeptisch. Dies scheint viel sicherer. Noch eine Frage. Ich möchte einige dieser Daten in einer Textdatei speichern. Ich sehe, dass es nur ein paar zufällige Zeichen sind, wenn ich meine Datei öffne, aber ist es sicher genug, dies zu tun? Oder empfehlen Sie eine andere Art der Speicherung der Daten?
Robin
2
Die Klasse heißt jetzt Rfc2898DeriveBytes (Kleinbuchstaben, .net 4.5 und 4.6) und ist hier zu finden: Namespace: System.Security.Cryptography Assembly: mscorlib (in mscorlib.dll)
Dashu
2
Sehr informativ, aber ich denke, der Sinn der Verwendung ProtectedDatabesteht darin, dass ich mir keine Sorgen machen muss, ob die Entropie und der Chiffretext sicher gespeichert werden sollen, sodass nur der aktuelle Benutzer sie lesen kann . Ich denke, es bietet Einfachheit, indem ich sie speichern kann, aber es ist bequem und trotzdem kann nur der CurrentUser es entschlüsseln. Der entropyParameter ist ebenfalls optional und ähnelt einer IV, bei der die Eindeutigkeit wichtiger ist als die Geheimhaltung. Daher könnte der Wert in Situationen, in denen die Variation und Aktualisierung von Klartext selten vorkommt, wahrscheinlich weggelassen oder fest im Programm codiert werden.
Antak
8

Ich habe dies schon einmal verwendet und denke, um sicherzustellen, dass die Anmeldeinformationen bestehen bleiben und dies auf die sicherste Art und Weise ist

  1. Sie können sie mit der ConfigurationManagerKlasse in die App-Konfigurationsdatei schreiben
  2. Sichern des Passworts mit der SecureStringKlasse
  3. Verschlüsseln Sie es dann mit Tools im CryptographyNamespace.

Dieser Link wird hoffentlich eine große Hilfe sein: Klicken Sie hier

Pradip
quelle
4

DPAPI ist nur für diesen Zweck. Verwenden Sie DPAPI, um das Kennwort zu verschlüsseln, wenn der Benutzer es zum ersten Mal eingibt, und speichern Sie es an einem sicheren Ort (die Registrierung des Benutzers, das Anwendungsdatenverzeichnis des Benutzers, sind einige Optionen). Überprüfen Sie bei jedem Start der App den Speicherort, um festzustellen, ob Ihr Schlüssel vorhanden ist. Wenn DPAPI zum Entschlüsseln und Zulassen des Zugriffs verwendet wird, verweigern Sie ihn andernfalls.

swiftgp
quelle
1

Ich wollte die Zeichenfolge als lesbare Zeichenfolge verschlüsseln und entschlüsseln.

Hier ist ein sehr einfaches Beispiel in C # Visual Studio 2019 WinForms basierend auf der Antwort von @Pradip.

Klicken Sie mit der rechten Maustaste auf Projekt> Eigenschaften> Einstellungen> Erstellen usernameund passwordEinstellung.

Geben Sie hier die Bildbeschreibung ein

Jetzt können Sie die soeben erstellten Einstellungen nutzen. Hier speichere ich das usernameund passwordverschlüssele aber nur das passwordin seinem respektablen Wertfeld in der user.configDatei.

Beispiel für die verschlüsselte Zeichenfolge in der user.configDatei.

<?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>

Geben Sie hier die Bildbeschreibung ein

Vollständiger Code

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
quelle