Ataques envolvendo tentativas de roubo ou, até mesmo, a modificação indevida de informações representam uma ameaça constante em sistemas dos mais variados tipos. Senhas, mensagens de conteúdo sigiloso e números de cartões de crédito são apenas alguns exemplos de dados bastante visados pelos responsáveis em levar a cabo estas ações.

Diante de um cenário em que o acesso não autorizado a informações pode resultar em consequências imprevisíveis, muitas organizações empreendem grandes esforços buscando garantir a segurança de seus dados.

A criptografia é, sem sombra de dúvidas, uma das práticas mais difundidas para assegurar a integridade e a confidencialidade de informações. O conceito de integridade refere-se à garantia de que dados não serão alterados de forma acidental ou, mesmo, deliberadamente, em processos que envolvam a transmissão de informações de uma fonte a outra. Já a ideia de confidencialidade remete à noção de que elementos sem autorização não possuirão o acesso a dados classificados como restritos.

Basicamente, o processo de criptografia consiste na criação de uma codificação que protege um conjunto de informações, empregando para isto alguma espécie de algoritmo e configurações conhecidos apenas por um sistema (ou ainda, por prováveis aplicações relacionadas a este último).

Diversos algoritmos computacionais foram desenvolvidos ao longo do tempo. Tais métodos podem ser agrupados em duas formas de se proceder com a criptografia de dados:

  • Simétrica: envolve o uso de uma chave privada, a qual é compartilhada pelo mecanismo que gera um conjunto de dados criptografados, assim como por aquele que receberá tais informações. Na prática, isto significa que uma mesma chave será empregada tanto para encriptar, quanto para converter os dados protegidos ao seu estado original;
  • Assimétrica: emprega duas chaves de criptografia (pública e privada) e que estão relacionadas matematicamente entre si. Normalmente, informações são encriptadas por meio da chave pública, ao passo que a chave privada relacionada serve de base para se reverter a criptografia. A criptografia assimétrica oferece maior segurança que métodos simétricos, embora esteja normalmente associada à utilização de certificados digitais (o que envolverá custos adicionais para a aquisição deste tipo de serviço).

A plataforma .NET suporta nativamente os dois tipos de criptografia aqui descritos. Os diferentes recursos que permitem a proteção de informações seguindo estes princípios encontram-se no namespace System.Security.Cryptography. A classe Rijndael represente um bom exemplo de algoritmo simétrico .

O objetivo deste artigo é justamente demonstrar como o tipo Rijndael pode ser utilizado na criptografia de informações em aplicações .NET. Para isto será construído um projeto de testes, com funcionalidades que possibilitam encriptar e decodificar strings fornecidas por um usuário.

Criando a aplicação de exemplo

A solução apresentada neste artigo foi criada no .NET framework 4.5, através da utilização do Microsoft Visual Studio 2012 Professional.

Para gerar o projeto de testes será necessário, dentro do Visual Studio, acessar o menu File, opção New, sub opção Project. Dentro da tela New Project (Figura 1) selecionar o template Windows Forms Application, informando no campo Name o nome da aplicação a ser gerada (“TesteCriptografia”, neste caso); no campo Location é possível ainda definir o diretório no qual serão criados os arquivos para este projeto.

Criando um projeto Windows Forms para testes

Figura 1: Criando um projeto Windows Forms para testes

Criação da classe utilizada para encriptar/decriptar textos

Com o projeto TesteCriptografia criado, será necessário prosseguir com a implementação das classe responsável por criptografar/descriptografar sequências de texto (este tipo será chamado de “CriptografiaHelper”).

A fim de gerar o arquivo necessário à codificação da classe CriptografiaHelper, clicar dentro do Solution Explorer com o botão direito do mouse sobre a aplicação TesteCriptografia, escolhendo em seguida no menu de atalho a opção Add, sub opção New Item. Neste momento será exibida a tela Add New Item (Figura 2); preencher o campo Name com “CriptografiaHelper.cs”.

Criando o arquivo para implementação da classe CriptografiaHelper

Figura 2: Criando o arquivo para implementação da classe CriptografiaHelper

