Uma biblioteca de criptografia

por André Tomás Velloso *

Para ler todas as matérias da FórumAccess, assine a revista no endereço http://www.forumaccess.com.br/novo/

Pré Requisitos

Este artigo usa as seguintes tecnologias:

Conhecimentos básicos de Visual Basic ou C#.NET

Visual Basic .NET ou C#.NET

 

 

Para exemplifcar o uso das rotinas de Hash e criptografia simétrica do namespace System.Security.Cryptography criamos uma biblioteca de funções, uma classe chamada “MyCryptoLib”, com uma série de método estáticos para serem utilizados diretamente, e, uma aplicação simples para testar as rotinas de criptografia. A biblioteca foi escrita originalmente em C# e depois traduzida também para VB.Net para agradar gregos e troianos. É bom lembrar que isto não seria necessário uma vez que é possível usar uma classe escrita em C# numa aplicação em VB sem problema algum. Como o objetivo é entender as rotinas de criptografia, isto ajuda quem tem dificuldade com uma das linguagens.

Início da páginaInício da página

A CLASSE MYCRYPTOLIB

O primeiro problema na hora de usar as classes do namespace System. Security.Cryptography é que elas, por padrão utilizam como entrada e saída vetores de bytes. Isto não é bem um problema, uma vez que todo dado é na verdade um conjunto de bytes e também porque há outras classes no .Net Framework para endereçar as diversas conversões. Mas no uso do dia a dia não é tão prático ficar fazendo estas operações. Tanto para as rotinas de cálculo de Hash quanto para as rotinas de criptografia, foram utilizados nomes diferentes de métodos para tipos de retorno diferentes como também utilizamos a sobrecarga como forma de permitir a omissão de parâmetros na chamada e também definir padrões de comportamento (MD5, para Hash, UTF8 para conjunto de caracteres e Rinjndael para algoritmo simétrico). A classe contém também três enumerações para facilitar a escolha dos tipos de Hash, Algoritmos ou conjunto de caracteres.

Início da páginaInício da página

CÁLCULO DE HASHES

Foram criados então métodos estáticos para devolver o resultado de rotinas de Hash em forma de vetores de bytes, em string com representação do Hash em hexadecimais e também em StringBase64 (própria para representar dados em arquivos texto, já que nem todo byte tem sua representação em caractere) convertida a partir dos bytes do Hash. Ainda, sobre os métodos para calcular Hash da classe, eles permitem especificar o Algoritmo a ser utilizado para calcular o Hash (MD5,SHA1,SHA256,SHA384,SHA512), bem como, o tipo de conjunto de caracteres utilizados pelas strings (ASCII,UNICODE,UTF7,UTF8). O método principal para cálculo de hash da biblioteca é o CalcHashByte-Array que, a partir de um vetor de bytes e, segundo a escolha feita de algoritmo, devolve o resultado do cálculo em um vetor de bytes. Este método é utilizado por todos os outros métodos criados para cálculo do hash na biblioteca. O interessante da implementação é que foi reunido em um só método o cálculo do Hash, independente do algoritmo escolhido. Assim, utilizamos um switch/case para escolher a classe do .Net Framework a ser utilizada no cálculo.

public static byte[] CalcHashByteArray(byte[] Dados, HashMode hMode)

{ // Declara um HashAlgorithm para abrigar

// o objeto que vai fazer o cálculo

HashAlgorithm hash;

// Veri.ca qual foi o Algoritmo escolhido

// e instancia a classe correspondente

switch (hMode)

{

case HashMode.SHA1:

hash= new SHA1CryptoServiceProvider();

break;

case HashMode.SHA256 :

hash=new SHA256Managed();

break;

case HashMode.SHA384 :

hash=new SHA384Managed();

break;

case HashMode.SHA512 :

hash=new SHA512Managed();

break;

default:

hash=new MD5CryptoServiceProvider();

break; }

// Faz o calcúlo do Hash retornando um array de bytes

hash.Initialize();

return hash.ComputeHash(Dados); }

