C# -  Login com SQL Server Compact e Hash com SHA256


Neste artigo vou mostrar como podemos usar o banco de dado SQL Server Compact em uma aplicação onde iremos ter um formulário de login e registro e a partir da qual também poderemos enviar emails de boas vindas usando um servidor SMTP. Vamos fazer o hash senha usando a classe SHA256 que calcula o hash de SHA256 para os dados de entrada.

Nota: Neste link - http://www.freeformatter.com/sha256-generator.html - existe um gerador de hash SHA256 a partir de uma string.

O que é um algoritmo hash?
Uma função hash é uma equação matemática que utiliza texto (tal como uma mensagem de e-mail) para criar um código chamado message digest (resumo de mensagem). Alguns exemplos conhecidos de funções hash: MD4 (MD significa message digest), MD5 e SHS.

O tamanho do MD depende do algoritmo escolhido (MD1, MD2, ..., MD5 ou SHA1), que é medido em bits - por exemplo, o SHA1    gera um hash de 160 bits.

O SHA256 é um algoritmo message digest usado para gerar um hash de 256-bit(32 byte) geralmente representando por números hexadecimais de 64 bits.
 
Uma função hash utilizada para autenticação digital deve ter certas propriedades que a tornem segura para uso criptográfico. Especificamente, deve ser impraticável encontrar:
1 - O Texto que dá um hash a um dado valor. Ou seja, mesmo que você conheça o MD-message digest, não conseguirá decifrar a mensagem.
2 - Duas mensagens distintas que dão um hash ao mesmo valor.

A capacidade de descobrir uma mensagem que dê um hash a um dado valor possibilita a um agressor substituir uma mensagem falsa por uma mensagem real que foi assinada.

Permite ainda que alguém rejeite de forma desleal uma mensagem, alegando que, na realidade, ele ou ela assinou uma mensagem diferente, dando um hash ao mesmo valor e violando assim a propriedade de não-repúdio das assinaturas digitais.

Recursos usados:

Criando o Projeto

Abra o Visual Studio 2012 Express for Windows Desktop e clique em New Project;

Selecione o template Visual C# -> Windows e o template Windows Forms Application e informe o nome Login_Hash;

Inclua um controle TabControl no formulário form1.cs e aceite o nome padrão tabControl1;

A seguir em sua propriedade TabPages crie as seguintes 4 tabPages : Login , Registro, Banco de Dados e Configuração:

A seguir temos a interface da aplicação onde em cada TabPage definimos uma interface para implementar as funcionalidades da aplicação:

TabPage - Login

2 Label
1 TextBox - txtUsuarioLogin
1 TextBox - txtSenhaLogin
1 Button - btnLogin
1 PictureBox

TabPage - Registro

4 Label
4 TextBox

  1. txtUsuario
  2. txtSenha
  3. txtConfirmarSenha
  4. txtEmail

1 Button - btnRegistrar
1 PictureBox

 

TabPage = Banco de dados

1 DataGridView - acessosDataGridView
1 BindingSource
1 BingingNavigator

TabPage - Configuração

1 TextBox - txtEnderecoSMTP
1 NumericUpDown - nupPortaSMTP
1 TextBox - txtEmailUsuarioSMTP
1 TextBox - txtSenhaEmailSMTP (Multiline=True)

Criando o Banco de dados no SQL Server Compact 4.0

O Microsoft SQL Server Compact 4.0 permite criar banco de dados compactos que podem ser distribuídos em computadores desktop, smart devices, e Tablets.

Quando você cria aplicações que usa o SQL Server Compact você pode usar tanto a linguagem Visual Basic .NET como a linguagem C# e o .NET Framework ou .NET Compact Framework para criar e gerenciar a aplicação.

Vamos criar o banco de dados Logins.sdf no SQL Server Compact 4.0 usando o ambiente do Visual Studio 2012 Express for Windows desktop.

Abra a janela DataBase Explorer (menu VIEW->DataBase Explorer) e clique com o botão direito sobre o item Data Connections e a seguir em Add Connection;

Na janela Add Connection clique no botão Change na caixa Data Source e selecione o Data Source - Microsoft SQL Server Compact 4.0, e a seguir no botão OK;

Informe o nome Logins.sdf em DataBase e clique no botão Create:

Na janela DataBase Explorer você verá o banco de dados Logins.sdf criado. Vamos criar a tabela Acessos.

Clique com o botão direito do mouse sobre o item Tables e a seguir clique em Create Table:

No editor defina o nome da tabela como Acessos e a estrutura e clique no botão OK para criar a tabela conforme a imagem a abaixo:

Nossa tabela acessos terá os campos:  id, usuario, senha e email.

Agora vamos criar um DataSource  a partir da tabela Acessos. No menu VIEW clique em DataSources e clique no ícone para criar um novo DataSource;

