En el mundo actual, se necesitan ciertos métodos de seguridad para asegurar la privacidad del usuario en cualquier aplicación. Uno de los métodos es el cifrado, que es lo que veremos en este post.

¿Qué es el cifrado?

El cifrado, a ejemplo práctico, es tener un contenido que queremos asegurar (por ejemplo, contraseñas del usuario o algún mensaje) y reestructurarlo con una clave para que un humano no sea capaz de entenderlo.

Ejemplo básico de cifrado
Ejemplo básico

Cifrado en MVC

En MVC hay incluido una librería llamada System.Security.Cryptography. Dicha librería incluye los métodos de diferentes tipos de cifrado. Ciertos tipos son más seguros que otros debido a la cantidad de bytes que son utilizados a la hora de realizar el cifrado.

En este post vamos a comparar dos tipos de cifrado. Uno será sin retorno y el otro será el cifrado Rijndael.

  • Sin retorno: Es un tipo de cifrado en el que el contenido se cifra y ya no se puede descifrar. Este es un cifrado muy útil a la hora de cifrar contraseñas.
  • Cifrado Rijndael: Es un cifrado que permite cifrar y descifrar con una misma clave. Es una forma de enviar contenido secreto, pero si un atacante descubre la clave podrá ver el contenido. No es muy recomendable usarlo.

Una vez dicho esto vamos a preparar el proyecto en el que vamos a trabajar.

Preparación del proyecto

Lo primero es abrir Visual Studio y crear un proyecto nuevo. El tipo de proyecto será ASP.NET Web Application (.NET Framework) que utiliza C#.

Creación del proyecto para cifrado 1

Después elige el nombre del proyecto, donde guardarlo y la versión de .NET Framework a utilizar. En mi caso utilicé la versión 4.7.2

Creación del proyecto para cifrado 2

Por último, escoge que plantilla a utilizar. Utilicé la plantilla de MVC ya que traía la configuración inicial de MVC incluida.

Creación del proyecto para cifrado 3

Una vez creado empezaremos por el cifrado sin retorno

Cifrado sin retorno

Este tipo de cifrado es muy útil para guardar las contraseñas de los usuarios en la base de datos debido a que, al guardar las contraseñas cifradas, si ocurriera algún ataque, los atacantes no obtendrían las contraseñas en texto plano de todos los usuarios.

En este post veremos dos formas (una más segura que la otra) de cómo usar este tipo de cifrado.

Cifrado simple

Este tipo de cifrado no es tan seguro como el que veremos a continuación, pero es algo básico que debería tener cualquier aplicación a la hora de proteger las contraseñas o datos privados de los usuarios.

Lo primero es crearnos una carpeta llamada Helpers en nuestro proyecto

Creación de carpeta en el proyecto de cifrado

Una vez creada la carpeta crearemos dentro de la carpeta una clase llamada CypherHelper.cs

Creación de la clase para cifrado CypherHelper.cs

CypherHelper.cs

En CypherHelper crearemos un método llamado CifradoHash() que se encargará de cifrar el mensaje que introduzcamos en distintos tipos de cifrado.

public static List<String> CifradoHash(String text)
 {
  UnicodeEncoding converter = new UnicodeEncoding();
  byte[] input = converter.GetBytes(text);
  List<String> output = new List<String>();
  // Tipos de cifrado a utilizar
  SHA1Managed sha1 = new SHA1Managed(); // No es recomendable.
  SHA256Managed sha256 = new SHA256Managed(); // Recomendado.
  MD5Cng md5 = new MD5Cng(); // No es recomendable.
  SHA512Managed sha512 = new SHA512Managed(); // Muy recomendado. 
  // Añadimos los resultados a la lista de Strings
  output.Add(converter.GetString(sha1.ComputeHash(input)));
  output.Add(converter.GetString(md5.ComputeHash(input)));
  output.Add(converter.GetString(sha256.ComputeHash(input)));
  output.Add(converter.GetString(sha512.ComputeHash(input)));
  // Devolvemos la lista
  return output;
}