Foram definidos no tipo estático CriptografiaHelper (Listagem 1) as seguintes operações envolvendo técnicas de criptografia simétrica:

  • CriarInstanciaRijndael: responsável pela criação de novas instâncias da classe Rijndael (namespace System.Security.Cryptography);
  • Encriptar: realiza a criptografia de texto normal, devolvendo como resultado uma string em que os diferentes bytes encriptados estão representados no formato hexadecimal;
  • ArrayBytesToHexString: converte um array de bytes em uma string, com cada elemento tendo sido transformado em um valor correspondente em texto hexadecimal;
  • Decriptar: efetua a decriptação de uma string, partindo do pressuposto que o valor a ser convertido é formado por uma sequência de caracteres representando o equivalente hexadecimal de um conjunto de bytes;
  • HexStringToArrayBytes: converte uma string com valores hexadecimais em um array cujos itens serão do tipo byte.

OBSERVAÇÃO: A fim de evitar problemas decorrentes da manipulação de caracteres especiais, optou-se pela conversão dos bytes criptografados para uma string no formato hexadecimal. Caso não se fizesse isto, haveria a probabilidade de ocorrerem erros ao se tentar decriptar uma string gerada anteriormente.

O método privado CriarInstanciaRijndael permite a geração de novos objetos do tipo Rijndael (através de uma chamada ao método estático Create), com essas instâncias sendo utilizadas pelas operações Encriptar e Decriptar.

A classe Rijndael é baseada em um algoritmo de mesmo nome e também conhecido como “Advanced Encryption Standard” (ou simplesmente “AES”); este padrão depende de uma chave e de um vetor de inicialização para a execução de operações de criptografia.

Um vetor de inicialização nada mais é do que um array de bytes que reforça, por meio de operações randômicas, a segurança em um processo de criptografia. O objetivo disto é dificultar o sucesso de ataques que buscam descobrir a chave que está sendo utilizada; normalmente, tais situações envolveriam a análise de diversas tentativas de encriptação/decriptação, procurando desse modo identificar algum padrão e assim se checar ao "segredo" empregado.

CriarInstanciaRijndael recebe como parâmetros duas strings, com a primeira destas correspondendo à chave de criptografia, ao passo que o segundo valor refere-se ao vetor de inicialização.

Ambos os parâmetros declarados em CriarInstanciaRijndael serão convertidos em arrays de bytes, invocando-se para isto o método GetBytes da propriedade ASCII, a qual pertence à classe estática Encoding (namespace System.Text). O conteúdo em bytes da chave será então associado à propriedade Key, já o valor do vetor de inicialização será atribuído à propriedade IV (sigla do inglês "Initialization Vector").

O algoritmo Rijndael suporte chaves de 128, 192 ou 256 bits; já para um vetor de inicialização, espera-se um valor formado por 128 bits. Pelo fato de cada caractere corresponder no padrão ASCII a 8 bits ou 1 byte, estão sendo efetuadas consistências para se garantir que a chave possua 16, 24 ou 32 caracteres, além do vetor de inicialização se basear em uma string com 16 caracteres.

Caberá à operação Encriptar executar a criptografia de textos utilizando instâncias da classe Rijndael. Este método recebe como parâmetros strings contendo a chave, o vetor de inicialização e um valor a ser encriptado.

Conforme pode ser observado, o processo de encriptação envolve as seguintes atividades:

  • Criação de uma instância da classe Rijndael (variável “algoritmo”), a qual é obtida realizando-se uma chamada ao método CriarInstanciaRijndael;
  • A invocação do método CreateEncryptor a partir da variável “algoritmo” irá gerar um objeto (referência “encryptor”) baseado na interface ICryptoTransform (namespace System.Security.Cryptography). Essa construção (interface ICryptoTransform) torna possível a execução de operações básicas de encriptação/decriptação;
  • A instanciação de um objeto (variável “streamResultado”) do tipo MemoryStream (namespace System.IO), de forma a possibilitar operações sobre um stream armazenado em memória. Stream é uma sequência de dados de qualquer tamanho; este tipo de estrutura representa a base a partir da qual a plataforma .NET permite a manipulação de arquivos e outros agrupamentos de informações;
  • A geração de uma referência ("csStream") da classe CryptoStream (namespace System.Security.Cryptography), empregando para isto os objetos do tipos MemoryStream e ICryptoTransform; além disso é utilizado o valor Write do enumeration CryptoStreamMode (namespace System.Security.Cryptography), indicando assim que o processo de criptografia procederá com a escrita de dados criptografados no objeto MemoryStream;
  • Uma instância do tipo StreamWriter é criada (variável “writer”), repassando-se ao construtor da mesma o objeto CryptoStream gerado no passo anterior; a invocação do método Write fará então que o valor associado ao parâmetro “textoNormal” seja criptografado;
  • Uma chamada ao método ArrayBytesToHexString, visando com isto converter um array de bytes (obtido através da operação ToArray do objeto MemoryStream) para uma string, em que cada posição equivale a um valor hexadecimal correspondente. Esta última ação encerra o processo de criptografia, retornando a quem invocou a operação Encriptar um texto já convertido.

