Escenarios no compatibles con DI para la protección de datos en ASP.NET Core

Por Rick Anderson

Normalmente, el sistema de protección de datos de ASP.NET Core se agrega a un contenedor de servicios y lo consumen los componentes dependientes mediante la inserción de dependencias (DI). Sin embargo, hay casos en los que esto no es factible o no se desea, especialmente cuando se importa el sistema a una aplicación existente.

Para admitir estos escenarios, el paquete Microsoft.AspNetCore.DataProtection.Extensions proporciona un tipo concreto, DataProtectionProvider, que ofrece una manera sencilla de usar la protección de datos sin depender de la inserción de dependencias. El tipo DataProtectionProvider implementa IDataProtectionProvider. La construcción de DataProtectionProvider solo requiere proporcionar una instancia DirectoryInfo para indicar dónde se deben almacenar las claves criptográficas del proveedor, como se muestra en el ejemplo de código siguiente:

using System;
using System.IO;
using Microsoft.AspNetCore.DataProtection;

public class Program
{
    public static void Main(string[] args)
    {
        // Get the path to %LOCALAPPDATA%\myapp-keys
        var destFolder = Path.Combine(
            System.Environment.GetEnvironmentVariable("LOCALAPPDATA"),
            "myapp-keys");

        // Instantiate the data protection system at this folder
        var dataProtectionProvider = DataProtectionProvider.Create(
            new DirectoryInfo(destFolder));

        var protector = dataProtectionProvider.CreateProtector("Program.No-DI");
        Console.Write("Enter input: ");
        var input = Console.ReadLine();

        // Protect the payload
        var protectedPayload = protector.Protect(input);
        Console.WriteLine($"Protect returned: {protectedPayload}");

        // Unprotect the payload
        var unprotectedPayload = protector.Unprotect(protectedPayload);
        Console.WriteLine($"Unprotect returned: {unprotectedPayload}");

        Console.WriteLine();
        Console.WriteLine("Press any key...");
        Console.ReadKey();
    }
}

/*
 * SAMPLE OUTPUT
 *
 * Enter input: Hello world!
 * Protect returned: CfDJ8FWbAn6...ch3hAPm1NJA
 * Unprotect returned: Hello world!
 *
 * Press any key...
*/

De forma predeterminada, el tipo concreto DataProtectionProvider no cifra el material de clave sin procesar antes de conservarlo en el sistema de archivos. Esto es para admitir escenarios en los que el desarrollador apunta a un recurso compartido de red y el sistema de protección de datos no puede deducir automáticamente un mecanismo de cifrado de clave en reposo adecuado.

Además, el tipo concreto DataProtectionProvider no aísla las aplicaciones de forma predeterminada. Todas las aplicaciones que usan el mismo directorio de claves pueden compartir cargas siempre que sus parámetros de propósito coincidan.

El constructor DataProtectionProvider acepta una devolución de llamada de configuración opcional que se puede usar para ajustar los comportamientos del sistema. En el ejemplo siguiente se muestra cómo restaurar el aislamiento con una llamada explícita a SetApplicationName. En el ejemplo también se muestra la configuración del sistema para cifrar automáticamente las claves persistentes mediante Windows DPAPI. Si el directorio apunta a un recurso compartido UNC, puede que desee distribuir un certificado compartido entre todas las máquinas pertinentes y configurar el sistema para usar el cifrado basado en certificados con una llamada a ProtectKeysWithCertificate.

using System;
using System.IO;
using Microsoft.AspNetCore.DataProtection;

public class Program
{
    public static void Main(string[] args)
    {
        // Get the path to %LOCALAPPDATA%\myapp-keys
        var destFolder = Path.Combine(
            System.Environment.GetEnvironmentVariable("LOCALAPPDATA"),
            "myapp-keys");

        // Instantiate the data protection system at this folder
        var dataProtectionProvider = DataProtectionProvider.Create(
            new DirectoryInfo(destFolder),
            configuration =>
            {
                configuration.SetApplicationName("my app name");
                configuration.ProtectKeysWithDpapi();
            });

        var protector = dataProtectionProvider.CreateProtector("Program.No-DI");
        Console.Write("Enter input: ");
        var input = Console.ReadLine();

        // Protect the payload
        var protectedPayload = protector.Protect(input);
        Console.WriteLine($"Protect returned: {protectedPayload}");

        // Unprotect the payload
        var unprotectedPayload = protector.Unprotect(protectedPayload);
        Console.WriteLine($"Unprotect returned: {unprotectedPayload}");

        Console.WriteLine();
        Console.WriteLine("Press any key...");
        Console.ReadKey();
    }
}

Sugerencia

Las instancias del tipo concreto DataProtectionProvider son costosas de crear. Si una aplicación mantiene varias instancias de este tipo y si todas usan el mismo directorio de almacenamiento de claves, el rendimiento de la aplicación podría degradarse. Si usa el tipo DataProtectionProvider, le recomendamos que cree este tipo una vez y reutilícelo tanto como sea posible. El tipo DataProtectionProvider y todas las instancias IDataProtector creadas a partir de él son seguras para subprocesos para varios autores de llamadas.