Jak bezpiecznie zapisać nazwę użytkownika / hasło (lokalnie)?

106

Tworzę aplikację Windows, do której musisz się najpierw zalogować.
Szczegóły konta składają się z nazwy użytkownika i hasła i muszą być zapisane lokalnie.
To tylko kwestia bezpieczeństwa, więc inne osoby korzystające z tego samego komputera nie widzą danych osobowych wszystkich.
Jaki jest najlepszy / najbezpieczniejszy sposób zapisywania tych danych?

Nie chcę używać bazy danych, więc wypróbowałem kilka rzeczy z plikami zasobów.
Ale ponieważ jestem w tym trochę nowy, nie jestem do końca pewien, co robię i gdzie powinienem szukać rozwiązania.

Rudzik
źródło
6
Przede wszystkim nie zapisuj hasła. Zhaszuj to (prawdopodobnie z wartością soli) i zamiast tego zapisz.
carlosfigueira
„Użytkownicy”, masz na myśli zwykłych użytkowników systemu Windows czy coś innego? (Myślę, że niektórzy z was mają „użytkowników”, ponieważ zwykli użytkownicy systemu Windows już nie widzą swoich danych ...)
Alexei Levenkov
Zmieniłem twój tytuł. Zobacz: „ Czy pytania powinny zawierać„ tagi ”w tytułach? ”, Gdzie konsensus brzmi „nie, nie powinny”.
John Saunders,
@John Saunders W porządku, wybacz moją ignorancję.
Robin
2
jakieś ostateczne rozwiązanie z pełnym kodem źródłowym?
Kiquenet

Odpowiedzi:

160

Jeśli zamierzasz tylko zweryfikować / zweryfikować wprowadzoną nazwę użytkownika i hasło, użyj klasy Rfc2898DerivedBytes (znanej również jako funkcja 2 wyprowadzania klucza opartego na haśle lub PBKDF2). Jest to bezpieczniejsze niż użycie szyfrowania, takiego jak Triple DES lub AES, ponieważ nie ma praktycznego sposobu, aby przejść od wyniku RFC2898DerivedBytes z powrotem do hasła. Możesz przejść tylko od hasła do wyniku. Zobacz Czy można używać skrótu SHA1 hasła jako soli podczas wyprowadzania klucza szyfrowania i IV z ciągu hasła? na przykład i dyskusja dla .Net lub String szyfruj / odszyfruj z hasłem c # Metro Style dla WinRT / Metro.

Jeśli przechowujesz hasło w celu ponownego użycia, na przykład przekazania go stronie trzeciej, użyj interfejsu API ochrony danych systemu Windows (DPAPI) . Wykorzystuje on wygenerowane i chronione przez system operacyjny klucze oraz algorytm szyfrowania Triple DES do szyfrowania i odszyfrowywania informacji. Oznacza to, że Twoja aplikacja nie musi martwić się o generowanie i ochronę kluczy szyfrowania, co jest głównym problemem podczas korzystania z kryptografii.

W języku C # użyj klasy System.Security.Cryptography.ProtectedData . Na przykład, aby zaszyfrować fragment danych, użyj 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);

Przechowuj bezpiecznie entropię i szyfrogram, na przykład w pliku lub kluczu rejestru z ustawionymi uprawnieniami, aby tylko bieżący użytkownik mógł je odczytać. Aby uzyskać dostęp do oryginalnych danych, użyj ProtectedData.Unprotect():

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

Zwróć uwagę, że istnieją dodatkowe względy bezpieczeństwa. Na przykład unikaj przechowywania sekretów, takich jak hasła, jako plik string. Ciągi znaków są niezmienne, ponieważ nie można ich powiadamiać w pamięci, więc ktoś przeglądający pamięć aplikacji lub zrzut pamięci może zobaczyć hasło. Zamiast tego użyj SecureString lub byte [] i pamiętaj, aby je usunąć lub wyzerować, gdy tylko hasło nie będzie już potrzebne.

akton
źródło
Cześć, próbowałem tego, ale pojawił się błąd przy entropii = rng.GetBytes (20) mówiący: Nie można przekonwertować z int na bajt []
Robin
@CrispyGMR Poprawiłem ten fragment kodu w odpowiedzi. Dobry chwyt.
akton
Wielkie dzięki. Na początku używałem md5 do mieszania, ale byłem do tego trochę sceptyczny. Wydaje się to o wiele bezpieczniejsze. Jeszcze jedno pytanie. Chcę zapisać sporo takich danych w pliku tekstowym. Widzę, że to tylko kilka przypadkowych znaków, kiedy otwieram plik, ale czy jest to wystarczająco bezpieczne, aby to zrobić? A może zalecasz inny sposób przechowywania danych?
Robin
2
Wygląda na to, że klasa jest teraz znana jako Rfc2898DeriveBytes (małe litery, .net 4.5 i 4.6) i można ją znaleźć tutaj: Przestrzeń nazw: System.Security.Cryptography Assembly: mscorlib (w mscorlib.dll)
Dashu
2
Bardzo pouczające, jednak myślę, że cały sens używania ProtectedDatajest taki, że nie muszę się martwić o bezpieczne przechowywanie entropii i szyfrogramu, ... więc tylko bieżący użytkownik może je odczytać . Myślę, że oferuje prostotę, ponieważ mogę je przechowywać, ale jest wygodny i nadal tylko CurrentUser może je odszyfrować. entropyParametr jest opcjonalny i pojawia się podobny do IV, w którym liczy się bardziej niż wyjątkowość tajemnicy. W związku z tym wartość mogłaby prawdopodobnie zostać pominięta lub na stałe zakodowana w programie w sytuacjach, w których zmiana i aktualizacja tekstu jawnego są rzadkie.
antak
8

Używałem tego wcześniej i myślę, że aby upewnić się, że poświadczenia są trwałe i w jak najbardziej bezpieczny sposób

  1. możesz zapisać je w pliku konfiguracyjnym aplikacji za pomocą ConfigurationManagerklasy
  2. zabezpieczenie hasła za pomocą SecureStringklasy
  3. następnie zaszyfruj go za pomocą narzędzi w Cryptographyprzestrzeni nazw.

Mam nadzieję, że ten link będzie bardzo pomocny: Kliknij tutaj

Pradip
źródło
4

DPAPI jest właśnie do tego celu. Użyj DPAPI do zaszyfrowania hasła przy pierwszym wejściu użytkownika, przechowuj je w bezpiecznej lokalizacji (rejestr użytkownika, katalog danych aplikacji użytkownika, to tylko niektóre opcje). Za każdym razem, gdy aplikacja jest uruchamiana, sprawdź lokalizację, aby zobaczyć, czy twój klucz istnieje, jeśli używa DPAPI do odszyfrowania go i zezwolenia na dostęp, w przeciwnym razie odmów.

swiftgp
źródło
1

Chciałem zaszyfrować i odszyfrować ciąg jako czytelny ciąg.

Oto bardzo prosty, szybki przykład w C # Visual Studio 2019 WinForms oparty na odpowiedzi z @Pradip.

Kliknij prawym przyciskiem myszy projekt> właściwości> ustawienia> Utwórz usernamei passwordustawienie.

wprowadź opis obrazu tutaj

Teraz możesz wykorzystać właśnie utworzone ustawienia. Tutaj zapisuję usernamei passwordszyfruję tylko passwordw odpowiednim polu wartości w user.configpliku.

Przykład zaszyfrowanego ciągu znaków w user.configpliku.

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

wprowadź opis obrazu tutaj

Pełny kod

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
źródło