Encriptar y desencriptar información sensible en C#

Como muchos sabemos, por temas de seguridad y de protección de datos (GDPR) debemos proteger la información sensible de nuestra base de datos como pueden ser los números de teléfono o el Email.

 

No debemos olvidar que en algunos casos guardaremos registro en los logs, yo personalmente no lo recomiendo, pero en caso de que tengamos que almacenar un log con el email, teléfono o dirección también podemos utilizar este método. 

 

Para realizar esta acción utilizaremos una interfaz que nos provee microsoft y nos ayuda a hacerlo de una forma muy sencilla. 

Además veremos un ejemplo práctico con C#.

 

 

 

1 - Por qué proteger la información sensible?

Tenemos que tener claro, que el motivo principal es cumplir la ley de protección de datos europea (GDPR) que debemos cumplir en caso de que cualquiera de nuestros usuarios resida en Europa. Así que si tu web es en inglés o castellano hay que aplicar un sistema para proteger la información sensible. 

 

Esta información es principalmente el teléfono, email, dirección física, o cualquier tipo de id. En resumen debemos proteger/encriptar cualquier información que sirva para identificar a nuestros usuarios. 

 

En caso de no hacerlo, y ser cazados, dependerá de la gravedad y cantidad de información de usuario que se filtre, pero las multas pueden llegar a millones de euros.

 

Por no hablar del tema “personal” o sobre qué va a pensar la gente de nuestra empresa o producto si se sabe que no realizamos buenas prácticas y o que su información se ha visto filtrada, es una muy mala imagen para la marca. 

GDPR issues

 

 

2 - Cómo encriptar datos en C#? 

Obviamente cuando cuando programamos debemos pensar desde el principio que vamos a tener que realizar acciones de encripción a nuestros datos, por ello si bien es cierto que en test no lo necesitamos, si lo haremos para producción. 

 

Microsoft ya ha pensado en esto y nos quiere hacer la vida más fácil, para ello nos proporciona la interfaz IDataProtectionProvider la cual nos permite crear “protectores”, los cuales nos sirven para encriptar y desencriptar datos.

 

 

3 - Proteger información sensible con IDataProtectionProvider

Para este post utilizaremos el código que hemos estado utilizando en el resto del módulo de web c# el cual se puede encontrar en Github

 

Para utilizar IDataProtectionProvider en nuestro código, debemos añadirlo a los servicios dentro de IServiceCollection llamando al metodo AddDataProtection.

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddDataProtection();
    ....
}

 

3.1 - Encriptar datos en C# con IDataProtectionProvider 

Una vez tenemos el servicio registrado lo inyectamos en los servicios que vayamos a utilziar.

public class PutPersonalProfile
{
    private readonly IPutPersonalProfileDependencies _dependencies;
    private readonly IDataProtector _protector;

    public PutPersonalProfile(IPutPersonalProfileDependencies dependencies, IDataProtectionProvider protectorProvider)
    {
        _dependencies = dependencies;
        _protector = protectorProvider.CreateProtector("PersonalProfile.Protector");
    }


    public async Task<Result<PersonalProfileDto>> Create(PersonalProfileDto personalProfile)
    {
        /*Código*/
    }

}

Como podemos ver estamos utilizando un IDataProtector pero insertamos IDataProtectionProvider en nuestro servicio, lo que significa que debemos crear nuestro IDataProtector a raíz de la interfaz insertada. 

 

Ambas interfaces son parte de Microsoft.AspNetCore.DataProtection lo que implica que debemos incluir el Assembly con la directiva using (al principio del fichero).

 

Y Para encriptar la información únicamente debemos llamar al método Protect de nuestro IDataProtector

Dónde realizar este cambio, dependera un poco de la lógica de nuestra aplicación, en mi caso en particular lo haremos en el mapper de nuestras DTO a la entity pero hay quien defiende que sea directamente antes de leer de la base de datos. Personalmente YO nunca utilizo un email (o teléfono) como clave primaria por lo que en el mapper es donde más sentido tiene.  

public static PostPersonalProfileWrapper MapToWraperEntities(this PersonalProfileDto profileDto, IDataProtector protector)
{
....
    string encryptedEmail = protector.Protect(profileDto.Email);
    string encryptedPhone = protector.Protect(profileDto.Phone);
....
}

 

Únicamente con esta acción ya tendremos la información encriptada en la base de datos. 

 

3.2 - Desencriptar datos en C# con IDataProtectionProvider 

Para desencriptar el proceso es el mismo, debemos inyectar en nuestro servicio la interfaz  IDataProtectionProvider  para crear IDataProtector

 

Debemos pasar como parámetro en el CreateProtector el mismo string que hemos utilizado para nuestro servicio post. Esto es debido a que obviamente para desencriptar necesitamos la misma “llave” que para encriptar. 

 

En este caso llamaremos al método .Unprotect(miembroadesencriptar)

