Encriptar y desencriptar cookies en una aplicación ASP.NET MVC

Posiblemente, las cookies sean uno de los elementos más característicos de la web desde sus principios. Como ya sabemos, las cookies almacenan información personal relevante en nuestros exploradores, de tal manera que en sucesivas interacciones con las aplicaciones Web, esta información pueda ser utilizada para la toma de decisiones y otros cometidos.

La información almacenada en forma de cookies en los exploradores, es siempre susceptible de ser manipulada o copiada por usuarios malintencionados, hasta el punto de comprometer gravemente la integridad y seguridad de nuestras aplicaciones web. Es por esto que en el caso de necesitar el uso de cookies en nuestras aplicaciones ASP.NET MVC, siempre es una buena práctica enviar a los exploradores de los usuarios cookies 'encriptadas'.

En este Post veremos como establecer cookies encriptadas desde nuestras aplicaciones ASP.NET MVC, para posteriormente desencriptarlas de una forma simple y efectiva utilizando la clase MachineKey.

Para realizar este ejemplo, crearemos una sencilla aplicación Web ASP.NET MVC que nos permita introducir un texto como contenido de una cookie, encriptarlo y asignar la cookie al explorador, y a continuación leer la cookie y desencriptar su contenido.

cookies

La clase MachineKey

ASP.NET dispone de un mecanismo propietario de 'cifrado' de datos a través de un algoritmo Hash, que es utilizado por el propio Framework para realizar tareas internas como encriptar el ViewState de una página ASP o el contenido de las cookies de autenticación de formularios. La clase MachineKey es la encargada de proporcionarnos estos métodos de 'cifrado' y hash que ASP.NET utiliza internamente.

En este ejemplo, utilizaremos los métodos Protect(Byte[], String[])Unprotect(Byte[], String[]) de la clase MachineKey, para encriptar y desencriptar el contenido de nuestra cookie de pruebas.

El Controlador

En primer lugar crearemos el Controlador de la aplicación CookiesController. Este contendrá además de la Acción principal Index(), las Acciones para 'encriptar/establecer' y 'recuperar/desencriptar' una determinada cookie de prueba.

    public class CookiesController : Controller
    {
        // GET: Cookies
        public ActionResult Index(string TextoCookie)
        {            
            return View();
        }

        public ActionResult Encriptar(string TextoCookie1)
        {
            if (!string.IsNullOrEmpty(TextoCookie1))
            {                
                // Descomponemos texto de la cookie en bytes.
                byte[] _textoCookie = Encoding.UTF8.GetBytes(TextoCookie1);
                // Encriptamos el texto de la Cookie y añadimos el resultado al ViewData.
                byte[] _TextoEncriptado = MachineKey.Protect(_textoCookie);
                this.ViewData.Add("CookieEncriptada", 
                    HttpServerUtility.UrlTokenEncode(_TextoEncriptado));

                // Establecemos la Cookie con el texto encriptado.
                HttpCookie Cookie1 = new HttpCookie("_CENC", 
                    HttpServerUtility.UrlTokenEncode(_TextoEncriptado));
                HttpContext.Response.SetCookie(Cookie1);
            }
            else
            {
                this.ViewData.Add("CookieEncriptada", string.Empty);
            }
            return View("Index");
        }

        public ActionResult Desencriptar()
        {
            // Recuperamos la Cookie 
            var _cookie = HttpContext.Request.Cookies["_CENC"];

            if (_cookie != null)
            {
                // Descomponemos valor de la cookie en bytes.
                byte[] _textoCookie = HttpServerUtility.UrlTokenDecode(_cookie.Value);
                // Desencriptamos el valor de la Cookie y añadimos el resultado al ViewData.
                byte[] _TextoDesencriptado = MachineKey.Unprotect(_textoCookie);
                this.ViewData.Add("CookieDesencriptada", 
                    Encoding.UTF8.GetString(_TextoDesencriptado));
            }
            else
            {
                this.ViewData.Add("CookieDesencriptada", string.Empty);
            }
            return View("Index");
        }        
    }

La Vista

Por último crearemos la Vista Index() de la aplicación. La estructura es bastante sencilla, simplemente crearemos dos formularios POST para el envío de datos al servidor, donde uno de ellos servirá para enviar el texto original de la cookie para ser encriptado, y el otro realizará el proceso contrario, enviará el texto encriptado para recuperar el original desencriptado. Todo el intercambio de datos entre la Vista y el Controlador se realizará a través del ViewData.