Na janela do assistente selecione DataBase  e clique no botão Next>;

A seguir selecione DataSet e clique no botão Next>:

Selecione a conexão com o banco de dados Logins.sdf e clique no botão Next>;

A seguir selecione a tabela Acessos, aceite o nome LoginDataSet e clique no botão Finish;

Ao final você deverá ver a janela Data Sources contendo o dataset LoginDataSet conforme figura a seguir:

Vamos usar a janela DataSource e abrir o formulário form1.cs selecionando a tabPage - Banco de Dados;

A seguir arraste e solte a tabela Acesso do dataset loginDataSet para a tabPage para gerar um DataGridView, um BindingSource, um BindingNavigator conforme a imagem a seguir:

Fizemos isso apenas para exibir os dados da tabela Acessos em um DataGridView no formulário form1.cs.

Será gerado um código no evento Load do formulário que irá carregar um TableAdapter com os dados do DataSet para exibição:

this.acessosTableAdapter.Fill(this.loginsDataSet.Acessos);

Dessa forma poderemos consultar os usuários registrados nesta TabPage.

Definindo as Configurações SMTP

Vamos agora definir as configurações do servidor SMTP para que possamos enviar um email de boas vindas ao usuário após a conclusão do registro.

Vamos definir valores para as variáveis: enderecoSMTP, portaSMTP, emailSMTP, senhaSMTP e loginConnectionString

Vamos armazenar essas aplicações no arquivo app.config definindo-as diretamente na janela de propriedades da aplicação, na guia Settings conforme mostra a figura a seguir:

Essas definições feitas irão se refletir no arquivo app.config como vemos no trecho de código extraído deste arquivo abaixo:

....
<
applicationSettings>

<Login_Hash.Properties.Settings>

<setting name="enderecoSMTP" serializeAs="String">

    <value>macoratti.smtp.com</value>

</setting>

<setting name="portaSMTP" serializeAs="String">

    <value>53</value>

</setting>

<setting name="emailSMTP" serializeAs="String">

    <value>macoratti@yahoo.com</value>

</setting>

</Login_Hash.Properties.Settings>

</applicationSettings>

<userSettings>

<Login_Hash.Properties.Settings>

<setting name="senhaSMTP" serializeAs="String">

    <value>EF797C8118F02DFB649607DD5D3F8C7623048C9C063D532CC95C5ED7A898A64F</value>

</setting>

</Login_Hash.Properties.Settings>

</userSettings>
....

Quando executarmos o projeto e o formulário for carregado, o evento Load será ativado, e neste evento vamos definir a rotina informacoesSMTP() que irá carregar os valores de configurações definidos exibindo-os nas caixas de texto da tabPage Configurações conforme o código a seguir:

 private void Form1_Load(object sender, EventArgs e)
{
   // TODO: This line of code loads data into the 'loginsDataSet.Acessos' table. You can move, or remove it, as needed.
   this.acessosTableAdapter.Fill(this.loginsDataSet.Acessos);
   informacoesSMTP();
}

O código da rotina informacoesSMTP(), dado abaixo, acessa os valores das propriedades e os atribui às caixas de texto do formulário:

 private void informacoesSMTP()
 {
            txtEnderecoSMTP.Text = Properties.Settings.Default.enderecoSMTP;
            nupPortaSMTP.Value = Convert.ToInt32(Properties.Settings.Default.portaSMTP);
            txtEmailUsuarioSMTP.Text = Properties.Settings.Default.emailSMTP;
            txtSenhaEmailSMTP.Text = Properties.Settings.Default.senhaSMTP;
  }

Fazendo o Login

Na tabPage Login temos a interface para realizar o login do usuário.

Abaixo temos o código no evento Click do botão de comando Login:

       private void btnLogin_Click(object sender, EventArgs e)
        {
                   //variaveis locais para tratar o usuario e a senha
       string usuario = txtUsuarioLogin.Text;
       string senha = Crypto.sha256encrypt(txtSenhaLogin.Text);

       //percorre cada tabela do banco de dados
       foreach (DataRow row in loginsDataSet.Acessos)
       {
          //e verifica pelo usuário e senha que coincidem
          if (row.ItemArray[1].Equals(usuario) && row.ItemArray[2].Equals(senha))
          {
              txtUsuarioLogin.Text = String.Empty;
              txtSenhaLogin.Text = String.Empty;
              MessageBox.Show("Login realizado com sucesso !");
              break;
          }
          //Se não achar então
          else
          {
              MessageBox.Show("Usuário/Senha incorretos");
              break;
          }
                  }
        }

Neste código estou obtendo o valor digitado na caixa de texto txtUsuarioLogin.Text atribuindo o valor à variável usuario.