public class PersonalProfile
{
    private readonly IGetPersonalProfileDependencies _dependencies;
    private readonly IDataProtector _protector;

    public PersonalProfile(IGetPersonalProfileDependencies dependencies, IDataProtectionProvider protectorProvider)
    {
        _dependencies = dependencies;
        _protector = protectorProvider.CreateProtector("PersonalProfile.Protector");
    }


    public async Task<Result<PersonalProfileDto>> GetPersonalProfileDto(string name)
    {
        ...
    }
   /*Más código*/

    private Task<PersonalProfileDto> Map((PersonalProfileEntity personalProfile, List<SkillEntity> skills,
        List<InterestEntity> interests) values, UserIdEntity userId)
    {

        PersonalProfileDto profile = new PersonalProfileDto()
        {
            Description = values.personalProfile.Description,
            Email = _protector.Unprotect(values.personalProfile.Email),//AQuí
            FirstName = values.personalProfile.FirstName,
            LastName = values.personalProfile.LastName,
            GitHub = values.personalProfile.GitHub,
            UserId = userId.UserId,
            UserName = userId.UserName,
            Phone = _protector.Unprotect(values.personalProfile.Phone),//AQuí
            Website = values.personalProfile.Website,
            Id = values.personalProfile.Id,
            Interests = values.interests.Select(a => new InterestDto()
            {
                Id = a.Id,
                Interest = a.Description
            }).ToList(),
            Skills = values.skills.Select(a => new SkillDto()
            {
                Id = a.Id,
                Name = a.Name,
                Punctuation = a.Punctuation
            }).ToList()
        };
        return Task.FromResult(profile);
    }

}

 

3.3 - Purpose en CreateProtector 

Como podemos observar, cuando creamos el protector con CreateProtector estamos pasando por parámetro una frase o bueno un string

 

Este string es el utilizado por el cypher para encriptar y desencriptar, y obviamente para realizar acciones sobre el mismo miembro debe ser el mismo. 

 

Además podemos tener una clave pública o un string único para la aplicación entera, o como en mi caso, todo lo que tiene que ver con el perfil personal tiene una, cuando cambie a otro servicio, utilizaré otra. 

 

El propósito de la clave no es ser privado al desarrollador (como podría ser una clave de la base de datos de producción).

 

Si utilizamos la opción de una única key para la aplicación entera, podemos añadir nuestro IDataProtectionProvider a las dependencias de cada servicio y únicamente devolver IDataProtector en las dependencias.

public interface IGetPersonalProfileDependencies
{
    IDataProtector Protector { get; }
    ...
}

public class GetPersonalProfileDependencies : IGetPersonalProfileDependencies
{
   public IDataProtector Protector { get; }

    public GetPersonalProfileDependencies(IDataProtectionProvider protectorProvider)
    {
        Protector = protectorProvider.CreateProtector("test")
    }
    ...
}
##and then just do the call
_dependencies.Protector.Protect(values.personalProfile.Phone);
_dependencies.Protector.Unprotect(values.personalProfile.Phone);

Pero esto es más cuestión de gustos, ambas opciones son completamente válidas. 

 

4 - implementar IDataProtectionProvider en test

Sabemos que debemos implementar test para todos nuestros servicios, lo que implica que debemos implementar la interfaz IDataProtectionProvider dentro de los test, pero como sabemos, no podemos implementar una interfaz. Entonces, cómo implementamos IDataProtectionProvider? (por así decirlo).

 

4.1 - Implementar IDataProtectionProvider en test unitarios

Para implementar IDataProtectionProvider en los test unitarios podriamos utilizar mock  pero la realidad es que no es una buena idea.

En este caso debemos utilizar la clase EphemeralDataProtectionProvider la cual nos la provee microsoft específicamente para este escenario, ya que es una clase la cual implementa IDataProtectionProvider.

IDataProtectionProvider protectionProvider = new EphemeralDataProtectionProvider();
//IDataProtector protector = protectionProvider.CreateProtector("Test_PutPersonalProfile");

Subject = new PutPersonalProfile(_dependencies.Object, protectionProvider);

 

4.2 - Implemenatr IDataProtectionProvider en test de integración

Para implentar IDataProtectionProvider  en los test de integración debemos realizar una acción similar a la anterior, pero esta vez únicamente debemos indicar en el contenedor de dependencias.

private IServiceCollection BuildDependencies()
{
    IServiceCollection services = new ServiceCollection();
    services
        .AddScoped<IDataProtectionProvider, EphemeralDataProtectionProvider>();
    return services;
}

 

 

Conclusión

 En este post hemos visto por qué debemos implementar encriptación para la información sensible o privada que almacenamos tanto el la base de datos o en ficheros de configuración. 

 

Definitivamente si vamos a tener una aplicación semi profesional debemos implementar este tipo de prácticas y ya no mencionar en la empresa.

 

Comparte