LGPD + EF CORE + ValueConverter
Mais 1 artigo??? Desculpa estou de férias!!!
Mas pera aí, você não vai falar nada de LGPD?... tá bom, LGPD é um acrônimo para (Lei Geral de Proteção de Dados Pessoais) que basicamente o Brasil adotou depois que alguns paises da Europa começaram exigir que o GDPR fosse implementado, para que os dados dos cidadões e sua privacidade pudesse estar segura.
Basicamente de forma muito resumida é isso... antes de criticas observe que falei "BASICAMENTE".
Você pode acessar os links abaixo para obter mais informações:
GDPR
LGPD
Cenário
Fulando: Rafael com todo respeito isso é fácil! Rafael: Tudo bem, só acredito que posso tornar ainda mais fácil.
Bom vamos começar a montar nosso sistema de cadastro de clientes, onde teremos uma classe Cliente com a seguinte estrutura.
public class Cliente
{
public int Id { get; set; }
public string Nome { get; set; }
public string Telefone { get; set; }
public string Endereco { get; set; }
public string CPF { get; set; }
}
public class DatabaseContext : DbContext
{
public DbSet<Cliente> Clientes { get; set; }
protected override void OnConfiguring(
DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer(@"Server=(localdb)\msSqlLocalDB;Integrated Security=True;Database=EFCoreValueConvertion;MultipleActiveResultSets=true;");
}
public class Program
{
static void Main(string[] args)
{
using var db = new DatabaseContext();
db.Database.EnsureCreated();
db.Clientes.Add(new Cliente
{
Nome = "Rafael Almeida",
Endereco = "Aqui mesmo",
Telefone = "7998829XXXX",
CPF = "123456"
});
db.SaveChanges();
var cliente = db
.Clientes
.AsNoTracking()
.FirstOrDefault(p => p.CPF == "123456");
}
}
Comandos gerados
Os comandos produzidos pelo EF Core foram esses:
Comando Inserir
exec sp_executesql N'SET NOCOUNT ON;
INSERT INTO [Clientes] ([CPF], [Endereco], [Nome], [Telefone])
VALUES (@p0, @p1, @p2, @p3);
SELECT [Id]
FROM [Clientes]
WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();
',N'@p0 nvarchar(4000),@p1 nvarchar(4000),@p2 nvarchar(4000),@p3 nvarchar(4000)',
@p0=N'123456',@p1=N'Aqui mesmo',@p2=N'Rafael Almeida',@p3=N'7998829XXXX'
Comando Consultar
SELECT TOP(1) [c].[Id], [c].[CPF], [c].[Endereco], [c].[Nome], [c].[Telefone]
FROM [Clientes] AS [c]
WHERE [c].[CPF] = N'123456'
Protegendo dados explícitamente
Até aqui tudo normal, nada de novo, então vamos voltar ao assunto de proteger os dados?!
Mas eu gostaria que Telefone e CPF, seja armazenado de forma criptografada, você poderia apenas criar uma função para criptografar os dados no momento que for persistir, e quando consultar descriptografar os dados.
Perfeito, então vejo você fazendo algo assim:
public class Program
{
static void Main(string[] args)
{
using var db = new DatabaseContext();
db.Database.EnsureCreated();
db.Clientes.Add(new Cliente
{
Nome = "Rafael Almeida",
Endereco = "Aqui mesmo",
Telefone = LockView("7998829XXXX"), //Criptografando
CPF = LockView("123456") // Criptografando
});
db.SaveChanges();
var cliente = db
.Clientes
.AsNoTracking()
.FirstOrDefault(p => p.CPF == LockView("123456"));
var cpf = UnLockView(cliente.CPF);
}
static string LockView(string texto)
{
using var hashProvider = new MD5CryptoServiceProvider();
var encriptar = new TripleDESCryptoServiceProvider
{
Mode = CipherMode.ECB,
Key = hashProvider.ComputeHash(_chave),
Padding = PaddingMode.PKCS7
};
using var transforme = encriptar.CreateEncryptor();
var dados = Encoding.UTF8.GetBytes(texto);
return Convert.ToBase64String(transforme.TransformFinalBlock(dados, 0, dados.Length));
}
}
Delegando responsabilidade
Funciona perfeitamente, não é a melhor maneira de fazer, então podemos melhorar isso e delegar a responsabilidade para o EF Core, vamos criar um atributo e extrair funcionalidades que o EF Core nos proporciona, nesse caso primeiramente vamos criar nosso atributo SensitiveData.
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
public sealed class SensitiveDataAttribute : Attribute
{
}
Agora vamos adicionar o atributo em todas propriedades que queremos que o EF Core fique responsável pelo trabalho pesado, de armazenar e ler os dados sensíveis.
public class Cliente
{
public int Id { get; set; }
public string Nome { get; set; }
[SensitiveData]
public string Telefone { get; set; }
public string Endereco { get; set; }
[SensitiveData]
public string CPF { get; set; }
}
public class DataProtectionConverter : ValueConverter<string, string>
{
private static byte[] _chave = Encoding.UTF8.GetBytes("#lgpd+ef");
public DataProtectionConverter()
: base(_convertTo, _convertFrom, default)
{
}
static Expression<Func<string, string>> _convertTo = x => LockView(x);
static Expression<Func<string, string>> _convertFrom = x => UnLockView(x);
static string LockView(string texto)
{
using var hashProvider = new MD5CryptoServiceProvider();
var encriptar = new TripleDESCryptoServiceProvider
{
Mode = CipherMode.ECB,
Key = hashProvider.ComputeHash(_chave),
Padding = PaddingMode.PKCS7
};
using var transforme = encriptar.CreateEncryptor();
var dados = Encoding.UTF8.GetBytes(texto);
return Convert.ToBase64String(transforme.TransformFinalBlock(dados, 0, dados.Length));
}
static string UnLockView(string texto)
{
using var hashProvider = new MD5CryptoServiceProvider();
var descriptografar = new TripleDESCryptoServiceProvider
{
Mode = CipherMode.ECB,
Key = hashProvider.ComputeHash(_chave),
Padding = PaddingMode.PKCS7
};
using var transforme = descriptografar.CreateDecryptor();
var dados = Convert.FromBase64String(texto.Replace(" ", "+"));
return Encoding.UTF8.GetString(transforme.TransformFinalBlock(dados, 0, dados.Length));
}
}
public class DatabaseContext : DbContext
{
public DbSet<Cliente> Clientes { get; set; }
protected override void OnConfiguring(
DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer(
@"Server=(localdb)\msSqlLocalDB;Integrated Security=True; Database=EFCoreValueConvertion; MultipleActiveResultSets=true;"
);
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Selecionar todas propriedades que tem a anotação SensitiveData
// e aplicar o conversor de valores para o que criamos.
foreach (var entity in modelBuilder.Model.GetEntityTypes())
{
foreach (var property in entity.GetProperties())
{
var attributes = property
.PropertyInfo
.GetCustomAttributes(typeof(SensitiveDataAttribute), false);
if (attributes.Length > 0)
{
property.SetValueConverter(new DataProtectionConverter());
}
}
}
}
}
Código final
Agora como você pode ver não iremos precisar mais ficar criptografando explicitamente as informações, nosso exemplo completo ficou assim:
public class Program
{
static void Main(string[] args)
{
using var db = new DatabaseContext();
db.Database.EnsureCreated();
db.Clientes.Add(new Cliente
{
Nome = "Rafael Almeida",
Endereco = "Aqui mesmo",
Telefone = "7998829XXXX",
CPF = "123456"
});
db.SaveChanges();
var cliente = db.Clientes.AsNoTracking().FirstOrDefault(p => p.CPF == "123456");
}
}
// SensitiveDataAttribute
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
public sealed class SensitiveDataAttribute : Attribute
{
}
// Cliente
public class Cliente
{
public int Id { get; set; }
public string Nome { get; set; }
[SensitiveData] // SensitiveData
public string Telefone { get; set; }
public string Endereco { get; set; }
[SensitiveData] // SensitiveData
public string CPF { get; set; }
}
// DatabaseContext
public class DatabaseContext : DbContext
{
public DbSet<Cliente> Clientes { get; set; }
protected override void OnConfiguring(
DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer(
@"Server=(localdb)\msSqlLocalDB;Integrated Security=True; Database=EFCoreValueConvertion; MultipleActiveResultSets=true;"
);
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var entity in modelBuilder.Model.GetEntityTypes())
{
foreach (var property in entity.GetProperties())
{
var attributes = property
.PropertyInfo
.GetCustomAttributes(typeof(SensitiveDataAttribute), false);
if (attributes.Length > 0)
{
property.SetValueConverter(new DataProtectionConverter());
}
}
}
}
}
// DataProtectionConverter
public class DataProtectionConverter : ValueConverter<string, string>
{
private static byte[] _chave = Encoding.UTF8.GetBytes("#lgpd+ef");
public DataProtectionConverter()
: base(_convertTo, _convertFrom, default)
{
}
private static Expression<Func<string, string>> _convertTo = x => LockView(x);
private static Expression<Func<string, string>> _convertFrom = x => UnLockView(x);
static string LockView(string texto)
{
using var hashProvider = new MD5CryptoServiceProvider();
var encriptar = new TripleDESCryptoServiceProvider
{
Mode = CipherMode.ECB,
Key = hashProvider.ComputeHash(_chave),
Padding = PaddingMode.PKCS7
};
using var transforme = encriptar.CreateEncryptor();
var dados = Encoding.UTF8.GetBytes(texto);
return Convert.ToBase64String(transforme.TransformFinalBlock(dados, 0, dados.Length));
}
static string UnLockView(string texto)
{
using var hashProvider = new MD5CryptoServiceProvider();
var descriptografar = new TripleDESCryptoServiceProvider
{
Mode = CipherMode.ECB,
Key = hashProvider.ComputeHash(_chave),
Padding = PaddingMode.PKCS7
};
using var transforme = descriptografar.CreateDecryptor();
var dados = Convert.FromBase64String(texto.Replace(" ", "+"));
return Encoding.UTF8.GetString(transforme.TransformFinalBlock(dados, 0, dados.Length));
}
}
Output SQL
Os comandos produzidos ficaram assim:
Comando Inserir
exec sp_executesql N'SET NOCOUNT ON;
INSERT INTO [Clientes] ([CPF], [Endereco], [Nome], [Telefone])
VALUES (@p0, @p1, @p2, @p3);
SELECT [Id]
FROM [Clientes]
WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();
',N'@p0 nvarchar(4000),@p1 nvarchar(4000),@p2 nvarchar(4000),@p3 nvarchar(4000)',
@p0=N'kOI/e7VQZhs=', -- Criptografado
@p1=N'Aqui mesmo',@p2=N'Rafael Almeida',
@p3=N'T2wQKyR8w28fKOgBXp0ytg==' -- Criptografado
Comando Consultar
SELECT TOP(1) [c].[Id], [c].[CPF], [c].[Endereco], [c].[Nome], [c].[Telefone]
FROM [Clientes] AS [c]
WHERE [c].[CPF] = N'kOI/e7VQZhs='
Deixe um comentário