Back-End

2 dez, 2013

Bouncy Castle – Senhas seguras usando SALT

Publicidade

Olá, caros leitores! Hoje vou falar sobre segurança e nesse artigo vamos aprender sobre os motivos de utilizar SALT de senha e sobre a poderosa biblioteca Bouncy Castle. Então mãos a obra!

Bouncy Castle

Apesar de ser pouco conhecida, a biblioteca Bouncy Castle é de longe a melhor opção quando se quer trabalhar com diversos algoritmos de criptografia, como DES, Triple DES, AES 256, Twofish, RSA, plataforma OpenPGP e diversos Hashes como SHA1, SHA-256, SHA-512, Whirlpool, GOST entre outros.

De acordo com a Wikipedia:

Bouncy castle é um coleção de APIs usadas em criptografia. Elas incluem APIs para as linguagens de programação Java e C#. Bouncy Castle é australiana em sua origem, então as restrições americanas de exportação de criptografia de software não se aplicam a ela.

Legal né? Mas porque eu utilizaria a Bouncy Castle no lugar das classes oferecidas pelo .Net Framework? A resposta é simples: RESTRIÇÃO! A biblioteca Bouncy Castle não está sob restrições de leis americanas, ao contrário do .Net Framework, dessa maneira você pode trabalhar com criptografia pesada sem o menor problema.

O SALT de senha

Você deve estar se perguntado “para que isso cara? Mais uma coisa que devo fazer! Não basta eu criar um Hash da senha e pronto?!”. Em teoria sim, mas podemos chegar a senha através de ataques de força bruta e com a tecnologia de hoje podemos processar o ataque usando o processador do computador juntamente com as placas de vídeo, o que, dependendo da senha, pode ser quebrado em pouco tempo.

Quando criamos um SALT de senha nós aprimoramos a segurança, já que para chegar ao Hash final nós precisamos da senha + SALT – isso evita que a senha 123456 que o usuário cadastrou (o que ainda é acontece) seja difícil de ser quebrada.

Para começar, primeiro faça o download da Bouncy Castle em http://www.bouncycastle.org/index.html. Agora crie um novo projeto Windows Forms e logo após crie uma nova classe. Em nosso exemplo ela vai se chamar SaltDeSenha e estará no namespace HashandDigest.

Após adicionar a referência para a Bouncy Castle utilizaremos a classe abaixo. Leiam os comentários da classe porque eles explicam o funcionamento da mesma e também faz parte do artigo.

using System;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Macs;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Security;
using System.Security.Cryptography;

namespace HashandDigest
{
    class SaltDeSenha
    {

        /*        
         * Iniciamos a variavel que instancia um Hash-based Message Authentication Code (HMac), que usa como         
         * parâmetro um algoritmos de hash no meu caso eu uso Sha512.
         * A Idéia de HMAC é combinar fun›ções hash conhecidas como MD5, SHA-1 e 
         * RIPEMD-160 a MAC (message authentication code)
        */
        private readonly IMac hMac = new HMac(new Sha512Digest());

        private void Blocks(
            byte[] PassowordBytes,
            byte[] SaltBytes,
            int IterationCount,
            byte[] iBuf,
            byte[] outBytes,
            int outOff)
        {

            //pega o tamanho do bloco para este MAC em bytes.
            byte[] state = new byte[hMac.GetMacSize()];

            //Configura o parametro de criptografia com uma senha
            //Como dito HMAC utiliza fun›ções hash conhecida com uma senha
            ICipherParameters param = new KeyParameter(PassowordBytes);

            //Iniciamos o objeto
            hMac.Init(param);

            //Verificamos se existe um SALT
            if (SaltBytes != null)
            {
                //Se existir atualizamos o Bloco com o SALT
                hMac.BlockUpdate(SaltBytes, 0, SaltBytes.Length);
            }

            //atualizamos o Bloco com os bytes de 
            hMac.BlockUpdate(iBuf, 0, iBuf.Length);

            //Calcula a fase final do MAC exclusivamente para o parâmetro de saída.
            //doFinal deixa o MAC no mesmo estado em que estava após a última inicialização.            
            hMac.DoFinal(state, 0);

            //Copia um intervalo de elementos de Array que começa no índice especificado de origem 
            //e os cola a outro Array que começam no índice especificado de destino.
            Array.Copy(state, 0, outBytes, outOff, state.Length);

            //Processa os blocos de acordo com a quantidade de interações definidas
            for (int count = 1; count != IterationCount; count++)
            {
                hMac.Init(param);
                hMac.BlockUpdate(state, 0, state.Length);
                hMac.DoFinal(state, 0);

                for (int j = 0; j != state.Length; j++)
                {
                    outBytes[outOff + j] ^= state[j];
                }
            }
        }