A seguir estou usando o método sha256encrypt() da classe Crypto passando a senha informada na caixa de texto txtSenhaLogin.Text.

Mas de onde veio essa classe ?

Essa classe foi criada no projeto via menu PROJECT -> Add Class, onde informamos o nome Crypto.cs e possui o seguinte código:

sing System.Text;
using System.Security.Cryptography;
namespace Login_Hash
{
    public static class Crypto
    {
        public static string md5encrypt(string frase)
        {
            UTF8Encoding encoder = new UTF8Encoding();
            MD5CryptoServiceProvider md5hasher = new MD5CryptoServiceProvider();
            byte[] hashedDataBytes = md5hasher.ComputeHash(encoder.GetBytes(frase));
            return byteArrayToString(hashedDataBytes);
        }
        public static string sha1encrypt(string frase)
        {
            UTF8Encoding encoder = new UTF8Encoding();
            SHA1CryptoServiceProvider sha1hasher = new SHA1CryptoServiceProvider();
            byte[] hashedDataBytes = sha1hasher.ComputeHash(encoder.GetBytes(frase));
            return byteArrayToString(hashedDataBytes);
        }
        public static string sha256encrypt(string frase)
        {
            UTF8Encoding encoder = new UTF8Encoding();
            SHA256Managed sha256hasher = new SHA256Managed();
            byte[] hashedDataBytes = sha256hasher.ComputeHash(encoder.GetBytes(frase));
            return byteArrayToString(hashedDataBytes);
        }
        public static string sha384encrypt(string frase)
        {
            UTF8Encoding encoder = new UTF8Encoding();
            SHA384Managed sha384hasher = new SHA384Managed();
            byte[] hashedDataBytes = sha384hasher.ComputeHash(encoder.GetBytes(frase));
            return byteArrayToString(hashedDataBytes);
        }
        public static string sha512encrypt(string frase)
        {
            UTF8Encoding encoder = new UTF8Encoding();
            SHA512Managed sha512hasher = new SHA512Managed();
            byte[] hashedDataBytes = sha512hasher.ComputeHash(encoder.GetBytes(frase));
            return byteArrayToString(hashedDataBytes);
        }
        public static string byteArrayToString(byte[] inputArray)
        {
            StringBuilder output = new StringBuilder("");
            for (int i = 0; i < inputArray.Length; i++)
            {
                output.Append(inputArray[i].ToString("X2"));
            }
            return output.ToString();
        }
    }
}

Esta classe contém diversos métodos que podem ser usados para cifrar, gerar hash, decifrar, etc. Como a classe os métodos são estáticos não temos que criar uma instância para usar os métodos.

Observe que a classe usa o namespace System.Security.Cryptography para acessar os diversos métodos para gerar o hash.

Depois percorremos cada tabela do DataSet e verificamos se a senha e usuário conferem.

Registrando um novo usuário

No evento Click do botão Registrar da tabPage - Registro - temos o código que chama a rotina para incluir um novo usuário na tabela Acessos:

 private void btnRegistrar_Click(object sender, EventArgs e)
 {
            AdicionarUsuario(txtUsuario.Text, txtSenha.Text, txtConfrimarSenha.Text, txtEmail.Text);
 }

Esta rotina recebe o nome do usuário, senha, a confirmação da senha e o email e após realizar algumas validações chama a rotina AdicionarUsuarioNoBD() passando apenas o nome do usuário, o hash da senha e o email :

 private void AdicionarUsuario(string _nomeUsuario, string _senha, string _confirmaSenha, string _email)
  {
	        //variaveis locais para tratar os valores
	        string smtpEmail = txtEmailUsuarioSMTP.Text;
	        string smtpPassword = txtSenhaEmailSMTP.Text;
	        int smtpPorta = (int)nupPortaSMTP.Value;
	        string smtpAddress = txtEnderecoSMTP.Text;

	        //Regex para validar o email
	        Regex regex = new Regex(@"^([\w\.\-]+)@([\w\-]+)((\.(\w){2,3})+)$");
	        Match match = regex.Match(_email);

	        //Percorre as tabelas do banco de dados
	        foreach (DataRow row in loginsDataSet.Acessos)
	        {
	            //E procura por nomes de usuários existentes
	            if (row.ItemArray[1].Equals(_nomeUsuario))
	            {
	                //Se achar um então avisa
	                MessageBox.Show("O nome do usuário já existe, tente informar outro nome.");
	                return;
	            }
	        }
	        //Confirma a senha
	        if (_senha != _confirmaSenha)
	        {
	            MessageBox.Show("A senha não confere.");
	        }
	        // A senha tem que ter no minimo 8 caracteres
	        else if (_senha.Length < 8)
	        {
	            MessageBox.Show("A senha deve conter no mínimo 8 caracteres");
	        }
	        //Se o email não for válido
	        else if (!match.Success)
	        {
	            MessageBox.Show("Email inválido");
	        }
	        //Se não informou o usuário
	        else if (_nomeUsuario == null)
	        {
	            MessageBox.Show("VOcê deve informar um usuário");
	        }
	        //Se estiver tudo certo então cria o usuário
	        else
	        {
	                string _hashSenha = Crypto.sha256encrypt(_senha);
	             AdicionaUsuarioNoBD(_nomeUsuario, _hashSenha, _email);

	                txtUsuario.Text = String.Empty;
	                txtSenha.Text = String.Empty;
	                txtConfrimarSenha.Text = String.Empty;
	                txtEmail.Text = String.Empty;

             	   MessageBox.Show("Obrigado por seu registro !");

                         if (String.IsNullOrWhiteSpace(smtpEmail) || String.IsNullOrWhiteSpace(smtpPassword) || 
String.IsNullOrWhiteSpace(smtpAddress) || smtpPorta <= 0)
	            {
	                MessageBox.Show("A configuração do Email não foi definida corretamente! \nNão é possível enviar emails!");
	            }
	            else
	            {
	                EnviaMensagem(_email.ToString(), _nomeUsuario.ToString(), _senha.ToString());
	            }
                  }
   }