El método devolverá los textos cifrados para luego poder pintarlos en la vista.

También crearemos el método CompararBytes() para comparar si los textos cifrados del mensaje introducido coinciden con los que se realizaron anteriormente.

private static bool CompararBytes(byte[] ar1, byte[] ar2)
{
  // Si no coincide la longitud de los arrays devolvemos false
  if(ar1.Length != ar2.Length)
    {
      return false;
    }
  else
  {
    for (int i = 0; i < ar1.Length; i++)
    {
      // Mientras coincidan nunca entrará aqui.
      if (!(ar1[i].Equals(ar2[i])))
      {
        // Si no coinciden, devolvemos false
        return false;
      }
    }
  // Si realiza todo el bucle devolvemos true.
  return true;
  }
}

Después crearemos el método CompararTexto() que usará el método anterior para devolver si el mensaje es correcto o no.

public static List<String> CompararTexto(String text, List<String> textoscifrados)
{
  List<String> resultados = new List<String>();
  List<String> textos = CypherHelper.CifradoHash(text);
  int contador = 0;
  // Por cada texto cifrado
  foreach(String txt in textos)
  {
    UnicodeEncoding converter = new UnicodeEncoding();
    byte[] arrayIntroducido = converter.GetBytes(txt);
    byte[] arrayAComparar = converter.GetBytes(textoscifrados[contador]);
    if (CypherHelper.CompararBytes(arrayIntroducido,arrayAComparar))
      {
        // Si devuelve true, es correcto
        resultados.Add("Es correcto");
      }
      else
      {
        // Si no, los valores no coinciden
        resultados.Add("No coincide");
      }
      contador++;
  }
  // Devolvemos la lista resultados
  return resultados;
}

Con esto ya tenemos los métodos necesarios para cifrar y comparar mensajes.

CifradoController.cs

En mi caso creé un nuevo Controller para poder diferenciarlo del que se crea por defecto (HomeController), pero se podría usar HomeController sin problemas.

Para hacerlo creamos un Controller en la carpeta Controllers. Lo crearemos vacío para no tener ActionResult que no vamos a utilizar.

Creación del controller para cifrado CifradoController

Después le damos el nombre CifradoController

Escribiendo el nombre del Controller para cifrado

Una vez creado necesitamos crear los ActionResult para poder recoger los datos que introduzca el usuario.

Para ello tendremos un ActionResult Get que sólo cargará la vista y un ActionResult Post para obtener los datos enviados por el usuario

// GET: SinRetorno
public ActionResult SinRetorno()
{
  return View();
}
// POST: SinRetorno
[HttpPost]
public ActionResult SinRetorno(String mensaje, String accion, String sha1, String md5, String sha256, String sha512)
  {
  // Si la accion es cifrar
  if(accion == "cifrar")
  {
    // Guardamos en un ViewBag el mensaje introducido
    ViewBag.Mensaje = mensaje;
    // Devolvemos a la vista la lista de Strings con los cifrados
    return View(CypherHelper.CifradoHash(mensaje));
  // Si la acción es comparar
  }else if(accion == "comparar")
  {
    // Guardamos en un ViewBag el mensaje introducido
    ViewBag.Mensaje = mensaje;
    // Instanciamos una lista de Strings para guardar los textos de la vista
    List<String> textoscifrados = new List<String>();
    textoscifrados.Add(sha1);
    textoscifrados.Add(md5);
    textoscifrados.Add(sha256);
    textoscifrados.Add(sha512);
    // Guardamos en ViewBag.Resultados los textos de "Es correcto" o "No coinciden" obtenidos del método CompararTexto
    ViewBag.Resultados = CypherHelper.CompararTexto(mensaje, textoscifrados);
    // Devolvemos los cifrados anteriores
    return View(textoscifrados);
  }
  return View();
}

Una vez preparados los ActionResult sólo quedaría crear la vista SinRetorno que tendrá el código necesario para realizar el cifrado.

Para crearlo haz clic derecho encima del nombre del ActionResult y añade la vista.

Añadir la vista desde el ActionResult