Sobre o método privado ArrayBytesToHexString, é possível notar no mesmo a existência de uma chamada ao método estático ConvertAll da classe Array (namespace System). Este procedimento fará com que um array de bytes seja convertido numa construção equivalente, porém empregando o tipo string; o uso do formato "X2" permite a transformação de cada byte em uma string hexadecimal equivalente. Por fim, os itens do array de string são concatenados, gerando-se assim um texto criptografado.

O código da operação Decriptar é bastante semelhante à implementação do método Encriptar, contando apenas com algumas diferenças em virtude de se tratar de uma funcionalidade de decriptação:

  • Inicialmente é verificado se o texto a ser decriptado (parâmetro “textoEncriptado”) não corresponde a uma string vazia, bem como se o comprimento deste é valido (cada byte corresponde a duas posições de texto representado no formato hexadecimal; logo o tamanho da string em questão deve ser um número par);
  • Um objeto do tipo ICryptoTransform é então criado, acionando-se para isto o método CreateDecryptor a partir de uma instância da classe Rijndael;
  • Um MemoryStream também é gerado, baseando-se para isso num array de bytes obtido através da conversão do parâmetro "textoEncriptado" via método HexStringToArrayBytes;
  • Também será instanciado um objeto CryptoStream, utilizando neste caso as referências já criadas para os tipos MemoryStream e ICryptoTransform;
  • Finalmente, é gerada uma instância do tipo StreamReader (namespace System.IO), fornecendo-se ao construtor da mesma a referência CryptoStream obtida no passo anterior. A invocação do método ReadToEnd sobre o objeto StreamReader produzirá como resultado uma string cujo conteúdo já se encontra decriptado.

Quanto ao método HexStringToArrayBytes, esta operação recebe como parâmetro uma string criptografada e cujos bytes encontram-se representados no formato hexadecimal. HexStringToArrayBytes devolverá como resultado de seu processamento um array de bytes. Cada conjunto de 2 caracteres corresponde a um byte, de forma que esta informação é convertida através do método ToByte da classe Convert (repassando-se como parâmetros a esta última uma string de 2 caracteres, bem como o valor 16 que indica que se está sendo considerado um número na base hexadecimal).

Listagem 1: Classe CriptografiaHelper


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.IO;

namespace TesteCriptografia
{
    public static class CriptografiaHelper
    {
        private static Rijndael CriarInstanciaRijndael(
            string chave, string vetorInicializacao)
        {
            if (!(chave != null && 
                  (chave.Length == 16 ||
                   chave.Length == 24 ||
                   chave.Length == 32)))
            {
                throw new Exception(
                    "A chave de criptografia deve possuir " +
                    "16, 24 ou 32 caracteres.");
            }

            if (vetorInicializacao == null ||
                vetorInicializacao.Length != 16)
            {
                throw new Exception(
                    "O vetor de inicialização deve possuir " +
                    "16 caracteres.");
            }

            Rijndael algoritmo = Rijndael.Create();
            algoritmo.Key =
                Encoding.ASCII.GetBytes(chave);
            algoritmo.IV = 
                Encoding.ASCII.GetBytes(vetorInicializacao);

            return algoritmo;
        }

        public static string Encriptar(
            string chave,
            string vetorInicializacao,
            string textoNormal)
        {
            if (String.IsNullOrWhiteSpace(textoNormal))
            {
                throw new Exception(
                    "O conteúdo a ser encriptado não pode " +
                    "ser uma string vazia.");
            }

            using (Rijndael algoritmo = CriarInstanciaRijndael(
                chave, vetorInicializacao))
            {
                ICryptoTransform encryptor =
                    algoritmo.CreateEncryptor(
                        algoritmo.Key, algoritmo.IV);

                using (MemoryStream streamResultado =
                       new MemoryStream())
                {
                    using (CryptoStream csStream = new CryptoStream(
                        streamResultado, encryptor,
                        CryptoStreamMode.Write))
                    {
                        using (StreamWriter writer =
                            new StreamWriter(csStream))
                        {
                            writer.Write(textoNormal);
                        }
                    }

                    return ArrayBytesToHexString(
                        streamResultado.ToArray());
                }
            }
        }

