Как надежно сохранить логин / пароль (локальный)?

106

Я делаю приложение для Windows, в которое нужно сначала войти.
Детали учетной записи состоят из имени пользователя и пароля, и их необходимо сохранить локально.
Это просто вопрос безопасности, поэтому другие люди, использующие тот же компьютер, не могут видеть личные данные всех.
Каков самый лучший / самый безопасный способ сохранить эти данные?

Я не хочу использовать базу данных, поэтому попробовал кое-что с файлами ресурсов.
Но поскольку я новичок в этом, я не совсем уверен в том, что делаю и где мне следует искать решение.

Робин
источник
6
Прежде всего, не сохраняйте пароль. Хешируйте его (возможно, со значением соли) и сохраните вместо этого.
carlosfigueira
"Пользователи" вы имеете в виду обычных пользователей Windows или что-то еще? (Я думаю, вы имеете в виду, что у некоторых из вас есть «пользователи», поскольку обычный пользователь Windows уже не может видеть данные друг друга ...)
Алексей Левенков
Я отредактировал ваше название. См. Раздел « Должны ли вопросы включать« теги »в свои заголовки? », Где единодушное мнение - «нет, не следует».
Джон Сондерс,
@John Saunders: Хорошо, извините за невежество.
Робин
2
какое-нибудь окончательное решение с полным исходным кодом?
Kiquenet

Ответы:

160

Если вы просто собираетесь проверить / подтвердить введенное имя пользователя и пароль, используйте класс Rfc2898DerivedBytes (также известный как функция вывода ключа на основе пароля 2 или PBKDF2). Это более безопасно, чем использование шифрования, такого как Triple DES или AES, потому что нет практического способа вернуться от результата RFC2898DerivedBytes к паролю. Вы можете только перейти от пароля к результату. См. Можно ли использовать хэш пароля SHA1 в качестве соли при получении ключа шифрования и IV из строки пароля? для примера и обсуждения .Net или String для шифрования / дешифрования с паролем c # Metro Style для WinRT / Metro.

Если вы храните пароль для повторного использования, например, для передачи его третьему лицу, используйте Windows Data Protection API (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 или byte [] и не забудьте удалить или обнулить их, как только пароль больше не понадобится.

Актон
источник
Привет, я пробовал это, но у меня возникла ошибка в entropy = rng.GetBytes (20), в которой говорится: Невозможно преобразовать int в byte []
Робин
@CrispyGMR Я исправил этот фрагмент кода в ответе. Хороший улов.
Актон 01
Большое спасибо. Сначала я использовал md5 для хеширования, но относился к нему скептически. Это кажется более безопасным. Еще один вопрос. Я хочу сохранить такие данные в текстовом файле. Когда я открываю свой файл, я вижу, что это просто набор случайных символов, но достаточно ли безопасно для этого? Или вы порекомендуете другой способ хранения данных?
Робин
2
Кажется, что класс теперь известен как Rfc2898DeriveBytes (строчные буквы, .net 4.5 и 4.6), и его можно найти здесь: Пространство имен: System.Security.Cryptography Сборка: mscorlib (в mscorlib.dll)
Дашу
2
Очень информативно, однако я думаю, что весь смысл использования ProtectedDataзаключается в том, что мне не нужно беспокоиться о безопасном хранении энтропии и зашифрованного текста, ... поэтому только текущий пользователь может его прочитать . Я думаю, что он предлагает простоту в том, что я могу хранить их, но это удобно, и по-прежнему только CurrentUser может его расшифровать. entropyПараметр также является необязательным , и появляется похожий на IV , где уникальность имеет значение больше , чем секретность. Таким образом, значение, вероятно, может быть опущено или жестко закодировано в программе в ситуациях, когда изменение и обновление открытого текста происходит нечасто.
antak
8

Я использовал это раньше, и я думаю, чтобы убедиться, что учетные данные сохраняются и наиболее безопасным способом

  1. вы можете записать их в файл конфигурации приложения, используя ConfigurationManagerкласс
  2. защита пароля с помощью SecureStringкласса
  3. затем зашифровать его с помощью инструментов в Cryptographyпространстве имен.

Я надеюсь, эта ссылка будет очень полезной: Нажмите здесь

Прадип
источник
4

DPAPI как раз для этого. Используйте DPAPI для шифрования пароля при первом вводе пароля, сохраните его в безопасном месте (реестр пользователя, каталог данных приложения пользователя - некоторые варианты). Всякий раз, когда приложение запускается, проверяйте местоположение, чтобы узнать, существует ли ваш ключ, использует ли оно DPAPI для его расшифровки и разрешения доступа, в противном случае - запретите его.

swiftgp
источник
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);
        }
    }
}
Йонас
источник