C# 如何安全地保存用户名/密码(本地)?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12657792/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 00:02:23  来源:igfitidea点击:

How to securely save username/password (local)?

c#securitylocal

提问by Robin

I'm making a Windows application, which you need to log into first.
The account details consist of username and password, and they need to be saved locally.
It's just a matter of security, so other people using the same computer can't see everyone's personal data.
What is the best/most secure way to save this data?

我正在制作一个 Windows 应用程序,您需要先登录。
账户详细信息由用户名和密码组成,需要保存在本地。
这只是一个安全问题,因此使用同一台计算机的其他人无法看到每个人的个人数据。
保存此数据的最佳/最安全方法是什么?

I don't want to use a database, so I tried some things with Resource files.
But since I'm kind of new with this, I'm not entirely sure of what I'm doing and where I should be looking for a solution.

我不想使用数据库,所以我尝试了一些资源文件。
但由于我对这个有点陌生,我不完全确定我在做什么以及我应该在哪里寻找解决方案。

采纳答案by akton

If you are just going to verify/validate the entered user name and password, use the Rfc2898DerivedBytesclass (also known as Password Based Key Derivation Function 2 or PBKDF2). This is more secure than using encryption like Triple DES or AES because there is no practical way to go from the result of RFC2898DerivedBytes back to the password. You can only go from a password to the result. See Is it ok to use SHA1 hash of password as a salt when deriving encryption key and IV from password string?for an example and discussion for .Net or String encrypt / decrypt with password c# Metro Stylefor WinRT/Metro.

如果您只是要验证/验证输入的用户名和密码,请使用Rfc2898DerivedBytes类(也称为基于密码的密钥派生函数 2 或 PBKDF2)。这比使用 Triple DES 或 AES 等加密更安全,因为没有实用的方法可以从 RFC2898DerivedBytes 的结果返回密码。您只能从密码转到结果。请参阅从密码字符串派生加密密钥和 IV 时可以使用密码的 SHA1 哈希作为盐吗?有关 .Net 或字符串加密/解密的示例和讨论,请使用密码 c# Metro Stylefor WinRT/Metro。

If you are storing the password for reuse, such as supplying it to a third party, use the Windows Data Protection API (DPAPI). This uses operating system generated and protected keys and the Triple DESencryption algorithm to encrypt and decrypt information. This means your application does not have to worry about generating and protecting the encryption keys, a major concern when using cryptography.

如果您要存储密码以供重复使用,例如将其提供给第三方,请使用Windows 数据保护 API (DPAPI)。这使用操作系统生成和保护的密钥以及三重 DES加密算法来加密和解密信息。这意味着您的应用程序不必担心生成和保护加密密钥,这是使用加密时的一个主要问题。

In C#, use the System.Security.Cryptography.ProtectedDataclass. For example, to encrypt a piece of data, use ProtectedData.Protect():

在 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);

Store the entropy and ciphertext securely, such as in a file or registry key with permissions set so only the current user can read it. To get access to the original data, use ProtectedData.Unprotect():

安全地存储熵和密文,例如在设置了权限的文件或注册表项中,以便只有当前用户可以读取它。要访问原始数据,请使用ProtectedData.Unprotect()

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

Note that there are additional security considerations. For example, avoid storing secrets like passwords as a string. Strings are immutable, being they cannot be notified in memory so someone looking at the application's memory or a memory dump may see the password. Use SecureStringor a byte[] instead and remember to dispose or zero them as soon as the password is no longer needed.

请注意,还有其他安全注意事项。例如,避免将密码等机密存储为string. 字符串是不可变的,因为无法在内存中通知它们,因此查看应用程序内存或内存转储的人可能会看到密码。改用SecureString或 byte[] 并记住在不再需要密码时立即处理或归零它们。

回答by Pradip

I have used this before and I think in order to make sure credential persist and in a best secure way is

我以前使用过这个,我认为为了确保凭证持续存在并以最佳安全方式

  1. you can write them to the app config file using the ConfigurationManagerclass
  2. securing the password using the SecureStringclass
  3. then encrypting it using tools in the Cryptographynamespace.
  1. 您可以使用ConfigurationManager该类将它们写入应用程序配置文件
  2. 使用SecureString类保护密码
  3. 然后使用Cryptography命名空间中的工具对其进行加密。

This link will be of great help I hope : Click here

我希望此链接对您有很大帮助:单击此处

回答by swiftgp

DPAPI is just for this purpose. Use DPAPI to encrypt the password the first time the user enters is, store it in a secure location (User's registry, User's application data directory, are some choices). Whenever the app is launched, check the location to see if your key exists, if it does use DPAPI to decrypt it and allow access, otherwise deny it.

DPAPI 就是为了这个目的。使用DPAPI对用户第一次输入的密码进行加密,将其保存在安全的位置(用户的注册表,用户的应用程序数据目录,是一些选择)。每当应用程序启动时,检查位置以查看您的密钥是否存在,如果它确实使用 DPAPI 对其进行解密并允许访问,否则拒绝它。

回答by Stephen Buck

This only works on Windows, so if you are planning to use dotnet core cross-platform, you'll have to look elsewhere. See https://github.com/dotnet/corefx/blob/master/Documentation/architecture/cross-platform-cryptography.md

这仅适用于 Windows,因此如果您计划使用 dotnet core 跨平台,则必须寻找其他地方。请参阅https://github.com/dotnet/corefx/blob/master/Documentation/architecture/cross-platform-cryptography.md

回答by Jonas

I wanted to encrypt and decrypt the string as a readable string.

我想将字符串加密和解密为可读字符串。

Here is a very simple quick example in C# Visual Studio 2019 WinForms based on the answer from @Pradip.

这是 C# Visual Studio 2019 WinForms 中一个非常简单的快速示例,基于@Pradip.

Right click project > properties > settings > Create a usernameand passwordsetting.

右键单击项目 > 属性 > 设置 > 创建usernamepassword设置。

enter image description here

在此处输入图片说明

Now you can leverage those settings you just created. Here I save the usernameand passwordbut only encrypt the passwordin it's respectable value field in the user.configfile.

现在,您可以利用刚刚创建的那些设置。在这里,我保存username并且password只加密文件password中它的可观值字段user.config

Example of the encrypted string in the user.configfile.

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>

enter image description here

在此处输入图片说明

Full Code

完整代码

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);
        }
    }
}