Encriptar QueryString en ASP .NET sin modificar código

En un momento de calma y tranquilidad, cuando la aplicación está a punto de ser liberada… muy profesionalmente, el cliente pregunta: ¿Se encriptó la información de la URL?

Es el momento en donde sueltas el teclado y te vas corriendo piensas en lo que eso implica.  —«¿Cómo puedo modificar todo mi código de una manera rápida para encriptar y desencriptar todos los parámetros de todas las URL que manejo en mi aplicación?».

Profundizando en el problema: Necesitamos que «automáticamente» todas las variables que pasan a través de la URL sean cifradas. ¿Por qué? Porqué no quiero desvelarme toda la noche buscando en todos los lugares donde hice Request.QueryString para descifrar el parámetro (eso suponiendo que ya sabemos como cifrar información) y además, en todos los lugares donde envío el parámetro.  —«No me sirve esa opción, no a estas alturas».

Utilizando WebForms, es normal que en muchos lugares (demasiados en mi caso) habrá código como:

//
string valorDeParametro = Request.QueryString["nombreDeParametro"].ToString();
//

(Por cierto, me gusta más la palabra «cifrar» que «encriptar», incluso creo que está mal dicho… pero seguro más personas lo encontrarán con «encriptar», y tal..)

El caso es que se puede solucionar fácilmente. Alguna vez encontré éste método en algún artículo, por más que busqué no lo volví a encontrar. Así que lo dejo como parte de las memorias de ThanksNetwork… Si alguien lo encuentra por favor decidme!.

Entonces, la pregunta del millón ¿que hacemos?. Fácil (una vez que ya lo sabes), pues necesitamos ayuda de los magníficos módulos HTTP.

HttpModules

Los módulos Http son una especie de filtros que pueden «atrapar» un post antes y después de ser enviado. Es decir, pueden pre y post procesar las solicitudes a medida que se van generando. Una gran cantidad de servicios que ofrece ASP.NET se implementan bajo los módulos HTTP, por ejemplo: la seguridad, el estado de la sesión, etc.

El nivel más básico que podemos utilizar es implementando la interfaz System.Web.IHttpModule, basta con ver «desde metadatos» lo que deberíamos implementar:

using System;

namespace System.Web
{
    // Resumen:
    //     Proporciona eventos de inicialización y eliminación de módulos a la clase
    //     que lo implementa.
    public interface IHttpModule
    {
        // Resumen:
        //     Elimina los recursos (distintos de la memoria) utilizados por el módulo que
        //     implementa System.Web.IHttpModule.
        void Dispose();
        //
        // Resumen:
        //     Inicializa un módulo y lo prepara para el control de solicitudes.
        //
        // Parámetros:
        //   context:
        //     Un objeto System.Web.HttpApplication que proporciona acceso a los métodos,
        //     propiedades y eventos comunes a todos los objetos de aplicación existentes
        //     en una aplicación ASP.NET.
        void Init(HttpApplication context);
    }
}

Más información sobre la implementación de los módulos Http

Además, ¿debería estar registrado en algún lado, no? ¿O como se podría configurar mi aplicación para que entienda que cada petición debe pasar por mis clases que implementan la interfaz IHttpModule?

Desde el web.config ¿Por qué no? Por algo debe llamarse archivo de configuración!

<httpModules>
<add type="classname,assemblyname" name="modulename"/>
<remove name="modulename"/>
<clear/>
</httpModules>

Más información sobre el elemento httpModules

Encriptalo ya!

Solución:

Para hacerme la vida fácil, solo crearé una clase (al cliente le importa un carajo SOLID) a la cual bautizaré como QueryString, ésta heredará de IHttpModule (Ojo, sin olvidarnos que es una interfaz y debemos implementar sus métodos).

Y para agilizar mi post, los comentarios de cada situación van dentro del código:

public class QueryString : IHttpModule
{
    ///
/// variables const
    ///
    private const string nombreParametro = "q=";
    //private const string nombreParametro;
    private const string llaveEncriptacion = "key";

    ///
/// Salt para "reforzar" encriptado
    ///
    private readonly static byte[] salt = Encoding.ASCII.GetBytes(llaveEncriptacion.Length.ToString());

    ///
///
    ///
    public QueryString()
	{
        //Algún día necesitaré esto lol
	}

    ///
/// Implementamos Dispose de interfaz
    ///
    public void Dispose()
    {
        //Nothing
    }

    ///
/// Implementamos Init de Interfaz
    /// Con esto construimos un nuevo evento para manejar la petición
    ///
    ///
    public void Init(HttpApplication context)
    {
        context.BeginRequest += new EventHandler(context_BeginRequest);
    }

