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#.
Índice
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.
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.
3.4 - Data protector cuando desplegas la aplicación
Si has estado siguiendo el post te habrás dado cuenta que una vez publicas la aplicación esta falla al proteger/desprotejer la información. esto es porque utilizamos una llaves en memoria, lo que tenemos que hacer es almacenarlas deltro del sistema de ficheros (tambien se puede en la nube.
Para ello, únicamente tenemos que llamar al método PersistKeysToFileSystem en la configuración inicial:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(@"PATH que contendrá las keys"));
....
}
Recuerda que es un directorio y que la aplicación necesitará permisos para acceder a dicho directorio.
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.