Envie dados via QueryString com mais segurança

Hoje vou dar continuidade ao assunto que abordei no artigo anterior, segurança em aplicações Web.
 
Como todos já devem saber semana passada foi lançado o novo sistema operacional da Microsoft o tão esperado Windows Vista, vocês devem estar se perguntando o que isso tem a ver com segurança? Eu digo que tem tudo, por que junto com esse novo SO vem também outros softwares atualizados e um deles é o browser Internet Explorer 7 que traz muitas atualizações mais uma delas me chamou muito atenção. Creio que alguns já devem ter notado que agora ao abrir uma janela popup já não é mais possível esconder a barra de endereços e vendo isso é que eu comecei a prestar atenção como muitos sistemas web trafegam informações quaisquer via querystring o que pode facilitar e muito um ataque ao sistema, além de alguns outros problemas que isso pode trazer para o mesmo.

Então hoje vou demonstrar como enviar dados via querystring de forma mais segura.

O primeiro passo é criar um projeto do tipo Class Library pra que nossa solução fique em uma .dll tal que possamos facilmente reutilizar e disponibilizar rapidamente, vamos colocar o nome dele de QueryStringSegura. Agora vamos adicionar uma referencia ao nosso projeto, para adicionar a referencia basta clicar com o botão direito do mouse sobre references em seu projeto QueryStringSegura e na listagem basta procurar por System.Web. Feito isso vamos adicionar uma classe dentro deste projeto e vamos chama - lá de criptografia.

Agora começou ficar interessante, vamos importar os namespaces que serão utilizados:
-
System.Security.Cryptography

- System.IO

É importante dizer que só devemos importar um namespace se este vai ser usado em boa parte do código caso contrario é interessante chamar no momento que for utilizar.


Uma outra coisa que eu costumo fazer é sempre implementar um destrutor em minhas classes e pra isso vamos implementar a interface IDisposable.

A Listagem 01 ilustra os métodos destrutores da classe criptografia:


/// <summary>

/// Destrutor da classe

/// </summary>

~criptografia(){

      this.Dispose();

}

/// <summary>

/// Método que limpa variaveis locais

/// </summary>

public void Dispose()

{

      GC.SuppressFinalize(this);

}
Listegem 01. Métodos destrutores.
 
Eu vou falar bem superficialmente sobre criptografia por que é um assunto que pode ser muito complexo e extenso.

Existem N métodos de se criptografar uma informação alguns deles pode ser vistos neste link:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemsecuritycryptography.asp

Felizmente o Framework nos oferece uma gama tremenda de classes que podem nos auxiliar na implementação de alguns destes métodos citados no link anterior, neste artigo vou demonstrar uma das formas de se implementar o método DES - Data Encrypt Standard de criptografia simétrica de 64-bits.

A Listagem 02 ilustra o método de criptografia:

/// <summary>

/// Método que codifica string

/// </summary>

/// <param name="strTexto">string</param>

/// <returns>string codificada</returns>

public string Codificar(string strTexto){

      DESCryptoServiceProvider oDes = new DESCryptoServiceProvider();

      byte[] IV = {121, 241, 10, 1, 132, 74, 11, 39, 255, 91, 45, 78, 14, 211, 22, 62};

      byte[] bytValor = System.Text.Encoding.ASCII.GetBytes(strTexto.ToCharArray());

      byte[] bytKey = System.Text.Encoding.ASCII.GetBytes(key.Substring(0, 8).ToCharArray());

 

      MemoryStream oMemory = new MemoryStream();

      CryptoStream oCStream = new CryptoStream(oMemory, oDes.CreateEncryptor(bytKey, IV), CryptoStreamMode.Write);

      oCStream.Write(bytValor, 0, bytValor.Length);

      oCStream.FlushFinalBlock();

 

      return Convert.ToBase64String(oMemory.ToArray());

}
Listagem 02. Método que criptografa informação.

C
omo podemos notar é preciso usar uma chave neste método essa chave é definida por vocês manualmente ou se acharem conveniente o próprio provider gera uma aleatoriamente:
DESCryptoServiceProvider oDes = (DESCryptoServiceProvider)DESCryptoServiceProvider.Create();

 

A Listagem 03 apresenta o método de decriptografia.

/// <summary>

/// Método que decodifica string

/// </summary>

/// <param name="strTexto">string codificada</param>

/// <returns>string decodificada</returns>

public string Decodificar(string strTexto){

      DESCryptoServiceProvider oDes = new DESCryptoServiceProvider();

      byte[] IV = {121, 241, 10, 1, 132, 74, 11, 39, 255, 91, 45, 78, 14, 211, 22, 62};

      byte[] bytValor = Convert.FromBase64String(strTexto);

      byte[] bytKey = System.Text.Encoding.ASCII.GetBytes(key.Substring(0, 8).ToCharArray());

 

      MemoryStream oMemory = new MemoryStream(bytValor, 0, bytValor.Length);

      CryptoStream oCStream = new CryptoStream(oMemory, oDes.CreateDecryptor(bytKey, IV), CryptoStreamMode.Read);

                 

      StreamReader oSr = new StreamReader(oCStream);

           

      return oSr.ReadToEnd();

}
Listagem 03. Método que descriptografa informação.
 