Una vez añadida la vista, quedaría realizar todo el código HTML/Razor para darle la funcionalidad deseada a la vista.

SinRetorno.cshtml

@{
    ViewBag.Title = "SinRetorno";
    List<String> resultados = new List<String>();
    if(ViewBag.Resultados != null)
    {
        resultados = ViewBag.Resultados as List<String>;
    }
}
@section styles{
    <style>
        p{font-size:14px; font-weight: bold;}
    </style>    
}
<h2>Cifrado sin retorno en MVC</h2>
<form method="post">
    <div>
        <label>Introduzca un mensaje a cifrar: </label>
        <textarea rows="5" name="mensaje" class="form-control">@ViewBag.Mensaje</textarea>
    </div>
    <br/>
    <button type="submit" class="btn btn-info" value="cifrar" name="accion">Cifrar mensaje</button>
    @if (Model != null)
    {
        <button type="submit" class="btn btn-info" value="comparar" name="accion">Comparar mensaje</button>
        <br/>
        <div>
            <label>Cifrado SHA1: </label>
            <textarea rows="5" name="sha1" class="form-control">@Model[0]</textarea>
            @if (resultados.Count > 0)
            {
                <p>@resultados[0]</p>
            }
        </div>
        <div>
            <label>Cifrado MD5: </label>
            <textarea rows="5" name="md5" class="form-control">@Model[1]</textarea>
            @if (resultados.Count > 0)
            {
                <p>@resultados[1]</p>
            }
        </div>
        <div>
            <label>Cifrado SHA256: </label>
            <textarea rows="5" name="sha256" class="form-control">@Model[2]</textarea>
            @if (resultados.Count > 0)
            {
                <p>@resultados[2]</p>
            }
        </div>
        <div>
            <label>Cifrado SHA512: </label>
            <textarea rows="5" name="sha512" class="form-control">@Model[3]</textarea>
            @if (resultados.Count > 0)
            {
                <p>@resultados[3]</p>
            }
        </div>
    }
</form>

Una vez listo ya podemos escribir un mensaje y poder cifrarlo o compararlo con el cifrado anterior.

Ejemplo de uso:

Ejemplo de prueba del cifrado Simple 1
Ejemplo de prueba del cifrado Simple 2
Ejemplo de prueba del cifrado Simple 3
Ejemplo de prueba del cifrado Simple 4

Cifrado con salt

Este tipo de cifrado es el mismo que el anterior, pero lo vuelve más seguro al utilizar un salt. El salt es un texto generado aleatoriamente que luego concatenaremos con el mensaje introducido para hacerlo más seguro, ya que para poder romper por fuerza bruta la contraseña tiene que saber tanto la contraseña del usuario como su salt.

Para realizar esto usaremos tanto el CypherHelper como el CifradoController anterior.

Para hacerlo funcionar tenemos que crear otro método en CypherHelper llamado GenerarSalt() que creará un salt aleatorio para cada contraseña.

CypherHelper.cs

// Con este método generamos un String aleatorio llamado Salt. Podemos escoger la cantidad de caracteres que tendrá el salt.
public static String GenerarSalt(int iteraciones)
{
  Random r = new Random();
  String salt = "";
// Bucle para generar los caractéres del salt. n veces introducidas en la variable iteraciones
  for (int i = 1; i <= iteraciones; i++)
  {
    // Generamos un número aleatorio
    int aleatorio = r.Next(1, 255);
    // Obtenemos el valor de la letra en formato ASCII del número generado aleatoriamente
    char letra = Convert.ToChar(aleatorio);
    // Lo concatenamos al salt
    salt += letra;
  }
  // Devolvemos el salt
  return salt;
}

Después iremos al CifradoController y crearemos los ActionResult Get y Post para SinRetornoSalt()

CifradoController.cs