<h2>Cookies</h2>
<div class="row">
    @{
        string _textoEncriptado = ViewData["CookieEncriptada"] != null ? 
            ViewData["CookieEncriptada"].ToString() : string.Empty;
        string _textoDesencriptado = ViewData["CookieDesencriptada"] != null ? 
            ViewData["CookieDesencriptada"].ToString() : string.Empty;
    }
    <div class="col-md-6">
        @using (Html.BeginForm("Encriptar", "Cookies", FormMethod.Post))
        {
            <label>Contenido de la Cookie</label>
            @Html.TextArea("TextoCookie1", _textoDesencriptado, 10, 1, 
                    new { @class = "form-control" });
            <br />
            <input type="submit" value="Encriptar y establecer Cookie" 
                   class="btn btn-primary" />
        }
    </div>
    <div class="col-md-6">
        @using (Html.BeginForm("Desencriptar", "Cookies", FormMethod.Post))
        {
            <label>Resultado</label>

            if (!string.IsNullOrEmpty(_textoEncriptado))
            {
                <textarea name="TextoCookie2" class="form-control" cols="1" rows="10">
                    @_textoEncriptado
                </textarea>
                <br />
                <input type="submit" value="Desencriptar Cookie" class="btn btn-danger" />
            }
            else
            {
                <textarea name="TextoCookie2" class="form-control" cols="1" rows="10">
                    @_textoDesencriptado
                </textarea>
            }
        }
    </div>
</div>

Publicando nuestra aplicación (Web hosting)

Supongamos que hemos implementado un sistema de encriptación de cookies como el anteriormente explicado en una de nuestras aplicaciones Web ASP.NET MVC de trabajo. Una vez probada y 'testeada' la nueva incorporación en nuestro servidor local de desarrollo, y viendo que todo funciona correctamente, procedemos a publicar la aplicación en los servidores web de nuestro proveedor de hosting.

Comenzamos entonces a trabajar con nuestra aplicación en el nuevo entorno de producción, y nos damos cuenta de que en ciertas ocasiones el proceso de 'desencriptado' de las cookies falla sin motivo alguno aparente. Afortunadamente, este tipo de errores criptográficos en los procesos de 'cifrado' son un problema muy común y de fácil solución.

El problema (Web farm)

Los proveedores de servicios de hosting funcionan con 'granjas de servidores web' (Web farm) que implementan 'balanceo de carga' para maximizar el rendimiento de las peticiones http y minimizar la carga de trabajo. Cuando nuestra aplicación arranca por primera vez en un servidor web, un 'elemento' machineKey es creado automáticamente y asignado a la aplicación mientras esta esté activa en el servidor.

El elemento machineKey es el que contiene las claves criptográficas necesarias para encriptar y desencriptar las cookies de nuestra aplicación. El problema surge cuando es un servidor web diferente (por el balanceo de carga) el que atiende las peticiones de la aplicación, e intenta desencriptar las cookies existentes con un 'elemento' machineKey diferente al original. 

La solución

ASP.NET nos permite definir un elemento machineKey personalizado para cada una de nuestras aplicaciones web. Esto implica que los servidores web donde se ejecute la aplicación, no 'auto-generen' uno propio y utilicen el que la aplicación les propone.

La configuración del elemento machineKey se realiza dentro de la sección <system.web/> del archivo de configuración Web.config de la aplicación:

<machineKey validationKey="B2A3A7ED71A85AC9BCBBBAD8407F9642EEFA3B17FD2583CFD66A3BAF407900D2C45A85379FA2B4BE2397BECF14650306B6292C96418B772055F01E1EE751E434"
decryptionKey="E60EC68AB3752322D236E5727370620398818355316FB5FDFBF00EE4344AE7B7"
validation="SHA1" decryption="AES" /> 

Es recomendable generar un machineKey aleatorio (y diferente) para cada una de la aplicaciones que queramos publicar en nuestro Web hosting. Desde este sitio web: ASP.Net MachineKey Generator podemos generar nuestros machineKey en función del Framework ASP.NET que estemos utilizando.

  Compartir


  Nuevo comentario

El campo Comentario es obligatorio.
El campo Nombre es obligatorio.

Enviando ...

  Comentarios

No hay comentarios para este Post.


Perfil para Rafael Acosta en Stack Overflow en español, preguntas y respuestas para programadores y profesionales de la informática.

  Etiquetas

.NET Core .NET Framework .NET MVC .NET Standard AJAX ASP.NET ASP.NET Core ASP.NET MVC Bootstrap Buenas prácticas C# Cookies Entity Framework Gráficos JavaScript jQuery JSON JWT PDF Pruebas Unitarias Seguridad SEO SOAP Sql Server SqLite Swagger Validación Web API Web Forms Web Services WYSIWYG

  Nuevos


  Populares















Utilizamos cookies propias y de terceros para mejorar nuestros servicios y ofrecerle una mejor experiencia de navegación. Si continúa navegando consideramos que acepta su uso. Más información   Acepto