        private void IntToOctet(
            byte[] Buffer,
            int i)
        {
            Buffer[0] = (byte)((uint)i >> 24);
            Buffer[1] = (byte)((uint)i >> 16);
            Buffer[2] = (byte)((uint)i >> 8);
            Buffer[3] = (byte)i;
        }

        public byte[] GenerateDerivedKey(int dkLen, byte[] mPassword, byte[] mSalt, int mIterationCount)
        {

            //pega o tamanho do bloco para este MAC em bytes.
            int hLen = hMac.GetMacSize();
            int l = (dkLen + hLen - 1) / hLen;
            byte[] iBuf = new byte[4];
            byte[] outBytes = new byte[l * hLen];

            for (int i = 1; i <= l; i++)
            {
                IntToOctet(iBuf, i);
                Blocks(mPassword, mSalt, mIterationCount, iBuf, outBytes, (i - 1) * hLen);
            }

            byte[] output = new byte[dkLen];
            Buffer.BlockCopy(outBytes, 0, output, 0, dkLen);
            return output;

        }

        /*
         * Gera valores aleatórios usando a classe SecureRandom e retorna um array de bytes.       
         */
        public byte[] GenerateSalt()
        {

            byte[] salt = new byte[32];
            SecureRandom random = new SecureRandom();
            random.NextBytes(salt, 0, 32);

            return salt;
        }

        /*
         * Retorna a nova senha gerada em formato string
         */
        public string getPassword(byte[] result)
        {

            string x = "";

            for (int i = 0; i < result.Length; i++)
            {
                if (i % 2 == 0)
                {
                    x += result[i].ToString("X");
                }
                else
                {
                    x += result[i].ToString("x");
                }
            }

            return x;
        }

    }
}

Agora vamos criar nosso formulário. Ele deve ficar como o mostrado abaixo:

No evento click do botão adicione o seguinte código:

       private void btnCompute_Click(object sender, EventArgs e)
        {

           //Instancia da classe SaltDeSenha
            SaltDeSenha oSaltDeSenha = new SaltDeSenha();

            //Criamos o objeto que vai serializar nosso SALT
            JavaScriptSerializer js = new JavaScriptSerializer();

            /*
             * Serializa o SALT chamando o método GenerateSalt()
             * Assim você poderá salva-lo do banco de dados em formato string.
             */
            string SerializeSalt = js.Serialize(oSaltDeSenha.GenerateSalt());           

            /*O SALT Serializado deverá ficar como o mostrado aqui, perceba que ele é uma array com 
             * números aleatórios.
             * Você deve guardar esse SALT no banco de dados para ser usado futuramente, 
             * sem o SALT não será possivel validar a senha do usuário no futuro.
             */
            string SaltSerializado = "[80,190,165,107,149,124,152,136,174,4,126,125,251,31,1,12,183,19,182,130,231,174,173,118,123,211,170,24,165,181,0,102]";

            /*
             * Aqui nós deserializamos o SALT para uma array de bytes, simulando 
             * o retorno de uma consulta no banco de dados.             
             */
            byte[] SaltDeSerializado = js.Deserialize<byte[]>(SaltSerializado);

            /*
             * Nessa etapa vamos passar o seguintes parametros:
             * - Tamanho da senha em bits, para calcular use a fórmula: dkLen * 8
             *      Se quiser uma senha com 256bits de força o valor deve ser 32 pois 32 * 8 = 256
             *      Se quiser uma senha com 128bits de força o valor deve ser 16 pois 16 * 8 = 128
             *      
             * - A senha do usuário 
             * - O SALT 
             * - A quantidade de interações
             */
            byte[] result = oSaltDeSenha.GenerateDerivedKey(32, ASCIIEncoding.UTF8.GetBytes(txtPwd.Text), SaltDeSerializado, 5000);

            txtNewPassword.Text = oSaltDeSenha.getPassword(result);

            if (txtPwd.Text.Length > 0)
            {
                // Geramos um Hash SHA-512
                // A classe está inclusa no projeto para download
                txtHash.Text = GenerateHash.GetSHA512(txtNewPassword.Text);
            }
        }

Veja o resultado, a senha 123456 se transformou em uma nova senha altamente segura. E a partir da nova senha geramos um Hash.
Lembrando que o sistema gerou uma senha totalmente nova a partir de um SALT e mesmo que muitos usuários usem a mesma senha, o resultado NUNCA será o mesmo, pois o SALT sempre será diferente.

frmHashAndDigest2

Espero ter ajudado, pois esse é o meu único objetivo.

Até a próxima!