Làm thế nào để lưu tên người dùng / mật khẩu (cục bộ) một cách an toàn?


106

Tôi đang tạo một ứng dụng Windows, mà bạn cần đăng nhập trước.
Chi tiết tài khoản bao gồm tên người dùng và mật khẩu và chúng cần được lưu cục bộ.
Đó chỉ là vấn đề bảo mật, vì vậy những người khác sử dụng cùng một máy tính không thể xem dữ liệu cá nhân của mọi người.
Cách tốt nhất / an toàn nhất để lưu dữ liệu này là gì?

Tôi không muốn sử dụng cơ sở dữ liệu, vì vậy tôi đã thử một số thứ với tệp Tài nguyên.
Nhưng vì tôi chưa quen với điều này, tôi không hoàn toàn chắc chắn về những gì tôi đang làm và nơi tôi nên tìm kiếm giải pháp.


6
Trước hết, đừng lưu mật khẩu. Băm nó (có thể với một giá trị muối) và lưu nó thay thế.
carlosfigueira

"Người dùng" bạn có nghĩa là người dùng Windows thông thường hay cái gì khác? (Tôi nghĩ bạn có nghĩa là một số bạn sở hữu "người sử dụng" như người dùng Windows thường xuyên đã không thể nhìn thấy dữ liệu của nhau ...)
Alexei Levenkov

Tôi đã chỉnh sửa tiêu đề của bạn. Vui lòng xem, " Câu hỏi có nên bao gồm" thẻ "trong tiêu đề của họ không? ", Trong đó sự đồng thuận là "không, không nên".
John Saunders

@John Saunders Được rồi, thứ lỗi cho sự thiếu hiểu biết của tôi.
Robin

2
bất kỳ giải pháp cuối cùng với mã nguồn đầy đủ?
Kiquenet

Câu trả lời:


160

Nếu bạn chỉ định xác minh / xác thực tên người dùng và mật khẩu đã nhập, hãy sử dụng lớp Rfc2898DerivedBytes (còn được gọi là Chức năng lấy lại khóa dựa trên mật khẩu 2 hoặc PBKDF2). Điều này an toàn hơn so với việc sử dụng mã hóa như Triple DES hoặc AES vì không có cách thực tế nào để chuyển từ kết quả của RFC2898DerivedBytes trở lại mật khẩu. Bạn chỉ có thể đi từ mật khẩu đến kết quả. Xem Sử dụng hàm băm SHA1 của mật khẩu làm muối khi lấy khóa mã hóa và IV từ chuỗi mật khẩu có được không? cho một ví dụ và thảo luận cho .Net hoặc Chuỗi mã hóa / giải mã bằng mật khẩu c # Metro Style cho WinRT / Metro.

Nếu bạn đang lưu trữ mật khẩu để sử dụng lại, chẳng hạn như cung cấp mật khẩu cho bên thứ ba, hãy sử dụng API bảo vệ dữ liệu của Windows (DPAPI) . Điều này sử dụng các khóa được tạo và bảo vệ của hệ điều hành cũng như thuật toán mã hóa Triple DES để mã hóa và giải mã thông tin. Điều này có nghĩa là ứng dụng của bạn không phải lo lắng về việc tạo và bảo vệ các khóa mã hóa, một mối quan tâm chính khi sử dụng mật mã.

Trong C #, sử dụng lớp System.Security.Cryptography.ProtectedData . Ví dụ: để mã hóa một phần dữ liệu, hãy sử dụng 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);

Lưu trữ entropy và bản mã một cách an toàn, chẳng hạn như trong tệp hoặc khóa đăng ký với các quyền được đặt để chỉ người dùng hiện tại mới có thể đọc được. Để có quyền truy cập vào dữ liệu gốc, hãy sử dụng ProtectedData.Unprotect():

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

Lưu ý rằng có những cân nhắc bảo mật bổ sung. Ví dụ: tránh lưu trữ bí mật như mật khẩu dưới dạng string. Các chuỗi là bất biến, vì chúng không thể được thông báo trong bộ nhớ nên ai đó đang nhìn vào bộ nhớ của ứng dụng hoặc kết xuất bộ nhớ có thể thấy mật khẩu. Thay vào đó, hãy sử dụng SecureString hoặc một byte [] và nhớ loại bỏ hoặc loại bỏ chúng ngay khi không cần mật khẩu nữa.


