Blog de programación, donde ademas de encontrar pequeños programas en C# tambien publicaré pequeñas ayudas para SQL Server

Vladimir Miranda - vladivirus666@gmail.com. Con la tecnología de Blogger.

viernes, 6 de octubre de 2017

Cifrado en una Web Api


Hola nuevamente, después de mucho tiempo decido hacer un nuevo post de algo que me ha pasado en mi diario trajinar; bueno al punto, me solicitaron de una empresa X que necesitaban informacion de mi empresa, la sugerencia fue crear una Web Api para que ellos puedan consumir la informacion e implementarla en su requerimiento.
En este punto doy por entendido que todos sabemos realizar una Web Api (ya crearé un post con este tema); mi inconveniente era que, al publicar mi Web Api dejá al descubierto la informacion para todo aquel que sepa la url.
Quiero aclarar que este post no pretende ser una “solución completa” al problema, pero para mi funciona bastante bien.

Conceptos básicos de cifrado

Hay varios métodos de encriptacion, a grandes rasgos podríamos indicar que existen dos tipos:
  • De clave simétrica
  • De clave asimétrica
En los métodos de clave simétrica, cliente y servidor comparten una clave. Dicha clave es usada para generar texto cifrado a partir de texto plano… y viceversa. Es decir alguien que conozca la clave puede tanto enviar mensajes cifrados y descifrarlos.
En los métodos de clave asimétrica cada uno de los actores tiene un par de claves. Ambas claves sirven tanto para cifrar como para descifrar, pero con una particularidad: lo cifrado con una clave debe ser descifrado con la otra y viceversa.
Los métodos asimétricos tienen a priori una seguridad mayor que el método simétrico. Imagina a alguien llamado Alice que quiera enviar un mensaje a Bob. Con un método simétrico:
  1. Alice cifraría el mensaje con la clave K de encriptación
  2. Bob usaría la misma clave K para descifrarlo
  3. Si Bob quiere enviar una respuesta cifrada a Alice la cifraría usando la misma clave K y Alice lo descifraría usando K.
El punto débil aquí es el intercambio de claves. Si queremos que un mensaje de Alice a Bob solo pueda ser leído por Bob debemos asegurarnos que la clave K solo sea conocida por Alice y por Bob. Porque si dicha clave K llega a un tercero este podrá leer el mensaje.
Para evitar este punto entran los sistemas asimétricos:
  1. Bob tiene su par de claves Kpu y Kpr. Bob publica la clave Kpu en su web pero se guarda bajo llave la clave Kpr.
  2. Alice quiere mandarle un mensaje cifrado a Bob. Para ello lo cifra con la clave Kpu de Bob (que está en su web).
  3. Bob recibe el mensaje y lo descifra usando su propia clave Kpr (que tiene guardada bajo llave).
  4. Si Bob quiere responder a Alice puede cifrar su respuesta usando la clave Kpu de Alice (que también está en la web de Alice). Alice puede descifrar el texto usando su propia clave Kpr.
No hay intercambio de claves. Par enviar algo a Bob se cifra con su clave pública (la Kpu de Bob) y tan solo Bob lo puede descifrar con su Kpr (su clave privada). Los métodos asimétricos son más lentos (tanto para cifrar como para descifrar) que los asimétricos y tienen sus propios problemas: existen ataques sobre la Kpu para intentar averiguar la Kpr asociada, ya que es obvio que tienen alguna relación. Algunos algoritmos asimétricos han sido rotos gracias a que se han encontrado relaciones más o menos obvias entre ambas claves que permiten deducir (o acotar suficientemente el ámbito de búsqueda para permitir un ataque por fuerza bruta) la clave privada al saber la pública.

Cifrando y Descifrando datos

Bueno… veamos como podemos implementar un cifrado asimétrico usando WebApi. Nos centramos solo en el cifrado de datos (no entraremos en temas de firma digital aunque los principios sean muy parecidos). El algoritmo que usaremos será RSA (pero vamos, hay otros) a través de la clase RSACryptoServerProvider.
La propia clase se encarga de crear el par de claves necesario y luego pueden usarse los métodos ToXmlString() y FromXmlString() para guardar dichas claves o bien incorporar dichas claves en un RSACryptoServerProvider:
System.Security.Cryptography.RSACryptoServiceProvider rsacp = new System.Security.Cryptography.RSACryptoServiceProvider();
String clavePrivada = rsacp.ToXmlString(true);
String clavePublica = rsacp.ToXmlString(false);
El primero contiene el par de claves (pública y privada) mientras que el segundo contiene tan solo la clase pública. Por supuesto hay mejores maneras de guardar las claves pero para este post eso será suficiente.
Ahora vamos a la aplicación ASP.NET WebApi.
Este par de claves las he guardado en una clase (he copiado el contenido entero de clavePrivada en una propiedad Token.privada y lo mismo para Token.publica):
 class Token
    {
        internal const string privada = "[CONTENIDO DE clavePrivada]";
        internal const string publica = "[CONTENIDO DE clavePublica]";
    }
Ahora creamos un controlador WebApi para que nos devuelva la clave pública:
[HttpGet]
        public HttpResponseMessage Get()
        {
            HttpStatusCode sc = HttpStatusCode.OK;
            string dato = Aplicacion.Lead.Api.Models.Token.publica;
            var a = Request.CreateResponse(sc);
            a.Content = new StringContent(dato, System.Text.Encoding.UTF8, "application/xml");
            return a;
        }