// GET: SinRetornoSalt
public ActionResult SinRetornoSalt()
{
  return View();
}
// POST: SinRetornoSalt
[HttpPost]
public ActionResult SinRetornoSalt(String texto, String accion, String salt, String sha1, String md5, String sha256, String sha512)
  {
    // Si la acción es cifrar
    if (accion == "cifrar")
    {
      salt = CypherHelper.GenerarSalt(25);
      // Concatenamos el salt al texto introducido
      String txtACifrar = texto + salt;
      ViewBag.Mensaje = texto;
      ViewBag.Salt = salt;
      // Devolvemos los textos cifrados generados por el texto mas el salt
      return View(CypherHelper.CifradoHash(txtACifrar));
    }
    // Si la accion es comparar
    else if (accion == "comparar")
    {
      String txtACifrar = texto + salt;
      ViewBag.Mensaje = texto;
      ViewBag.Salt = salt;
      List<String> textoscifrados = new List<String>();
      textoscifrados.Add(sha1);
      textoscifrados.Add(md5);
      textoscifrados.Add(sha256);
      textoscifrados.Add(sha512);
      // Guardamos en el ViewBag si coinciden o no los textos cifrados
      ViewBag.Resultados = CypherHelper.CompararTexto(txtACifrar, textoscifrados);
      // Devolvemos en el Model los textos cifrados del último cifrado
      return View(textoscifrados);
    }
    return View();
}

Por último queda crear la vista como se hizo antes y realizar la funcionalidad con HTML y Razor.

Con esto ya podemos comprobar como funciona el cifrado con salt.

Ejemplo del cifrado con salt 1
Ejemplo del cifrado con salt 2
Ejemplo del cifrado con salt 3
Ejemplo del cifrado con salt 4

Ahora vamos a ver un ejemplo del cifrado Rijndael.

Cifrado Rijndael

Este tipo de cifrado utiliza una clave pública y una clave privada para realizar el cifrado. Es el método más seguro en varios aspectos, como la firma electrónica o para cifrar archivos y que estén seguros a la hora de enviarlos por Internet.

Para realizar este ejemplo crearemos dos métodos más en CypherHelper.

Uno será para cifrar el mensaje introducido por el usuario y el otro será para descifrarlo.

CypherHelper.cs

// Con este método ciframos el texto que introduzca el usuario y lo devolvemos
public static String CifrarTexto(String texto, byte[] clave)
{
  // Instanciamos el Rijndael
  Rijndael cifradoRijn = Rijndael.Create();
  byte[] encriptado = null;
  byte[] salida = null;
  try
  {
    // Asignamos en la clase Rijndael la clave introducida por el usuario
    cifradoRijn.Key = clave;
    // Generamos un vector de inicialización
    cifradoRijn.GenerateIV();
    // Convertimos el texto introducido a un array de bytes
    byte[] entrada = Encoding.UTF8.GetBytes(texto);
    // Encriptamos el mensaje
    encriptado = cifradoRijn.CreateEncryptor().TransformFinalBlock(entrada, 
    0, entrada.Length);
    // Inicializamos un array de bytes con la longitud del mensaje 
    encriptado y la longitud del vector de inicialización
    salida = new byte[cifradoRijn.IV.Length + encriptado.Length];
    // Copiamos el vector al principio del array salida
    cifradoRijn.IV.CopyTo(salida, 0);
    // Copiamos el mensaje encriptado en el array salida después del vector
    encriptado.CopyTo(salida, cifradoRijn.IV.Length);
  }
  catch (Exception)
  {
    throw new Exception("Error al cifrar los datos.");
  }
  finally
  {
    // Limpiamos el Rijndael
    cifradoRijn.Dispose();
    cifradoRijn.Clear();
  }
  // Convertimos el array de bytes salida a String
  String resultado = Encoding.Default.GetString(salida);
  // Devolvemos el resultado
  return resultado;
}
//Con este método usamos el mensaje cifrado y la clave introducida para descirar el mensaje
public static String DescifrarTexto(byte[] entrada, byte[] clave)
{
  // Instanciamos el Rijndael
  Rijndael cifradoRijn = Rijndael.Create();
  // Inicializamos un array con la longitud del vector de inicialización
  byte[] arrayTemporal = new byte[cifradoRijn.IV.Length];
  // Inicializamos un array que tendrá la longitud del mensaje encriptado
  byte[] encriptado = new byte[entrada.Length - cifradoRijn.IV.Length];
  String textodescifrado = String.Empty;
  try
  {
    // Asignamos la clave
    cifradoRijn.Key = clave;
    // Copiamos en el array temporal el vector de inicialización
    Array.Copy(entrada, arrayTemporal, arrayTemporal.Length);
    // Copiamos el mensaje sin el vector de inicialización en un array
    Array.Copy(entrada, arrayTemporal.Length, encriptado, 0, 
    encriptado.Length);
    // Asignamos el vector de inicialización
    cifradoRijn.IV = arrayTemporal;
    // Desencriptamos el mensaje
    byte[] prueba = 
    cifradoRijn.CreateDecryptor().TransformFinalBlock(encriptado, 0, 
    encriptado.Length);
    // Convertimos el mensaje descifrado a String
    textodescifrado = Encoding.UTF8.GetString(prueba);
  }
  catch (Exception ex)
  {
    throw new Exception(ex.Message);
  }
  finally
  {
    // Limpiamos el Rijndael
    cifradoRijn.Dispose();
    cifradoRijn.Clear();    
  }
  // Devolvemos el mensaje descifrado
  return textodescifrado;
}

