¿Cómo guardar de forma segura el nombre de usuario / contraseña (local)?


106

Estoy creando una aplicación para Windows, en la que primero debes iniciar sesión.
Los detalles de la cuenta consisten en nombre de usuario y contraseña, y deben guardarse localmente.
Es solo una cuestión de seguridad, por lo que otras personas que usan la misma computadora no pueden ver los datos personales de todos.
¿Cuál es la forma mejor / más segura de guardar estos datos?

No quiero usar una base de datos, así que probé algunas cosas con los archivos de recursos.
Pero como soy un poco nuevo con esto, no estoy completamente seguro de lo que estoy haciendo y dónde debería buscar una solución.


6
En primer lugar, no guarde la contraseña. Aplíquelo (posiblemente con un valor de sal) y guárdelo en su lugar.
carlosfigueira

"Usuarios", ¿te refieres a usuarios habituales de Windows o algo más? (Creo que te refieres a algunos de tus propios "usuarios", ya que los usuarios habituales de Windows ya no pueden ver los datos de los demás ...)
Alexei Levenkov

He editado tu título. Consulte " ¿Deben las preguntas incluir" etiquetas "en sus títulos? ", Donde el consenso es "no, no deberían".
John Saunders

@John Saunders Muy bien, disculpe mi ignorancia.
Robin

2
¿Alguna solución final con código fuente completo?
Kiquenet

Respuestas:


160

Si solo va a verificar / validar el nombre de usuario y la contraseña ingresados, use la clase Rfc2898DerivedBytes (también conocida como Función de derivación de clave basada en contraseña 2 o PBKDF2). Esto es más seguro que usar cifrado como Triple DES o AES porque no existe una forma práctica de pasar del resultado de RFC2898DerivedBytes a la contraseña. Solo puede pasar de una contraseña al resultado. Consulte ¿Está bien usar el hash de contraseña SHA1 como una sal al derivar la clave de cifrado y el IV de la cadena de contraseña? para ver un ejemplo y una discusión para .Net o String encriptar / desencriptar con contraseña c # Estilo Metro para WinRT / Metro.

Si está almacenando la contraseña para reutilizarla, por ejemplo, proporcionándola a un tercero, utilice la API de protección de datos de Windows (DPAPI) . Utiliza claves protegidas y generadas por el sistema operativo y el algoritmo de cifrado Triple DES para cifrar y descifrar la información. Esto significa que su aplicación no tiene que preocuparse por generar y proteger las claves de cifrado, una de las principales preocupaciones al utilizar la criptografía.

En C #, use la clase System.Security.Cryptography.ProtectedData . Por ejemplo, para cifrar un dato, utilice 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);

Almacene la entropía y el texto cifrado de forma segura, como en un archivo o clave de registro con permisos establecidos para que solo el usuario actual pueda leerlos. Para acceder a los datos originales, use ProtectedData.Unprotect():

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

Tenga en cuenta que existen consideraciones de seguridad adicionales. Por ejemplo, evite almacenar secretos como contraseñas como string. Las cadenas son inmutables, ya que no se pueden notificar en la memoria, por lo que alguien que mire la memoria de la aplicación o un volcado de memoria pueda ver la contraseña. Use SecureString o un byte [] en su lugar y recuerde deshacerse de ellos o ponerlos a cero tan pronto como la contraseña ya no sea necesaria.


Hola, intenté esto, pero recibí un error en entropy = rng.GetBytes (20) que decía: No se puede convertir de int a byte []
Robin

@CrispyGMR He arreglado ese fragmento de código en la respuesta. Buena atrapada.
Akton

Muchas gracias. Usé md5 para hash al principio, pero era un poco escéptico al respecto. Esto parece mucho más seguro. Sin embargo, una pregunta más. Quiero guardar bastantes datos como este en un archivo de texto. Veo que son solo un montón de caracteres aleatorios cuando abro mi archivo, pero ¿es lo suficientemente seguro para hacer esto? ¿O recomienda otra forma de almacenar los datos?
Robin

2
Parece que la clase ahora se conoce como Rfc2898DeriveBytes (letras pequeñas, .net 4.5 y 4.6) y se puede encontrar aquí: Espacio de nombres: System.Security.Cryptography Assembly: mscorlib (en mscorlib.dll)
Dashu

2
Muy informativo, sin embargo, creo que el objetivo del uso ProtectedDataes que no necesito preocuparme por Almacenar la entropía y el texto cifrado de forma segura, ... para que solo el usuario actual pueda leerlo . Creo que ofrece simplicidad en el sentido de que puedo almacenarlos, sin embargo, es conveniente y aún así, solo el CurrentUser puede descifrarlo. El entropyparámetro también es opcional y parece similar a un IV donde la singularidad importa más que el secreto. Como tal, el valor probablemente podría omitirse o codificarse en el programa en situaciones en las que la variación y actualización del texto sin formato es poco frecuente.
antak

8

He usado esto antes y creo que para asegurarme de que la credencial persista y de la mejor manera segura

  1. puede escribirlos en el archivo de configuración de la aplicación usando la ConfigurationManagerclase
  2. asegurar la contraseña usando la SecureStringclase
  3. luego encriptarlo usando herramientas en el Cryptographyespacio de nombres.

Este enlace será de gran ayuda espero: Haga clic aquí


4

DPAPI es solo para este propósito. Utilice DPAPI para cifrar la contraseña la primera vez que el usuario ingrese, guárdelo en una ubicación segura (el registro del usuario, el directorio de datos de la aplicación del usuario, son algunas opciones). Siempre que se inicie la aplicación, verifique la ubicación para ver si su clave existe, si usa DPAPI para descifrarla y permitir el acceso; de lo contrario, deniéguela.



1

Quería cifrar y descifrar la cadena como una cadena legible.

Aquí hay un ejemplo rápido muy simple en C # Visual Studio 2019 WinForms basado en la respuesta de @Pradip.

Haga clic derecho en proyecto> propiedades> configuración> Crear una configuración usernamey password.

ingrese la descripción de la imagen aquí

Ahora puede aprovechar la configuración que acaba de crear. Aquí guardo el usernamey, passwordpero solo cifro el passwordcampo de valor respetable en el user.configarchivo.

Ejemplo de la cadena cifrada en el user.configarchivo.

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

ingrese la descripción de la imagen aquí

Código completo

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);
        }
    }
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.