[HttpGet]
public HttpResponseMessage Get()
   {
      HttpStatusCode sc = HttpStatusCode.OK;
      string dato = Aplicacion.Lead.Api.Models.Token.publica;
      var a = Request.CreateResponse(sc);
      a.Content = new StringContent(dato, System.Text.Encoding.UTF8, "application/xml");
      return a;
   }
Ya que mi Web Api esta configurada para entregar los datos en JSON al final utilizo StringContent para que la informacion de este controlador (en especifico de este metodo GET) sea en xml.
Una llamada GET a /api/Key nos permite obtener la clave pública del servidor:


Ahora veamos que se deberia hacer del lado del cliente
string kpu = null;
    // Obtenemos clave del servidor
    using (var client = new HttpClient())
    {
        client.DefaultRequestHeaders.Accept.ParseAdd("application/json");
        var data = await client.GetAsync("http://localhost:25986/api/key");
        kpu = await data.Content.ReadAsStringAsync();
        kpu = JsonConvert.DeserializeObject<string>(kpu);
    }
 
    var encrypted = EncryptData(kpu, "PRIVATE DATA TO SEND");
    await SendEncryptedDataAsync(encrypted);
}
static void Main(string[] args)
{
    DoEverythingAsync().Wait();
}
 
private static async Task DoEverythingAsync()
{
    string kpu = null;
    // Obtenemos clave del servidor
    using (var client = new HttpClient())
    {
        client.DefaultRequestHeaders.Accept.ParseAdd("application/json");
        var data = await client.GetAsync("http://localhost:25986/api/token");
        kpu = await data.Content.ReadAsStringAsync();
        kpu = JsonConvert.DeserializeObject<string>(kpu);
    }
 
    var encrypted = EncryptData(kpu, "PRIVATE DATA TO SEND");
    await SendEncryptedDataAsync(encrypted);
}
Básicamente obtenemos la clave pública (llamando a /api/Key del servidor), encriptamos unos datos y los enviamos. El código de EncryptData es:
private static byte[] EncryptData(string kpu, string data)
{
    var rsa = new RSACryptoServiceProvider();
    rsa.FromXmlString(kpu);
    var bytes = new byte[data.Length * sizeof(char)];
    Buffer.BlockCopy(data.ToCharArray(), 0, bytes, 0, bytes.Length);
    var encrypted = rsa.Encrypt(bytes, true);
    return encrypted;
}
Simplemente usamos el método FromXmlString para inicializar el RSACryptoServiceProvider con la clave pública obtenida previamente. Luego llamamos a Encrypt para encriptar los datos.
Finalmente el código de SendEncryptedDataAsync:
private static async Task SendEncryptedDataAsync(byte[] encrypted)
{
    using (var client = new HttpClient())
    {
        var content = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("CC", Convert.ToBase64String(encrypted)),
            new KeyValuePair<string, string>("Name", "edu"),
        });
        var result = await client.PostAsync("http://localhost:25986/api/Test", content);
        Console.WriteLine("Status Code: " + result.StatusCode);
    }
}
private static async Task SendEncryptedDataAsync(byte[] encrypted)
{
    using (var client = new HttpClient())
    {
        var content = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("CC", Convert.ToBase64String(encrypted)),
            new KeyValuePair<string, string>("Name", "edu"),
        });
        var result = await client.PostAsync("http://localhost:25986/api/Test", content);
        Console.WriteLine("Status Code: " + result.StatusCode);
    }
}
Enviamos dos campos en el POST, uno llamado CC (el encriptado) y otro llamado Name que NO está encriptado.
Para mi caso en particular lo que deseaba era que el cliente me envíe su “clave de consulta” cifrada. En un inicio mi api estaba de esta forma:

Con los cambios que realice me queda de esta forma

Asi al momento de la consulta debería incluirme el token, en mi Web Api con la clave privada decifrar el contenido y darle paso a la consulta que necesita si es que es correcta la “clave” enviada.
El codigo para descrifrar
 private bool ValidarClienteConsulta(string token)
        {
            byte[] anotherCrypt = Convert.FromBase64String(token);
            RSACryptoServiceProvider rsaPrivate = new RSACryptoServiceProvider();
            rsaPrivate.FromXmlString(Token.privada);
            byte[] decryptedRSA = rsaPrivate.Decrypt(anotherCrypt, true);
            var chars = new char[decryptedRSA.Length / sizeof(char)];
            Buffer.BlockCopy(decryptedRSA, 0, chars, 0, decryptedRSA.Length);
            var decryptedString = new string(chars);
            if (decryptedString == "[CLAVE ENTREGADA AL USUARIO]")
            { return true; }
            else { return false; }
        }
Y lo que tengo en mis métodos
public Newtonsoft.Json.Linq.JObject Get(string token)
        {
            if (ValidarClienteConsulta(token))
            {
//El codigo aquí
            }
            else
            {
                return new Newtonsoft.Json.Linq.JObject();
            }
        }

        
        public Newtonsoft.Json.Linq.JObject Get([FromUri]string id, string token)
        {
            if (ValidarClienteConsulta(token))
            {
//El codigo aquí
            }
            else
            {
                return new Newtonsoft.Json.Linq.JObject();
            }
        }
Y eso sería todo hasta el momento, cualquier inquietud o sugerencia que tengan; no olviden dejar sus comentarios.
Parte de este contenido fue tomado de https://geeks.ms/etomas/2014/07/01/cifrado-en-webapi/