Як надійно зберегти ім'я користувача / пароль (локальний)?


106

Я роблю додаток для Windows, в яке потрібно спочатку увійти.
Дані облікового запису складаються з імені користувача та пароля, і їх потрібно зберігати локально.
Це лише питання безпеки, тому інші люди, що використовують той же комп'ютер, не можуть бачити особисті дані кожного.
Який найкращий / найбезпечніший спосіб збереження цих даних?

Я не хочу використовувати базу даних, тому я спробував деякі речі з файлами Resource.
Але оскільки я з цим щось нове, я не зовсім впевнений у тому, що роблю, і де мені слід шукати рішення.


6
Перш за все, не зберігайте пароль. Помістіть його (можливо, із сольовим значенням) і збережіть натомість.
carlosfigueira

"Користувачі" ви маєте на увазі звичайних користувачів Windows або щось інше? (Я думаю, ви маєте на увазі, що хтось із вас є "користувачами", оскільки звичайний користувач Windows вже не бачить даних один одного ...)
Олексій Левенков

Я відредагував вашу назву. Будь ласка, дивіться: " Чи повинні питання включати" теги "в їх заголовки? ", Де консенсус "ні, вони не повинні".
Джон Сондерс

@John Saunders Добре, вибачте моє незнання.
Робін

2
будь-яке остаточне рішення з повним вихідним кодом?
Кікенет

Відповіді:


160

Якщо ви просто збираєтесь перевірити / підтвердити введене ім'я користувача та пароль, використовуйте клас Rfc2898DerivedBytes (також відомий як Функція виведення ключа на основі пароля 2 або PBKDF2). Це більш безпечно, ніж використання шифрування типу Triple DES або AES, оскільки немає жодного практичного способу повернутися від результату RFC2898DerivedBytes до пароля. Ви можете перейти лише від пароля до результату. Див. Чи нормально використовувати хеш пароля SHA1 як сіль для отримання ключа шифрування та IV із рядка пароля? для прикладу та обговорення для .Net або String шифрування / дешифрування паролем c # Стиль метро для WinRT / Metro.

Якщо ви зберігаєте пароль для повторного використання, наприклад, надання ним третьої сторони, використовуйте API захисту даних Windows (DPAPI) . Тут використовуються створені операційною системою та захищені ключі та алгоритм шифрування Triple DES для шифрування та дешифрування інформації. Це означає, що вашій програмі не потрібно турбуватися про створення та захист ключів шифрування, головне занепокоєння при використанні криптографії.

У C # використовуйте клас System.Security.Cryptography.ProtectedData . Наприклад, для шифрування фрагмента даних використовуйте 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);

Зберігайте ентропію та шифротекст надійно, наприклад, у файлі чи ключі реєстру із встановленими дозволами, щоб його міг читати лише поточний користувач. Щоб отримати доступ до оригінальних даних, використовуйте ProtectedData.Unprotect():

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

Зауважте, що є додаткові міркування щодо безпеки. Наприклад, уникайте зберігання таємниць, таких як паролі, як string. Рядки незмінні, оскільки вони не можуть бути повідомлені в пам'яті, тому хтось, дивлячись на пам’ять програми або дамп пам'яті, може побачити пароль. Замість цього використовуйте SecureString або байт [] і пам’ятайте, що розпоряджатись або нулювати їх, як тільки пароль більше не знадобиться.


Привіт, я спробував це, але у мене з'явилася помилка entropy = rng.GetBytes (20), яка сказала: Неможливо перетворити з int в байт []
Робін

@CrispyGMR Я зафіксував цей фрагмент коду у відповіді. Хороший улов.
akton

Дуже дякую. Спочатку я використовував md5 для хешування, але я був скептично налаштований до цього. Це здається більш безпечним. Ще одне питання. Я хочу зберегти в текстовому файлі дуже багато подібних даних. Я бачу, що це лише купа випадкових символів, коли я відкриваю свій файл, але чи достатньо це безпечно для цього? Або ви рекомендуєте інший спосіб зберігання даних?
Робін

2
Здається, що клас тепер відомий як Rfc2898DeriveBytes (маленькі літери, .net 4.5 і 4.6) і його можна знайти тут: Простір імен: System.Security.Cryptography Assembly: mscorlib (в mscorlib.dll)
Дашу

2
Дуже інформативно, однак я думаю, що вся суть використання ProtectedDataполягає в тому, що мені не потрібно турбуватися про безпечне зберігання ентропії та шифротексту, ... тому читати його може лише поточний користувач . Я думаю, що вона пропонує простоту в тому, що я можу їх зберігати, проте це зручно, і все ж тільки CurrentUser може розшифрувати це. entropyПараметр також є необов'язковим , і з'являється схожий на IV , де унікальність має значення більше , ніж секретність. Таким чином, значення, ймовірно, може бути опущено або жорстко закодовано в програму в ситуаціях, коли варіації та оновлення простого тексту нечасті.
antak

8

Я використовував це раніше, і я думаю, щоб переконатися, що дані зберігаються та найкращим чином захищені

  1. ви можете записати їх у конфігураційний файл програми за допомогою ConfigurationManagerкласу
  2. забезпечення пароля за допомогою SecureStringкласу
  3. потім зашифруйте його за допомогою інструментів у Cryptographyпросторі імен.

Це посилання буде корисною, я сподіваюся: натисніть тут


4

DPAPI саме для цієї мети. Використовуйте DPAPI для шифрування пароля при першому введенні користувачем, збережіть його в захищеному місці (реєстр користувача, каталог даних програми користувача, деякі варіанти). Щоразу, коли програма запущена, перевірте місце, щоб побачити, чи існує ваш ключ, чи він використовує DPAPI, щоб розшифрувати його та дозволити доступ, інакше заборонити його.



1

Я хотів зашифрувати та розшифрувати рядок як читабельну строку.

Ось дуже простий швидкий приклад в C # Visual Studio 2019 WinForms на основі відповіді від @Pradip.

Клацніть правою кнопкою миші проект> Властивості> Установки> Створити usernameі passwordналаштування.

введіть тут опис зображення

Тепер ви можете використовувати ті налаштування, які ви тільки що створили. Тут я зберігаю usernameі passwordлише зашифровую passwordв цьому поважному полі значення значення у user.configфайлі.

Приклад зашифрованого рядка у user.configфайлі.

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

введіть тут опис зображення

Повний код

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);
        }
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.