    ///
/// Evento que maneja la petición
    ///
    ///
    ///
    void context_BeginRequest(object sender, EventArgs e)
    {
        HttpContext context = HttpContext.Current; //Contexto http actual

        if (context.Request.Url.OriginalString.Contains("aspx") && context.Request.RawUrl.Contains("?")) //url contiene ".aspx" && "?" ?
        {
            string query = ExtraerCadena(context.Request.RawUrl);
            string ruta = ObtenerRutaVirtual();

            if (query.StartsWith(nombreParametro, StringComparison.OrdinalIgnoreCase))
            {
                // Desencripta queryString y se vuelve a establecer la ruta
                string rawQuery = query.Replace(nombreParametro, string.Empty);
                string decryptedQuery = Desencriptar(rawQuery);
                context.RewritePath(ruta, string.Empty, decryptedQuery);
            }
            else if (context.Request.HttpMethod == "GET")
            {
                // Encripta queryString y reedirecciona a la url encriptada
                //Encripta todas las queryString automáticamente. ****Eliminar esta parte si no se desea encriptar automáticamente y realizar otras acciones
                string encryptedQuery = Encriptar(query);
                context.Response.Redirect(ruta + encryptedQuery);
            }
        }
    }

    ///
/// Analiza la url actual y extrae la ruta virtual sin usar la queryString
    ///
    /// Ruta virtual de la url actual
    private static string ObtenerRutaVirtual()
    {
        string ruta = HttpContext.Current.Request.RawUrl;
        ruta = ruta.Substring(0, ruta.IndexOf("?"));
        ruta = ruta.Substring(ruta.LastIndexOf("/") + 1);
        return ruta;
    }

    ///
/// Analiza la url y regresa la queryString
    ///
    ///url a analizar
    /// QueryString sin signo "?"
    private static string ExtraerCadena(string url)
    {
        int indice = url.IndexOf("?") + 1;
        return url.Substring(indice);
    }

    ///
/// Encripta cualquier cadena con el algoritmo Rijndael.
    ///
    ///Cadena a encriptar
    /// Cadena encriptada Base64
    public static string Encriptar(string cadenaEntrada)
    {
        RijndaelManaged rijndaelCipher = new RijndaelManaged();
        byte[] textoPlano = Encoding.Unicode.GetBytes(cadenaEntrada);
        PasswordDeriveBytes llave = new PasswordDeriveBytes(llaveEncriptacion, salt);

        using (ICryptoTransform encryptor = rijndaelCipher.CreateEncryptor(llave.GetBytes(32), llave.GetBytes(16)))
        {
            using (MemoryStream memoryStream = new MemoryStream())
            {
                using (CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
                {
                    cryptoStream.Write(textoPlano, 0, textoPlano.Length);
                    cryptoStream.FlushFinalBlock();
                    return "?" + nombreParametro + Convert.ToBase64String(memoryStream.ToArray());
                }
            }
        }
    }

    ///
/// Desencripta la cadena encriptada previamente
    ///
    ///Cadena a desencriptar
    /// Cadena desencriptada
    public static string Desencriptar(string cadenaEntrada)
    {
        try
        {
            RijndaelManaged rijndaelCipher = new RijndaelManaged();
            byte[] datosCifrados = Convert.FromBase64String(cadenaEntrada);
            PasswordDeriveBytes llave = new PasswordDeriveBytes(llaveEncriptacion, salt);

            using (ICryptoTransform decryptor = rijndaelCipher.CreateDecryptor(llave.GetBytes(32), llave.GetBytes(16)))
            {
                using (MemoryStream memoryStream = new MemoryStream(datosCifrados))
                {
                    using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
                    {
                        byte[] textoPlano = new byte[datosCifrados.Length];
                        int contador = cryptoStream.Read(textoPlano, 0, textoPlano.Length);
                        return Encoding.Unicode.GetString(textoPlano, 0, contador);
                    }
                }
            }
        }
//Por implementación rápida, cualquier error o cualquier dato extra que introduzca el usuario en la url encriptada o algo por el estilo, lo enviaré a la misma //página de error personalizada
        catch (FormatException)
        {
            HttpContext context = HttpContext.Current;
            context.Response.Redirect("wfrmErrorPagina.aspx");
            return null;
        }
        catch (CryptographicException)
        {
            HttpContext context = HttpContext.Current;
            context.Response.Redirect("wfrmErrorPagina.aspx");
            return null;
        }
        catch (IndexOutOfRangeException)
        {
            HttpContext context = HttpContext.Current;
            context.Response.Redirect("wfrmErrorPagina.aspx");
            return null;
        }
        finally
        {
            //Destruimos o finalizamos lo que necesitemos. No olvidar que existe el método Dispose ;)
        }
    }
}

Una vez teniendo la clase creada, hay que añadirla al Web.config

<configuration>
    <system.web>
       <httpModules>
           <add type="QueryString" name="QueryString"/>
       </httpModules>
    </system.web>
<configuration>

Con todo eso, ya deberíamos estar viendo nuestros parámetros de la URL encriptados, sin necesidad de modificar alguna parte de nuestro código.

Saludos!

26 pensamientos en “Encriptar QueryString en ASP .NET sin modificar código