Por ser um método simétrico a chave usada para encriptar deve ser a mesma usada para desencriptar a informação.

Bem, agora vamos adicionar mais uma classe ao nosso projeto esta deve ser nomeada como CriptoQueryString e devemos importar o namespace:

 - System.Collections.Specialized;

 

Essa classe deve implementar o namespace NameValueCollection, além da interface IDisposable.

 

O namespace NameValueCollection foi escolhido, pois com ele nosso objeto oferece a possibilidade de recuperar os itens de sua collection a partir da chave ou do índice do item.

Essa classe é tão simples quanto à classe de criptografia, ela vai funcionar da seguinte forma. Vamos reimplementar o método ToString para que quando for chamado ele retorna a querystring já criptografada, isso é possível por que esse objeto é uma collection e cada parâmetro da nossa querystring vai ser um item dessa collection.

A Listagem 04 ilustra a codificação da nossa querystring:

/// <summary>

/// Re-implementacao do método ToString

/// </summary>

/// <returns>string criptografada</returns>

public override string ToString()

{

      string _strCriptografada = string.empty;

      _oCriptografia = new criptografia();

     

      _strCriptografada = System.Web.HttpUtility.UrlEncode(_oCriptografia.Codificar(this.Serialize()));

 

      return this._strCriptografada;

}

 

/// <summary>

/// Método que gera string a partir dos itens da collections

/// </summary>

/// <returns>querystring</returns>

private string Serialize(){

      System.Text.StringBuilder _strParam = new System.Text.StringBuilder();

      foreach(string key in this.AllKeys){

             _strParam.Append(key);

            _strParam.Append("=");

            _strParam.Append(base[key]);

            _strParam.Append("&");

      }

return _strParam.ToString();

}
Listagem 04.Override do método ToString() e método que codifica querystring.

 

Agora vamos à decodificação da querystring, eu criei neste objeto dois construtores um que não recebe parâmetro algum e outro que recebe a querystring criptografada, a Listagem 05 ilustra como deve ficar a decodificação:

/// <summary>

/// Método que gera colleciton a partir de uma querystring

/// </summary>

/// <param name="strParamQueryStringEncript">querystring criptografada</param>

private void Deserialize(string strParamQueryStringEncript)

{

      char[] s  = {'&'};

      char[] s2 = {'='};

 

      this._oCriptografia = new criptografia();

         

      this._strCriptografada = this._oCriptografia.Decodificar(strParamQueryStringEncript);

      this._strCriptografada = this._strCriptografada.Substring(0, this._strCriptografada.Length -1);

 

      string[] _strParametros = this._strCriptografada.Split(s);

 

      for(int i = 0; i < _strParametros.Length; i++){

            if(_strParametros[i].Split(s2).Length == 2)

                  base.Add(_strParametros[i].Split(s2)[0], _strParametros[i].Split(s2)[1]);

      }

}

/// <summary>

/// Construtor da classe

/// </summary>

public QueryStringSegura() : base(){}         

/// <summary>

/// Contrutor da classe

/// </summary>

/// <param name="QueryStringEncript">Querystring criptografada</param>

public QueryStringSegura(string QueryStringEncript){

      try

      {

            this.Deserialize(QueryStringEncript);

      }

      catch(Exception ex){

            throw new Exception("QueryString inválida.");

      }

}
Listagem 05. Método que decodifica querystring.
 

Agora vamos utilizar nossa .dll, como ilustra a listagem 06:

QueryStringSegura objQuery = new QueryStringSegura();

objQuery.Add("id","1");

objQuery.Add("nome","Diego");

objQuery.Add("sobrenome","Dias");

 

Response.Redirect("webform7.aspx?x=" + objQuery.ToString());
Listagem 06. Utilização da .dll em um webform.

 

Para recuperar os parametros é simples, assim como ilustra a listagem 07:

if(Request.QueryString["x"] != null){

      QueryStringSegura objQuery = new QueryStringSegura(Request.QueryString["x"]);

 

      foreach(string key in objQuery.AllKeys)

            Response.Write(key + "=" + objQuery[key] + "<br>");

}
Listagem 07. Recuperando parâmetros.

A Listagem 07 demonstra como recuperar todos parâmetros e seus respequitivos valores, porem pode se recuperar um parâmetro especifico, como ilustra a Listagem 08.

Response.Write("id :" + objQuery["id"]);
Listagem 08. Recuperando parâmetro especifico da collection de parâmetros.

O resultado obtido pode ser visto na Figura 01.

ddequerystringmsfig01.JPG

Figura 01. Projeto em execução.

Espero que tenham gostado e qualquer sugestão ou dúvida é só enviar um e-mail ou postar um comentário.

Obrigado,

Diego Dias
diego.dfsd@uol.com.br