Hi, tôi đã cố gắng này, nhưng tôi đã nhận ra lỗi tại entropy = rng.GetBytes (20) nói: Không thể chuyển đổi từ int để byte []
Robin

@CrispyGMR Tôi đã sửa đoạn mã đó trong câu trả lời. Nắm bắt tốt.
akton

Cảm ơn rất nhiều. Lúc đầu, tôi đã sử dụng md5 để băm, nhưng tôi hơi nghi ngờ về nó. Cách này có vẻ an toàn hơn. Còn một câu hỏi nữa. Tôi muốn lưu một số dữ liệu như thế này trong một tệp văn bản. Tôi thấy rằng đó chỉ là một loạt các ký tự ngẫu nhiên khi tôi mở tệp của mình, nhưng liệu nó có đủ an toàn để thực hiện việc này không? Hay bạn đề xuất một cách khác để lưu trữ dữ liệu?
Robin

2
Có vẻ như các lớp học bây giờ được gọi là Rfc2898DeriveBytes (chữ nhỏ, .net 4.5 và 4.6) và có thể được tìm thấy ở đây: Namespace: System.Security.Cryptography hội: mscorlib (trong mscorlib.dll)
Dashu

2
Rất nhiều thông tin, tuy nhiên tôi nghĩ rằng toàn bộ điểm của việc sử dụng ProtectedDatalà tôi không cần phải lo lắng về việc Lưu trữ entropy và bản mã một cách an toàn, ... vì vậy chỉ người dùng hiện tại mới có thể đọc nó . Tôi nghĩ rằng nó mang lại sự đơn giản ở chỗ tôi có thể lưu trữ chúng tuy nhiên rất tiện lợi và vẫn chỉ Người dùng hiện tại mới có thể giải mã nó. Các entropytham số cũng là tùy chọn và xuất hiện tương tự như một IV nơi độc đáo quan trọng hơn giữ bí mật. Do đó, giá trị có thể bị bỏ qua hoặc được mã hóa cứng vào chương trình trong các tình huống mà sự thay đổi và cập nhật của bản rõ là không thường xuyên.
antak

8

Tôi đã sử dụng điều này trước đây và tôi nghĩ rằng để đảm bảo thông tin đăng nhập vẫn tồn tại và theo cách bảo mật tốt nhất là

  1. bạn có thể ghi chúng vào tệp cấu hình ứng dụng bằng ConfigurationManagerlớp
  2. bảo mật mật khẩu bằng SecureStringlớp học
  3. sau đó mã hóa nó bằng các công cụ trong Cryptographykhông gian tên.

Liên kết này sẽ giúp ích rất nhiều, tôi hy vọng: Bấm vào đây


4

DPAPI chỉ dành cho mục đích này. Sử dụng DPAPI để mã hóa mật khẩu lần đầu tiên người dùng nhập, lưu trữ nó ở một vị trí an toàn (Sổ đăng ký của người dùng, thư mục dữ liệu ứng dụng của người dùng, là một số lựa chọn). Bất cứ khi nào ứng dụng được khởi chạy, hãy kiểm tra vị trí để xem liệu khóa của bạn có tồn tại hay không, nếu khóa có sử dụng DPAPI để giải mã và cho phép truy cập, nếu không hãy từ chối khóa.



1

Tôi muốn mã hóa và giải mã chuỗi dưới dạng một chuỗi có thể đọc được.

Đây là một ví dụ nhanh rất đơn giản trong C # Visual Studio 2019 WinForms dựa trên câu trả lời từ @Pradip.

Nhấp chuột phải vào dự án> thuộc tính> cài đặt> Tạo usernamepasswordcài đặt.

nhập mô tả hình ảnh ở đây

Bây giờ bạn có thể tận dụng những cài đặt bạn vừa tạo. Ở đây tôi lưu usernamepasswordchỉ mã hóa passwordtrường giá trị đáng kính của nó trong user.configtệp.

Ví dụ về chuỗi được mã hóa trong user.configtệp.

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

nhập mô tả hình ảnh ở đây

Mã đầy đủ

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);
        }
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.