Como salvar com segurança o nome de usuário / senha (local)?

106

Estou criando um aplicativo do Windows, no qual você precisa fazer login primeiro.
Os detalhes da conta consistem em nome de usuário e senha e precisam ser salvos localmente.
É apenas uma questão de segurança, para que outras pessoas que usam o mesmo computador não possam ver os dados pessoais de todos.
Qual é a maneira melhor / mais segura de salvar esses dados?

Não quero usar um banco de dados, então tentei algumas coisas com arquivos de recursos.
Mas, como sou novato nisso, não tenho certeza do que estou fazendo e onde devo procurar uma solução.

Robin
fonte
6
Em primeiro lugar, não salve a senha. Hash-o (possivelmente com um valor de sal) e salve-o.
carlosfigueira
"Usuários", você quer dizer usuários regulares do Windows ou algo mais? (Eu acho que você quer dizer alguns de vocês próprios "usuários" como usuários normais do Windows já não podem ver os dados uns dos outros ...)
Alexei Levenkov
Eu editei seu título. Consulte " As perguntas devem incluir“ tags ”em seus títulos? ", Onde o consenso é "não, não deveriam".
John Saunders
@John Saunders Tudo bem, desculpe minha ignorância.
Robin de
2
alguma solução final com código-fonte completo?
Kiquenet

Respostas:

160

Se você for apenas verificar / validar o nome de usuário e a senha inseridos, use a classe Rfc2898DerivedBytes (também conhecida como Função 2 de Derivação de Chave Baseada em Senha ou PBKDF2). Isso é mais seguro do que usar criptografia como Triple DES ou AES porque não há uma maneira prática de voltar do resultado de RFC2898DerivedBytes para a senha. Você só pode ir de uma senha para o resultado. Consulte Está certo usar o hash SHA1 de senha como um sal ao derivar a chave de criptografia e IV da sequência de senha? para obter um exemplo e discussão para .Net ou String criptografar / descriptografar com a senha c # Metro Style para WinRT / Metro.

Se você estiver armazenando a senha para reutilização, como fornecê-la a terceiros, use a API de Proteção de Dados do Windows (DPAPI) . Isso usa chaves protegidas e geradas pelo sistema operacional e o algoritmo de criptografia Triple DES para criptografar e descriptografar informações. Isso significa que seu aplicativo não precisa se preocupar em gerar e proteger as chaves de criptografia, uma grande preocupação ao usar criptografia.

Em C #, use a classe System.Security.Cryptography.ProtectedData . Por exemplo, para criptografar um dado, use 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);

Armazene a entropia e o texto cifrado com segurança, como em um arquivo ou chave de registro com permissões definidas para que apenas o usuário atual possa lê-lo. Para obter acesso aos dados originais, use ProtectedData.Unprotect():

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

Observe que existem considerações adicionais de segurança. Por exemplo, evite armazenar segredos como senhas como um string. Strings são imutáveis, sendo que não podem ser notificados na memória para que alguém olhando para a memória do aplicativo ou um despejo de memória possa ver a senha. Use SecureString ou um byte [] em vez disso e lembre-se de descartá-los ou zerá-los assim que a senha não for mais necessária.

Akton
fonte
Olá, tentei fazer isso, mas recebi um erro em entropy = rng.GetBytes (20) dizendo: Não é possível converter de int para byte []
Robin
@CrispyGMR Corrigi esse trecho de código na resposta. Boa pegada.
Akton
Muito obrigado. Usei o md5 para hash no início, mas estava meio cético quanto a isso. Isso parece muito mais seguro. Porém, mais uma pergunta. Quero salvar alguns dados como este em um arquivo de texto. Vejo que são apenas um monte de caracteres aleatórios quando abro meu arquivo, mas é seguro fazer isso? Ou você recomenda outra maneira de armazenar os dados?
Robin de
2
Parece que a classe agora é conhecida como Rfc2898DeriveBytes (letras minúsculas, .net 4.5 e 4.6) e pode ser encontrada aqui: Namespace: System.Security.Cryptography Assembly: mscorlib (em mscorlib.dll)
Dashu
2
Muito informativo, no entanto acho que todo o ponto de uso ProtectedDataé para que eu não precise me preocupar em Armazenar a entropia e o texto cifrado com segurança, ... para que apenas o usuário atual possa lê-lo . Acho que oferece simplicidade na medida em que posso armazená-los, no entanto, é conveniente e ainda assim apenas o CurrentUser pode descriptografá-lo. O entropyparâmetro também é opcional e parece semelhante a um IV, onde a exclusividade é mais importante do que o sigilo. Como tal, o valor provavelmente pode ser omitido ou codificado no programa em situações em que a variação e atualização do texto simples não são frequentes.
antak
8

Já usei isso antes e acho que para garantir que a credencial persista e da melhor maneira segura,

  1. você pode gravá-los no arquivo de configuração do aplicativo usando a ConfigurationManagerclasse
  2. protegendo a senha usando a SecureStringclasse
  3. em seguida, criptografando-o usando ferramentas no Cryptographynamespace.

Este link será de grande ajuda, espero: Clique aqui

Pradip
fonte
4

DPAPI é apenas para esse propósito. Use a DPAPI para criptografar a senha na primeira vez que o usuário inserir is, armazene-a em um local seguro (registro do usuário, diretório de dados do aplicativo do usuário, são algumas opções). Sempre que o aplicativo for iniciado, verifique o local para ver se sua chave existe, se ele usa DPAPI para descriptografá-la e permitir o acesso, caso contrário, negue-a.

swiftgp
fonte
1

Eu queria criptografar e descriptografar a string como uma string legível.

Aqui está um exemplo rápido muito simples em C # Visual Studio 2019 WinForms com base na resposta de @Pradip.

Clique com o botão direito em projeto> propriedades> configurações> Criar uma configuração usernamee password.

insira a descrição da imagem aqui

Agora você pode aproveitar as configurações que acabou de criar. Aqui, salvo o usernamee, passwordmas apenas criptografo o passwordem seu campo de valor respeitável no user.configarquivo.

Exemplo da string criptografada no user.configarquivo.

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

insira a descrição da imagem aqui

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