Bagaimana cara menyimpan nama pengguna / kata sandi (lokal) dengan aman?

106

Saya sedang membuat aplikasi Windows, Anda harus masuk terlebih dahulu.
Detail akun terdiri dari nama pengguna dan kata sandi, dan mereka perlu disimpan secara lokal.
Ini hanya masalah keamanan, jadi orang lain yang menggunakan komputer yang sama tidak dapat melihat data pribadi semua orang.
Apa cara terbaik / teraman untuk menyimpan data ini?

Saya tidak ingin menggunakan database, jadi saya mencoba beberapa hal dengan file Resource.
Tetapi karena saya agak baru dalam hal ini, saya tidak sepenuhnya yakin dengan apa yang saya lakukan dan di mana saya harus mencari solusi.

Robin
sumber
6
Pertama-tama, jangan simpan kata sandi. Hash itu (mungkin dengan nilai garam), dan simpan sebagai gantinya.
carlosfigueira
"Pengguna" yang Anda maksud adalah pengguna Windows biasa atau yang lainnya? (Saya pikir maksud Anda beberapa dari Anda memiliki "pengguna" karena pengguna Windows biasa sudah tidak dapat melihat data satu sama lain ...)
Alexei Levenkov
Saya telah mengedit judul Anda. Silakan lihat, " Haruskah pertanyaan menyertakan" tag "dalam judulnya? ", Di mana konsensusnya adalah "tidak, seharusnya tidak".
John Saunders
@ John Saunders Baiklah, maafkan ketidaktahuan saya.
Robin
2
ada solusi akhir dengan kode sumber lengkap?
Kiquenet

Jawaban:

160

Jika Anda hanya akan memverifikasi / memvalidasi nama pengguna dan kata sandi yang dimasukkan, gunakan kelas Rfc2898DerivedBytes (juga dikenal sebagai Fungsi Penurunan Kunci Berbasis Kata Sandi 2 atau PBKDF2). Ini lebih aman daripada menggunakan enkripsi seperti Triple DES atau AES karena tidak ada cara praktis untuk beralih dari hasil RFC2898DerivedBytes kembali ke kata sandi. Anda hanya dapat beralih dari kata sandi ke hasilnya. Lihat Apakah boleh menggunakan hash sandi SHA1 sebagai salt saat mendapatkan kunci enkripsi dan IV dari string sandi? untuk contoh dan pembahasan untuk .Net atau String encrypt / decrypt dengan kata sandi c # Metro Style untuk WinRT / Metro.

Jika Anda menyimpan kata sandi untuk digunakan kembali, seperti memasoknya ke pihak ketiga, gunakan Windows Data Protection API (DPAPI) . Ini menggunakan kunci yang dihasilkan dan dilindungi sistem operasi dan algoritma enkripsi Triple DES untuk mengenkripsi dan mendekripsi informasi. Ini berarti aplikasi Anda tidak perlu khawatir tentang pembuatan dan perlindungan kunci enkripsi, masalah utama saat menggunakan kriptografi.

Di C #, gunakan kelas System.Security.Cryptography.ProtectedData . Misalnya, untuk mengenkripsi suatu data, gunakan 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);

Simpan entropi dan ciphertext dengan aman, seperti dalam file atau kunci registri dengan izin yang ditetapkan sehingga hanya pengguna saat ini yang dapat membacanya. Untuk mendapatkan akses ke data asli, gunakan ProtectedData.Unprotect():

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

Perhatikan bahwa ada pertimbangan keamanan tambahan. Misalnya, hindari menyimpan rahasia seperti sandi sebagai file string. String tidak dapat diubah, karena tidak dapat diberitahukan dalam memori sehingga seseorang yang melihat memori aplikasi atau dump memori dapat melihat sandi. Gunakan SecureString atau byte [] sebagai gantinya dan ingat untuk membuang atau membolosnya segera setelah kata sandi tidak lagi diperlukan.

akton
sumber
Hai, Saya mencoba ini, tetapi saya mendapat kesalahan di entropy = rng.GetBytes (20) mengatakan: Tidak dapat mengkonversi dari int ke byte []
Robin
@CrispyGMR Saya telah memperbaiki potongan kode itu di jawabannya. Tangkapan yang bagus.
akton
Terima kasih banyak. Saya menggunakan md5 untuk hashing pada awalnya, tetapi saya agak skeptis tentang itu. Sepertinya ini jauh lebih aman. Tapi satu pertanyaan lagi. Saya ingin menyimpan cukup banyak data seperti ini dalam file teks. Saya melihat bahwa itu hanya sekumpulan karakter acak ketika saya membuka file saya, tetapi apakah cukup aman untuk melakukan ini? Atau apakah Anda merekomendasikan cara lain untuk menyimpan data?
Robin
2
Tampaknya kelas tersebut sekarang dikenal sebagai Rfc2898DeriveBytes (huruf kecil, .net 4.5 dan 4.6) dan dapat ditemukan di sini: Namespace: System.Security.Cryptography Assembly: mscorlib (dalam mscorlib.dll)
Dashu
2
Sangat informatif, namun menurut saya inti dari penggunaannya ProtectedDataadalah agar saya tidak perlu khawatir tentang Simpan entropi dan ciphertext dengan aman, ... jadi hanya pengguna saat ini yang dapat membacanya . Saya pikir ini menawarkan kesederhanaan karena saya dapat menyimpannya namun nyaman dan masih hanya CurrentUser yang dapat mendekripsinya. The entropyparameter juga opsional dan muncul mirip dengan IV di mana keunikan lebih penting daripada kerahasiaan. Dengan demikian, nilai mungkin dapat dihilangkan atau dikodekan secara keras ke dalam program dalam situasi di mana variasi dan pembaruan teks biasa jarang terjadi.
antak
8

Saya telah menggunakan ini sebelumnya dan saya pikir untuk memastikan kredensial tetap ada dan dengan cara terbaik yang aman

  1. Anda dapat menuliskannya ke file konfigurasi aplikasi menggunakan ConfigurationManagerkelas
  2. mengamankan kata sandi menggunakan SecureStringkelas
  3. lalu mengenkripsinya menggunakan alat di Cryptographynamespace.

Tautan ini akan sangat membantu Saya harap: Klik di sini

Pradip
sumber
4

DPAPI hanya untuk tujuan ini. Gunakan DPAPI untuk mengenkripsi kata sandi pertama kali pengguna memasukkannya, simpan di lokasi yang aman (Registri pengguna, direktori data aplikasi Pengguna, adalah beberapa pilihan). Setiap kali aplikasi diluncurkan, periksa lokasi untuk melihat apakah kunci Anda ada, apakah menggunakan DPAPI untuk mendekripsinya dan mengizinkan akses, jika tidak, tolak.

swiftgp
sumber
1

Saya ingin mengenkripsi dan mendekripsi string sebagai string yang dapat dibaca.

Berikut adalah contoh cepat yang sangat sederhana di C # Visual Studio 2019 WinForms berdasarkan jawaban dari @Pradip.

Proyek klik kanan> properti> pengaturan> Buat usernamedan passwordpengaturan.

masukkan deskripsi gambar di sini

Sekarang Anda dapat memanfaatkan pengaturan yang baru saja Anda buat. Di sini saya menyimpan usernamedan passwordtetapi hanya mengenkripsi passwordbidang nilai terhormat di dalamnya dalam user.configfile.

Contoh string terenkripsi dalam user.configfile.

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

masukkan deskripsi gambar di sini

Kode Lengkap

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
sumber