  1. hola amigo está interesante el tema pero tratando de usarlo me sale el error que dice «No se pudo cargar el tipo ‘QueryString'», sería de ayuda que publiques un ejemplo para descargar, muchas gracias

    • dIEGO,

      La clase la puedes implementar tal cual, obviamente solo la ajustas a tus necesidades, pero es funcional desde ya. De cualquier manera trataré de actualizar el post para poner un ejemplo.

      ¿Donde te marca el error?

      Gracias por comentar. Saludos!

  2. Esta barbaro… Pero peca por la automatizacion, los links no quedan encryptados a nivel navegador y pues se corre riesgo.

    Para que quede, fino fino, se implementa todo tal cual, excepto que Encryptar lo dejamos con return texto, y hacemos un encryptado manual, luego a cada bucle que genera los links con query string, los ponemos asi.

    hyperLink[i].NavigateUrl = «ges_animales.aspx» + QueryString.EncriptarManual(«?&idInt=» + _SP.ToString() + «&idTabla=» + ds.Tables[0].Rows[i][0].ToString().Trim() + «&grupo=» + dropGrupos.SelectedItem.Text.Trim() + «&subtitulo=» + ds.Tables[0].Rows[i][1].ToString().Trim());

    Este es un ejemplo desde mi propio codigo, y esto queda, JAMON JAMON, ya que aparece encryptado a nivel navegador, y luego el sistema lo desencripta en forma automatica.

    Excelente aporte.

  3. A La Hora de Nombrar La Clase en El Web Config Me Sale Un Error
    Dice que la configuracion no se aplica en el modo integrado de canalizacion administrada
    ¿Que puedo hacer?

    Estoy Trabajando con Visual Studio 2012

    Espero Pronta Respuesta

    Gracias

    Es URGENTE !

  4. muy interesante y util, pero una duda , al pasar el mouse sobre un enlace se puede ver gracias al explorador las variables get, como puedo ocultar esto?, por lo demas todo funciona bien

    • Correcto. Eso sucede porque las variables se encriptan a nivel petición. Es decir los httpmodules «capturan» la petición, transforman el contenido y lo entregan diferente al navegador, pero internamente las variables siguen ahí, igual que el enlace.

      Entonces te dejo dos opciones:
      – Una opción es que cambies el modo de encriptación.
      – La segunda es con un «truco» con javascript para ocultar lo que aparece en la barra de estado. Revisa este enlace: http://www.forosdelweb.com/f4/ocultar-link-parte-abajo-del-navegador-276159/

  5. la verdad no quisiera utilizar javascript, pero al modificar el modo de encriptacion, tendria que llamar a la funcion por cada enlace de mi pagina?, no existe alguna solucion, como la que propusiste en tu ejemplo

    • Igual al ejemplo, hasta donde sé no. Se puede utilizar la misma clase pero tendrías que hacerlo a nivel variable, como lo haz comentado, página por página…

      Ahora, es lo que conozco, puede que haya alguna otra solución extraña que desconozca… pero hasta donde sé, así como lo propuse con httpmodules, no.

      Gracias por tus comentarios, son de bastante utilidad. Voy a considerar tus dudas para añadirlas a la entrada.

      Saludos!

  6. he estado leyendo tu post y la verdad que me es bastante útil pero tengo una dudila… yo programo en vb en vez de en c y hay trozos de código los cuales no se poner en vb. Alguien podría ponerlo en vb?
    Gracias!

    • Por lo pronto no traigo tiempo de traducirlo a VB. Pero, si me dices donde estás batallando te puedo ir ayudando =D

      Gracias por comentar. Saludos!

  7. Hola!, primero que nada, que buen articulo el que haz publicado, estoy implementandolo en un proyecto MVC 4, al momento de crear las clases y ‘configurar’ el WebConfig, no me da ningun error, pero al momento de ejecutar el proyecto, este muestrar mis urls sin cifrado, nose que se me esta escapando, apreciaria mucho si me ayudaras con esto, saludos.

  8. Hey! Someone in my Twitter group shared this site with us so I came to take a
    look. I’m definitely loving the information. I’m bookmarking and will be tweeting this to my
    followers! Terrific blog and wonderful style and design. Keep up the excellent work!

  9. estoy intentando pasarlo a VB
    pero me quedé atorado aquí:

    public class QueryString : IHttpModule

    qué significa : IHttpModule ??
    seguramente hace referencia al objeto insertado en el web.config, pero cómo se escribe en VB?
    gracias!

Deja un comentario