Trafegando dados sensíveis entre aplicações .NET com DataProtection API

Leonardo Guilen
6 min readAug 15, 2023

Fortalecendo a Segurança: Como a DataProtection API do .NET pode ajudar na proteção de dados sensíveis durante o tráfego entre aplicações.

Photo by rc.xyz NFT gallery on Unsplash

Nos dias atuais, onde os dados sensíveis fluem constantemente entre aplicações, a segurança da informação se tornou uma prioridade inegável. Empresas e desenvolvedores precisam garantir que as informações confidenciais, como senhas, tokens de autenticação e outros dados sensíveis, sejam transmitidas de maneira segura e protegida contra olhares curiosos e ameaças cibernéticas. Existem ótimas, e bem conhecidas, estratégias de segurança para esse tipo de segurança, como: TLS e SSL (HTTPS), autenticação e autorização, criptografia, entre outros.

Nesse post, demonstrarei como que a DataProtection API do .NET entra em cena como uma aliada valiosa na segurança no compartilhamento de dados entre aplicações.

O que é o DataProtection API?

A DataProtection API do .NET oferece um conjunto poderoso de ferramentas para proteger os dados durante sua transmissão entre aplicações. Ela utiliza técnicas de criptografia para garantir que apenas o emissor original e o receptor pretendido possam acessar e compreender os dados. Diferentemente da criptografia tradicional, a DataProtection API permite que os dados sejam criptografados em um local e descriptografados em outro, tornando-a ideal para cenários de troca de informações entre sistemas.

As principais vantagens da utilização do DataProtection são:

  1. Simplicidade de Implementação: A API é projetada para ser facilmente incorporada em projetos .NET, com uma curva de aprendizado suave para desenvolvedores familiarizados com o ecossistema.
  2. Proteção Abrangente: Além da criptografia, a API lida com aspectos como proteção contra replay attacks e manipulação de dados, garantindo uma camada abrangente de segurança.
  3. Integração Flexível: A API pode ser aplicada a diversos cenários, desde cookies em aplicações web até dados em serviços RESTful, proporcionando uma abordagem coesa para a segurança de dados em diferentes contextos.
  4. Chaveamento Automático: A DataProtection API trata automaticamente da geração, gerenciamento e rotação de chaves criptográficas, aliviando a carga de trabalho do desenvolvedor nessa área.

Como começar…

Para integrar a DataProtection API em seu projeto .NET é bem fácil. A API é parte do pacote NuGet oficial da Microsoft e pode ser instalada com poucos comandos. Depois de instalada, você pode começar a proteger os dados sensíveis com apenas algumas configurações e linhas de código.

Para iniciar, devemos seguir com a instalação dos 2 pacotes abaixo:

dotnet add package Microsoft.AspNetCore.DataProtection
dotnet add package Microsoft.AspNetCore.DataProtection.Abstractions

Caso seu projeto seja do tipo WebApi, esse passo de instalação pode ser ignorado, pois, esses pacotes já estarão inclusos implicitamente no projeto.

Com os pacotes devidamente instalados, temos que fazer a configuração do DataProtection no container de dependências da aplicação (ServiceCollection).

// Console Application
var serviceCollection = new ServiceCollection();
serviceCollection.AddDataProtection();

// Other Applications such WebApi, Worker, Blazor Server App etc.
builder.Services.AddDataProtection();

O método de extensão AddDataProtection é responsável por injetar as dependências para a utilização das funcionalidades do DataProtection, em destaque a interface IDataProtectionProvider. Existem configurações adicionais que são necessárias, mas vamos falar sobre elas mais para frente.

Com tudo isso configurado, podemos implementar um exemplo simples para ver na prática como tudo funciona.

using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.DependencyInjection;

// Instantiate our DI Container
var serviceCollection = new ServiceCollection();

// Inject DataProtection dependencies
serviceCollection.AddDataProtection();

// Build service provider
using var services = serviceCollection.BuildServiceProvider();

// Create the IDataProtector with purpose called "Demo"
var dataProtector = services.GetDataProtector(purpose: "Demo");

// Protect data
var protectedData = dataProtector.Protect("Confidential content");
Console.WriteLine("Protected data: {0}", protectedData);

// Unprotect data that has been protected above
var unprotectedData = dataProtector.Unprotect(protectedData);
Console.WriteLine("Unprotected data: {0}", unprotectedData);

/*
* SAMPLE OUTPUT
*
* Protected data: CfDJ8ICcgQwZZhlAlTZT...OdfH66i1PnGmpCR5e441xQ
* Unprotected data: Confidential content
*/

Repare que o conteúdo protegido tem um formato de “hash”, que por padrão, é o resultado da criptografia usando o algoritmo AES.
É possível customizar o algoritmo de criptografia usando o método de UseCustomCryptographicAlgorithms.

Usando de forma distribuída

Pense no seguinte cenário, temos um serviço A que consome informações do serviço B. Porém, essas informações são extremamente sigilosas e de forma alguma deveriam ser acessadas de outra maneira. Como o DataProtection pode nos ajudar com isso?

Simples, criando um vinculo exclusivo entre essas duas aplicações.

Para que isso funcione, temos que garantir 3 coisas importantes nesses serviços, que são:

  1. Compartilhamento das chaves: Garantir que ambas aplicações estão configuradas para acessar e ler as chaves do mesmo local.
  2. Nome exclusivo de aplicativo: Definir o nome exclusivo deste aplicativo dentro do sistema de proteção de dados. Onde tanto os emissores quanto os consumidores devem ser configurados com esse nome exclusivo para terem acesso a um determinado contexto.
  3. Parâmetro de finalidade exclusivo: Criar isolamento entre consumidores criptográficos, ou seja, nenhum outro componente pode ler suas cargas e não pode ler as cargas de nenhum outro componente.

