Criptografia em .NET: utilizando a classe Rijndael

Veja neste artigo como efetuar operações envolvendo a criptografia de textos, empregando para isto recursos disponibilizados pela classe Rijndael do .NET Framework.

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:

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.


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”.


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:

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:

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:

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.


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:

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.


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.


Figura 5: Encriptando um valor

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


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).


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!


Artigos relacionados