        private static string ArrayBytesToHexString(byte[] conteudo)
        {
            string[] arrayHex = Array.ConvertAll(
                conteudo, b => b.ToString("X2"));
            return string.Concat(arrayHex);
        }

        public static string Decriptar(
            string chave,
            string vetorInicializacao,
            string textoEncriptado)
        {
            if (String.IsNullOrWhiteSpace(textoEncriptado))
            {
                throw new Exception(
                    "O conteúdo a ser decriptado não pode " +
                    "ser uma string vazia.");
            }

            if (textoEncriptado.Length % 2 != 0)
            {
                throw new Exception(
                    "O conteúdo a ser decriptado é inválido.");
            }


            using (Rijndael algoritmo = CriarInstanciaRijndael(
                chave, vetorInicializacao))
            {
                ICryptoTransform decryptor =
                    algoritmo.CreateDecryptor(
                        algoritmo.Key, algoritmo.IV);

                string textoDecriptografado = null;
                using (MemoryStream streamTextoEncriptado =
                    new MemoryStream(
                        HexStringToArrayBytes(textoEncriptado)))
                {
                    using (CryptoStream csStream = new CryptoStream(
                        streamTextoEncriptado, decryptor,
                        CryptoStreamMode.Read))
                    {
                        using (StreamReader reader =
                            new StreamReader(csStream))
                        {
                            textoDecriptografado =
                                reader.ReadToEnd();
                        }
                    }
                }

                return textoDecriptografado;
            }
        }

        private static byte[] HexStringToArrayBytes(string conteudo)
        {
            int qtdeBytesEncriptados =
                conteudo.Length / 2;
            byte[] arrayConteudoEncriptado =
                new byte[qtdeBytesEncriptados];
            for (int i = 0; i < qtdeBytesEncriptados; i++)
            {
                arrayConteudoEncriptado[i] = Convert.ToByte(
                    conteudo.Substring(i * 2, 2), 16);
            }

            return arrayConteudoEncriptado;
        }
    }
}

Implementando a tela para testes

Concluída a codificação da classe CriptografiaHelper, será preciso agora renomear o formulário principal da aplicação. Isto é feito, dentro da janela Solution Explorer, clicando com o botão direito sobre o arquivo Form1.cs e selecionando a opção Rename: alterar então este nome de arquivo para FormPrincipal.cs. Tal procedimento fará com que a identificação da classe que corresponde ao formulário principal da aplicação também passe a ser FormPrincipal. Além disso, modificar por meio da janela Properties do Visual Studio a propriedade Text de FormPrincipal para “Teste de Criptografia - Utilizando a Classe Rijndael”.

Adicionar a este formulário os controles que se encontram listados na Tabela 1.

Tipo do ControleNome do Controle
GroupBoxgrpConfiguracoes
LabellblChave
TextBoxtxtChave
LabellblVetorInicializacao
TextBoxtxtVetorInicializacao
GroupBoxgrpEncriptacao
LabellblTextoAEncriptar
TextBoxtxtTextoAEncriptar
ButtonbtnEncriptar
LabellblResultadoEncriptacao
TextBoxtxtResultadoEncriptacao
GroupBoxgrpDecriptacao
LabellblTextoADecriptar
TextBoxtxtTextoADecriptar
ButtonbtnDecriptar
LabellblResultadoDecriptacao
TextBoxtxtResultadoDecriptacao
ButtonbtnSair

Tabela 1: Controles a serem adicionados ao formulário FormPrincipal

Já a Tabela 2 lista algumas propriedades dos componentes adicionados a FormPrincipal que precisarão ser alteradas.

Nome do ControlePropriedadeValor
grpConfiguracoesTextConfigurações
lblChaveTextChave:
txtChaveMaxLength32
lblVetorInicializacaoTextVetor de Inicialização:
txtVetorInicializacaoMaxLength16
grpEncriptacaoTextEncriptação
lblTextoAEncriptarLabelTexto a ser ncriptado:
btnEncriptarText&Encriptar
lblResultadoEncriptacaoTextResultado:
txtResultadoEncriptacaoReadOnlyTrue
grpDecriptacaoTextDecriptação
lblTextoADecriptarLabelTexto a ser decriptado:
btnDecriptarText&Decriptar
lblResultadoDecriptacaoTextResultado:
txtResultadoDecriptacaoReadOnlyTrue
btnSairButton&Sair