Una vez creados estos dos métodos tenemos que ir a CifradoController para crear los ActionResult necesarios para pedir los datos al usuario.

CifradoController.cs

// GET: CifradoRinjdael
public ActionResult CifradoRijndael()
{
  return View();
}
// POST: CifradoRinjdael
[HttpPost]
public ActionResult CifradoRijndael(String texto, String clave, String textocifrado, String accion)
{
  byte[] clavebyte = new PasswordDeriveBytes(clave, null).GetBytes(32);
  String mensaje = null;
  switch (accion)
  {
    case "cifrar":
      mensaje = CypherHelper.CifrarTexto(texto, clavebyte);
      break;
    case "descifrar":
      byte[] textobytes = Encoding.Default.GetBytes(textocifrado);
      mensaje = CypherHelper.DescifrarTexto(textobytes, clavebyte);
      break;
    default:
      break;
  }
  ViewBag.Mensaje = mensaje;
  return View();
}

Ahora podemos solo queda crear la vista con el código HTML/Razor para pedir los datos al usuario.

CifradoRijndael.cshtml

@{
    ViewBag.Title = "CifradoRijndael";
}
<h2>Cifrado Rijndael en MVC</h2>
@using (Html.BeginForm())
{
    <div>
        <label>Clave: </label>
        <input type="text" name="clave" class="form-control" placeholder="Escriba la clave para (des)cifrar el mensaje" />
    </div>
    <div>
        <label>Mensaje: </label>
        <textarea rows="5" name="texto" class="form-control"></textarea>
    </div>
    <div>
        <button type="submit" name="accion" value="cifrar" class="btn btn-success">Cifrar mensaje</button>
        <button type="submit" name="accion" value="descifrar" class="btn btn-success">Descifrar mensaje</button>
    </div>
    <div>
        <label>Mensaje cifrado: </label>
        <textarea rows="5" class="form-control">@ViewBag.Mensaje</textarea>
        <input type="hidden" name="textocifrado" value="@ViewBag.Mensaje" />
    </div>
}

Con esto hecho, ya podemos comprobar si se realiza el cifrado y descifrado Rijndael.

Ejemplo de Cifrado Rijndael 1
Ejemplo de Cifrado Rijndael 2
Ejemplo de Cifrado Rijndael 3

Con este post has podido comprobar distintas formas de realizar cifrados que podrían ser implementadas de una manera segura en producción.

Autor: Adrian Moreno Gallardo
Curso: Microsoft MCSA Web Applications + Microsoft MCSD App Builder + Xamarin
Centro: Tajamar
Año académico: 2019 – 2020
Código / recursos utilizados / Otros datos de interés: GitHub 
Redes Sociales: LinkedIn

Leave a Comment

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.