A seguir vemos o código da rotina AdicionaUsuarioNoBD() que realiza uma conexão com o banco de dados Logins.sdf e usando a instrução SQL Insert/Into cria um objeto Command e passa os parâmetros para o usuario, senha e email.

O método ExecuteNonQuery() faz a inclusão no banco de dados:

 private void AdicionaUsuarioNoBD(string _nomeUsuario, string _senha, string _email)
  {
         string ConnectString = Properties.Settings.Default.LoginsConnectionString;
          SqlCeConnection cn = new SqlCeConnection(ConnectString);
          if (cn.State == ConnectionState.Closed)
          {
            cn.Open();
          }
          SqlCeCommand cmd;
          string sql = "insert into Acessos "
                         + "(usuario, senha, email) "
                         + "values (@usuario, @senha, @email)";
          try
          {
            cmd = new SqlCeCommand(sql, cn);
            cmd.Parameters.AddWithValue("@usuario", _nomeUsuario);
            cmd.Parameters.AddWithValue("@senha", _senha);
            cmd.Parameters.AddWithValue("@email", _email);
            cmd.ExecuteNonQuery();
            MessageBox.Show("Usuario incluído.");
          }
          catch (SqlCeException sqlexception)
          {
            MessageBox.Show(sqlexception.Message, "Arre Égua.", MessageBoxButtons.OK, MessageBoxIcon.Error);
          }
          catch (Exception ex)
          {
            MessageBox.Show(ex.Message, "Arre Égua, a coisa falhou...", MessageBoxButtons.OK, MessageBoxIcon.Error);
          }
          finally
          {
            cn.Close();
          }
      }

O código da rotina que envia um email ao usuário é dado a seguir. Você poderia chamar esta rotina logo após o usuário ter sido incluído na tabela Acessos. Eu não inclui a chamada no código deixando a seu critério a sua utilização ou não:

  public void EnviaMensagem(string ParaEndereco, string ParaNome, string _senha)
   {
       var cliente = new SmtpClient(txtEnderecoSMTP.Text, (int)nupPortaSMTP.Value)
        {
            Credentials = new NetworkCredential(txtEmailUsuarioSMTP.Text, txtSenhaEmailSMTP.Text),
            EnableSsl = true
        };
        cliente.Send(txtEmailUsuarioSMTP.Text, ParaEndereco, "Obrigado !", "Obrigado por seu registro ! \n Seu usuário/senha são: \n \nUsuário: "
                 + ParaNome.ToString() + "\nSenha: " + _senha.ToString());
   }

Concluímos assim o artigo mostrando a implementação do acesso e inclusão de informações em um banco de dados SQL Server Compact bem como a utilização da classe SHA256 para gerar um hash de uma string, no nosso caso a senha do usuário.

Pegue o projeto completo aqui:  Login_Hash.zip

João 8:42 Respondeu-lhes Jesus: Se Deus fosse o vosso Pai, vós me amaríeis, porque eu saí e vim de Deus; pois não vim de mim mesmo, mas ele me enviou.

João 8:43 Por que não compreendeis a minha linguagem? é porque não podeis ouvir a minha palavra.

João 8:44 Vós tendes por pai o Diabo, e quereis satisfazer os desejos de vosso pai; ele é homicida desde o princípio, e nunca se firmou na verdade, porque nele não há verdade; quando ele profere mentira, fala do que lhe é próprio; porque é mentiroso, e pai da mentira.

 

             Gostou ?   Compartilhe no Facebook   Compartilhe no Twitter

Referências:


José Carlos Macoratti