Tabela 2: Propriedades a serem alteradas nos controles adicionados a FormPrincipal

OBSERVAÇÃO: Para efeitos de simplificação, serão omitidos deste artigo os valores de propriedades de controles que definam tamanhos (Size, por exemplo) ou posicionamento (Location, por exemplo).

A Figura 3 demonstra um exemplo, em termos gerais, de como ficaria o formulário FormPrincipal ao término da configuração dos controles que foram adicionados ao mesmo. Esta imagem foi gerada dentro do Visual Studio, por meio do editor visual desta ferramenta.

Formulário FormPrincipal em modo Design com todos os controles já configurados

Figura 3: Formulário FormPrincipal em modo Design com todos os controles já configurados

Por fim, na Listagem 2 é apresentado o código-fonte em que se definem os diferentes métodos de FormPrincipal:

  • btnEncriptar_Click: evento que é acionado ao se clicar no botão “btnEncriptar”, de maneira que se efetue a criptografia de uma string (via método Encriptar de CriptografiaHelper), levando em conta a chave e o vetor de inicialização informados por um usuário;
  • btnDecriptar_Click: evento Click do botão “btnDecriptar”. Possibilita a conversão de uma string criptografada anteriormente para texto normal (por meio da operação Decriptar do tipo CriptografiaHelper), empregando para isto a mesma chave e o vetor de inicialização utilizados durante a encriptação;
  • btnSair_Click: evento disparado ao se clicar no botão “btnSair”.

Listagem 2: Classe FormPrincipal


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace TesteCriptografia
{
    public partial class FormPrincipal : Form
    {
        public FormPrincipal()
        {
            InitializeComponent();
        }

        private void btnEncriptar_Click(object sender, EventArgs e)
        {
            try
            {
                txtResultadoEncriptacao.Text =
                    CriptografiaHelper.Encriptar(
                        txtChave.Text,
                        txtVetorInicializacao.Text,
                        txtTextoAEncriptar.Text);
            }
            catch (Exception ex)
            {
                txtResultadoEncriptacao.Text =
                    "Erro: " + ex.Message;
            }
        }

        private void btnDecriptar_Click(object sender, EventArgs e)
        {
            try
            {
                txtResultadoDecriptacao.Text =
                    CriptografiaHelper.Decriptar(
                        txtChave.Text,
                        txtVetorInicializacao.Text,
                        txtTextoADecriptar.Text);
            }
            catch (Exception ex)
            {
                txtResultadoDecriptacao.Text =
                    "Erro: " + ex.Message;
            }
        }

        private void btnSair_Click(object sender, EventArgs e)
        {
            Close();
        }
    }
}

Executando a aplicação de testes

Iniciando a execução da solução implementada neste artigo, será apresentada uma tela como aquela que consta na Figura 4.

Projeto TesteCriptografia em execução

Figura 4: Projeto TesteCriptografia em execução

Preencher agora a chave e o vetor de inicialização. Informar uma string a ser criptografada, executando na sequência a opção “Encriptar”. Como resultado disso. o texto será convertido para um valor que já se encontra encriptado (Figura 5); copiar tal valor para a memória, já que o mesmo será utilizado mais adiante.

Encriptando um valor

Figura 5: Encriptando um valor

Fornecer agora um novo valor a ser criptogrado, repetindo os mesmos passos do teste anterior (Figura 6).

Executando um segundo teste de criptografia

Figura 6: Executando um segundo teste de criptografia

Preencher agora o campo “Texto a ser decriptado” com a string que foi gerada no primeiro teste. Acionar o botão “Decriptar”: neste momento, o texto encriptado será convertido para o seu valor original (Figura 7).

Decriptando texto

Figura 7: Decriptando texto

Conclusão

Conforme discutido ao longo deste artigo, a plataforma .NET oferece um amplo suporte na implementação de funcionalidades que dependam de mecanismos de encriptação. Os principais algoritmos de criptografia (tanto simétricos, quanto assimétricos) contam com classes próprias no .NET Framework, sendo um bom exemplo disto o tipo Rijndael.

Por mais que a criptografia contribua para uma maior proteção de dados sensíveis, a adoção desta prática não garante que uma aplicação estará livre de ataques. A segurança de informações é um assunto bastante amplo e, frequentemente, exigirá que as organizações combinem diversas técnicas a fim de alcançarem os resultados esperados.

Espero que o conteúdo aqui apresentado tenha sido útil. Até uma próxima oportunidade!