CRIPTOGRAFIA SIMÉTRICA

Como estamos usando a criptografia simétrica, optamos por utilizar a string Pwd para receber a senha que será utilizada para criptografar os dados. A partir desta senha utilizamos uma classe do .Net Framework para gerar as chaves e vetores de inicialização que os algoritmos de criptografia utilizam. A classe em questão chama-se PasswordDeriveBytes e utiliza um algoritmo de Hash escolhido para gerar uma seqüência de bytes para fazer a criptografia. Se utilizarmos a mesma senha, o resultado esperado será idêntico. Depois usa-se o método GetBytes para inicializar a chave e o vetor de inicialização (IV).

private static PasswordDeriveBytes MyPDB(string Pwd)

{ // Instancia um PasswordDeriveBytes chmando o construtor

// para criar uma seqüencia de bytes derivada da senha

// de um salt e usando o algoritmo de hash SHA512

// com 100 iterações

PasswordDeriveBytes pdb = new PasswordDeriveBytes(Pwd, new byte[]

{ 0x40, 0x6e, 0x64, 0x72, 0x33, 0x76, 0x33, 0x31,

0x31, 0x30, 0x35, 0x30, 0x2e, 0x6e, 0x65, 0x74},"SHA512", 100);

// Devolve o pdb para ser usado pela CryptoStream

return pdb; }

Usamos o método, MyCStream, para criar a CryptoStream que é a classe que, de acordo com o algoritmo escolhido, chave simétrica e vetor de inicialização, vai fazer a criptografia ou decifrar os dados. É neste ponto que usamos a classe PasswordDeriveBytes para buscar os bytes necessários a cada algoritmo. Veja que o tamanho da chave e o vetor de inicialização variam de acordo com o algoritmo. A criação da CrytoStream foicondensada em uma só classe,independentemente do algoritmo escolhido ou se a operação é de criptografar ou decifrar.

private static CryptoStream MyCStream(MemoryStream msEnc,

string Pwd, AlgList alg, bool toEnc)