Importante: Os parâmetros de finalidade (purpose and sub-purposes) são responsáveis por garantir a exclusividade e isolamento de quem pode consumir uma determinada informação protegida. Caso um consumidor não contenha estritamente uma finalidade do emissor, não importa se tiverem as mesmas chaves ou estiverem dentro do mesmo contexto, não será possível desproteger a informação e um erro esperado do tipo CryptographicException é lançado.

No diagrama abaixo, as instâncias A e B não podem ler as cargas umas das outras, apenas as próprias.

Fonte: https://learn.microsoft.com/pt-br/aspnet/core/security/data-protection/consumer-apis/purpose-strings/_static/purposes.png?view=aspnetcore-7.0

Para exemplificar esse processo temos uma API como emissor, e um console como consumidor.

Implementação da API:

using Microsoft.AspNetCore.DataProtection;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDataProtection()
.SetApplicationName("Demo")
.PersistKeysToFileSystem(new DirectoryInfo(@"C:\temp\keys"));

var app = builder.Build();

app.MapGet("/secret-data", (IDataProtectionProvider provider) =>
{
var dataProtector = provider.CreateProtector(purpose: "GetSecretData");
return dataProtector.Protect("{\"data\": \"secret data\"}");
});

await app
.RunAsync()
.ConfigureAwait(false);

No código acima definimos um servidor com um endpoint que simula o retorno de um conteúdo secreto. Porém, o mais importante são as configurações feitas no sistema do DataProtection.

builder.Services.AddDataProtection()
.SetApplicationName("Demo")
.PersistKeysToFileSystem(new DirectoryInfo(@"C:\temp\keys"));

Primeiro, configuramos um contexto de aplicação chamado “Demo” chamando o método SetApplicationName(“Demo”), em seguida, a instrução PersistKeysToFileSystem(new DirectoryInfo(@”C:\temp\keys”)) define o local onde as chaves serão armazenadas, nesse caso, em um diretório local do meu sistema de arquivos.

E por último, mas não menos importante, criamos uma instância do IDataProtector com a finalidade nomeada como “GetSecretData”, sendo assim, nosso consumidor deverá usar essa mesma finalidade para conseguir ler o conteúdo protegido.

var dataProtector = provider.CreateProtector(purpose: "GetSecretData");

Com tudo configurado, ao testar a API deveremos ter o seguinte resultado:

> curl -i "http://<host>/secret-data"
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Thu, 10 Aug 2023 22:24:26 GMT
Server: Kestrel
Transfer-Encoding: chunked

CfDJ8IQXXevAKHBOp7-k5vzwWW5GrLjJVgc6WHFWaINS3d_-zXpze1bptT4NSxu2tH1ozxs-XYrHxmU6d2lKRZM5nLLepMh6A28CRHBgA1nUj3sroemTtv26kfNv2sX5C1P_ayeINH2Qiq0p-GFq4Pw9oR4

Tudo certo! Seguiremos com a implementação do cliente que será o consumidor dessa API.

Implementação do Cliente (Console App)

using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.DependencyInjection;

var serviceCollection = new ServiceCollection();

serviceCollection.AddDataProtection()
.SetApplicationName("Demo")
.PersistKeysToFileSystem(new DirectoryInfo(@"C:\temp\keys"));
serviceCollection.AddHttpClient(
name: "Api",
configureClient: client => client.BaseAddress = new("<api-base-uri>"));

using var services = serviceCollection.BuildServiceProvider();

using var scope = services.CreateScope();

var clientFactory = scope.ServiceProvider.GetRequiredService<IHttpClientFactory>();
var client = clientFactory.CreateClient("Api");

var responseBody = await client.GetStringAsync("secret-data");
Console.WriteLine("Response body: {0}", responseBody);

var protector = scope.ServiceProvider.GetDataProtector("GetSecretData");

var unprotectData = protector.Unprotect(responseBody);
Console.WriteLine("Data: {0}", unprotectData);

/*
* SAMPLE OUTPUT
*
* Response body: CfDJ8ICcgQwZZhlAlTZT...OdfH66i1PnGmpCR5e441xQ
* Data: {"data": "secret data"}
*/

Note que configuramos o DataProtection do cliente igual ao da API, e a finalidade sendo a mesma criada pelo emissor (API). Com isso, é possível descriptografar o conteúdo protegido.

Caso mudasse a finalidade para qualquer outra coisa, teríamos um erro na tentativa de desproteger o responseBody.

var protector = scope.ServiceProvider.GetDataProtector("Anything");
var unprotectData = protector.Unprotect(responseBody);

// System.Security.Cryptography.CryptographicException: The payload was invalid.

Conclusão

A troca de dados sensíveis entre aplicações .NET exige uma abordagem de segurança sólida, e a DataProtection API é uma excelente ferramenta para esse trabalho. Garanta a confidencialidade e integridade dos dados, protegendo-os contra ameaças à medida que eles trafegam entre sistemas.

Além disso, existem estratégias para aumentar a confiabilidade no uso do DataProtection, como por exemplo, proteger as suas chaves com criptografia simétrica ou assimétrica, limitar o tempo que uma informação pode ser lida, e etc.

Referências

Visão geral da proteção de dados do ASP.NET Core | Microsoft Learn

--

--

Leonardo Guilen

Software Engineer focused on backend technologies, DevOps and Cloud. Researcher of the best coding standards, performance optimization and security enhancements