{ // Declaramos uma CryptoStream para usar na encryptacao

CryptoStream cStream;

// Cria o PasswordDerivedBytes usando MyPdb

PasswordDeriveBytes EncPdb = MyPDB(Pwd);

// Usamos um dos Algorítimos simétricos de acordo com o parâmetro alg

switch (alg)

// Tamanhos de Keys, Blocos e IV variam de acordo com o algoritmo

// para cada algoritmo é possível veri.car valores válidos

// tamanho de Blocos e IV válidos através das propriedades

// LegalBlockSizes e LegalKeySizes.

// O tamanho do IV é o mesmo do tamanho do bloco.

// Assim usa-se o PasswordDerivedBytes para pegar os

// bytes correspondentes aos bits necessários.

// O padrão de todos algoritmos é fazer

// CBC (Cipher Block Chaining). Também é possível

// usar outros modos, mas este é o mais seguro.

// Ao criar a CriptoStream especi.ca-se que

// msEnc, a MemoryStream referenciada como parâmetro,

// como destino da operação, o algorítmo, assim como

// se a operação sera se encriptar ou desencriptar.

{

case AlgList.DES :

// Instancia o Algoritmo DES

DES encAlgDES = DES.Create();

// Usa-se uma chave de 64 bits

encAlgDES.Key=EncPdb.GetBytes(8);

// Usa-se IV de 64 bits para bloco de 64 bits

encAlgDES.IV=EncPdb.GetBytes(8);

if (toEnc)

{ // toEnc == true --> Cria a CryptoStream para encriptar

cStream=new CryptoStream(msEnc,

encAlgDES.CreateEncryptor(),

CryptoStreamMode.Write); }

else

{ // toEnc == false --> Cria a CryptoStream para desencriptar

cStream=new CryptoStream(msEnc,

encAlgDES.CreateDecryptor(),

CryptoStreamMode.Write); }

break;

case AlgList.TRIPLEDES :

// Instancia o Algoritmo TripleDES

TripleDES encAlg3DES = TripleDES.Create();

// Usa-se uma chave de 192 bits

encAlg3DES.Key=EncPdb.GetBytes(24);

// Usa-se IV de 64 bits para bloco de 64 bits

encAlg3DES.IV=EncPdb.GetBytes(8);

if (toEnc)

{ // toEnc == true --> Cria a CryptoStream para encriptar

cStream=new CryptoStream(msEnc,

encAlg3DES.CreateEncryptor(),

CryptoStreamMode.Write); }

else

{ // toEnc == false --> Cria a CryptoStream para desencriptar

cStream=new CryptoStream(msEnc,

encAlg3DES.CreateDecryptor(),

CryptoStreamMode.Write); }

break;

case AlgList.RC2 :

// Instancia o Algoritmo RC2

RC2 encAlgRC2 = RC2.Create();

// Usa-se uma chave de 128 bits

encAlgRC2.Key=EncPdb.GetBytes(16);

// Usa-se IV de 64 bits para bloco de 64 bits

encAlgRC2.IV=EncPdb.GetBytes(8);

if (toEnc)

{ // toEnc == true --> Cria a CryptoStream para encriptar

cStream=new CryptoStream(msEnc,

encAlgRC2.CreateEncryptor(),

CryptoStreamMode.Write); }

else

{ // toEnc == false --> Cria a CryptoStream para desencriptar

cStream=new CryptoStream(msEnc,

encAlgRC2.CreateDecryptor(),

CryptoStreamMode.Write); }

break;

default :

// Instancia o Algoritmo Rijndael

Rijndael encAlgR = Rijndael.Create();

// Usa-se uma chave de 256 bits

encAlgR.Key=EncPdb.GetBytes(32);

// Usa-se IV de 64 bits para bloco de 128 bits

encAlgR.IV=EncPdb.GetBytes(16);

if (toEnc)

{ // toEnc == true --> Cria a CryptoStream para encriptar

cStream=new CryptoStream(msEnc,

encAlgR.CreateEncryptor(),

CryptoStreamMode.Write); }

else

{ // toEnc == false --> Cria a CryptoStream para desencriptar

cStream=new CryptoStream(msEnc,

encAlgR.CreateDecryptor(),

CryptoStreamMode.Write); }

break; }

// Devolve a CriptoStream pronta para fazer a operação

Para criptografar utilizando as rotinas do namespace System.Security. Cryptography criamos um método privado simples que faz tanto a criptografia como também decifra em memória dados criptografados utilizando o algoritmo escolhido, Rinjndael, Des, Tripledes ou Rc2. O modo de operação deste método é sempre o mesmo e quem vaimesmo definir que tipo de operação será efetuada ou que algoritmo será utilizado é mesmo a CryptoStream criada com o método MyCStream. Criamos um método parecido para lidar com arquivos, sendo que este engloba em seu código a criação da CryptoStream.

private static byte[] CryptoMagic(byte[] EncriptadoOuNao,string Pwd,

AlgList alg,bool toEnc)

{ // Declaramos uma Memory Stream para guardar os bytes de

naoEncriptado

MemoryStream msEnc = new MemoryStream();

// Declaramos uma CryptoStream para usar na encryptacao

// e chamamos o método para criar a CryptoStream de

// acordo com o Algoritmo escolhido

CryptoStream crypStream = MyCStream(msEnc,Pwd,alg,toEnc);

// Faz a criptogra.a e envia o resultado para a MemoryStream

crypStream.Write(EncriptadoOuNao, 0, EncriptadoOuNao.Length);

// Fecha a CryptoStream uma vez que não há mais o que criptografar

crypStream.Close();

// Pegamos agora os bytes encriptados da MemoryStream num vetor

byte[] MagicCrytoResult = msEnc.ToArray();

// Retornamos o vetor de bytes encriptados

return MagicCrytoResult; }

Por fim, criamos vários outros métodos para facilitar o uso utilizando um mesmo identificador de método para cada tipo de retorno e função e assinaturas diferentes para facilitar a escolha dos demais parâmetros da criptografia ou adotar o padrão.

Início da páginaInício da página

UM EXEMPLO DE APLICAÇÃO

A aplicação exemplo é bastante simples e ilustra a situação onde é preciso criptografar algumasinformações do cliente antes de gravá-las em um banco de dados. Neste exemplo, ao invés de usar um banco de dados simples como o Access, optamos por usar arquivos Xml porque estes podem ser visualizados diretamente no Internet Explorer ou no Notepad sem a necessidade de nenhum programa suplementar. Ainda, como vantagem adicional do uso do Xml na aplicação, é interessante ver as rotinas utilizadas para carregar o arquivo de Schema para o Dataset, carregar os dados do arquivo Xmlpara o Dataset, assim como form criados dois botões para permitir uma navegação simples entre os registros presentes no Xml.

No início, os arquivosTabelaClientes.XmleTabelaClientes.Xsd, não existem e o programa vai criá-los na mesma pasta onde estiver sendo executado. Embora a biblioteca criada tenha vários métodos com diferentes sobrecargas, a aplicação ilustra em três aspectos a utilização do namespace System.Security.Cryptography. O primeiro deles é a utilização de um Hash MD5 da senha ao invés de gravar a senha em texto puro no Xml. O segundo é a utilização de criptografia simétrica para cifrar em memória um campo confidencial. Para não usar a mesma senha para todos os registros, optamos por usar o CPF de cada cliente como senha para iniciar o processo de criptografia.

Assim, cada cliente tem seus dados criptografados com uma senha diferente. A própria senha do cliente(ou seu Hash MD5)poderia ser usada. Repare que, quando este for o caso, numa eventual troca de senha, para não perder os dados para sempre, é preciso decifrar os dados criptografados com a senha antiga e criptografá-los novamente com a nova senha. Por último utilizamos também a criptografia simétrica pra encriptar um arquivo usando FileStreams na criptografia. Desta forma o arquivo a ser criptografado é encriptado e escrito à medida que é lido, em blocos de 4Kbytes.

<MeusClientes>

<Apelido>JS</Apelido>

<Con.dencial>noRlsjRLCjcctSFcFBqX87wi4/k+oXPoBTc4lw4hGsPReSHItBt/

hgLJXbGOumNbFPpqnW+1oosqtYTtY2CI64KpjKtros4qo6sHscikvpyd47EBaFR

kHe1fP68WpLsWe4VtrIwnxBdY+AIh9320CwjHeRhYB0AVGpbIJbx8AqU=

</Con.dencial>

<CPF>741.254.256-31</CPF>

<DataNasc>1955-10-11T00:00:00.0000000-03:00</DataNasc>

<NomeCompleto>João da Silva de Albuquerque Teixeira</NomeCompleto>

<Senha>BE6B9084A5DCDB09AF8F433557A2119C</Senha>

</MeusClientes>
Início da páginaInício da página

CONCLUSÃO

A aplicação utilizada para testar a biblioteca é bem simples, mas as rotinas podem ser usadas em aplicações do mundo real quase sem nenhuma alteração. A única ressalva que faríamos na utilização destas rotinas é em relação à escolha da chave a ser utilizada bem como onde guardá-la para que não fique exposta. Outro ponto é que será necessário adicionar a cada caso de utilização as rotinas de tratamento de erro adequadas.

* André Tomás Velloso (andre@velloso.com) é Analista de Sistemas, colaborador da Revista FórumAccess, é consultor técnico e colunista do caderno de Informática do Jornal da Tarde, é MCP, MCSA, MCSA:Security, MCSE, MCSE:Security, MCSA Windows 2003, e trabalha no departamento de Desenvolvimento da Agência Estado.


Início da páginaInício da página