Configurar la protección de datos en ASP.NET Core

Cuando se inicializa el sistema de protección de datos, este aplica la configuración predeterminada en función del entorno operativo. Esta configuración es adecuada para las aplicaciones que se ejecutan en una sola máquina. Sin embargo, hay casos en los que al desarrollador le sea más conveniente cambiar la configuración predeterminada:

  • La aplicación se distribuye entre varias máquinas.
  • Por motivos de cumplimiento.

En estos escenarios, el sistema de protección de datos ofrece una API de configuración enriquecida.

Advertencia

De forma similar a los archivos de configuración, el anillo de claves de protección de datos debe protegerse con los permisos adecuados. Se puede optar por cifrar las claves en reposo, pero eso no impedirá que los atacantes creen nuevas claves. Por lo tanto, la seguridad de la aplicación se ve afectada. La ubicación de almacenamiento configurada con la protección de datos debe tener el acceso limitado a la propia aplicación, de forma similar a como se protegerían los archivos de configuración. Por ejemplo, si el anillo de claves se almacena en el disco, deben usarse los permisos del sistema de archivos. Hay que asegurarse de que solo la identidad con la que se ejecuta la aplicación web tenga acceso de lectura, escritura y creación a ese directorio. Si se usa Azure Blob Storage, solo la aplicación web debería poder leer, escribir o crear nuevas entradas en el almacén de blobs, etc.

El método de extensión AddDataProtection devuelve un valor IDataProtectionBuilder. IDataProtectionBuilder expone métodos de extensión que se pueden encadenar para configurar las opciones de protección de datos.

Los siguientes paquetes NuGet son necesarios para las extensiones de protección de datos que se usan en este artículo:

ProtectKeysWithAzureKeyVault

Inicie sesión en Azure con la CLI, por ejemplo:

az login

Para administrar claves con Azure Key Vault, hay que configurar el sistema con ProtectKeysWithAzureKeyVault en Program.cs. blobUriWithSasToken es el URI completo donde debería almacenarse el archivo de claves. El URI debe contener el token de SAS como parámetro de cadena de consulta:

builder.Services.AddDataProtection()
    .PersistKeysToAzureBlobStorage(new Uri("<blobUriWithSasToken>"))
    .ProtectKeysWithAzureKeyVault(new Uri("<keyIdentifier>"), new DefaultAzureCredential());

Para que una aplicación pueda comunicarse y autorizarse a sí misma con KeyVault, debe agregarse el paquete AzureIdentity.

Establezca la ubicación de almacenamiento del anillo de claves (por ejemplo, PersistKeysToAzureBlobStorage). Hay que establecer la ubicación porque la llamada a ProtectKeysWithAzureKeyVault implementa un IXmlEncryptor que deshabilita la configuración automática de protección de datos, incluida la ubicación de almacenamiento del anillo de claves. En el ejemplo anterior se usa Azure Blob Storage para garantizar la persistencia del anillo de claves. Para obtener más información, consulte Proveedores de almacenamiento de claves: Azure Storage. También se puede facilitar la persistencia del anillo de claves localmente con PersistKeysToFileSystem.

keyIdentifier es el identificador de clave del almacén de claves que se usa para el cifrado de claves. Por ejemplo, una clave creada en el almacén de claves denominado dataprotection en contosokeyvault tiene el identificador de clave https://contosokeyvault.vault.azure.net/keys/dataprotection/. Proporcione a la aplicación los permisos Get, Unwrap Key y Wrap Key en el almacén de claves.

Sobrecargas de ProtectKeysWithAzureKeyVault:

Si la aplicación usa paquetes de Azure anteriores (Microsoft.AspNetCore.DataProtection.AzureStorage y Microsoft.AspNetCore.DataProtection.AzureKeyVault), se recomienda quitar estas referencias y actualizarlas a Azure.Extensions.AspNetCore.DataProtection.Blobs y Azure.Extensions.AspNetCore.DataProtection.Keys. Estos paquetes suministran las nuevas actualizaciones y abordan algunos problemas clave de seguridad y estabilidad de los paquetes anteriores.

builder.Services.AddDataProtection()
    // This blob must already exist before the application is run
    .PersistKeysToAzureBlobStorage("<storageAccountConnectionString", "<containerName>", "<blobName>")
    // Removing this line below for an initial run will ensure the file is created correctly
    .ProtectKeysWithAzureKeyVault(new Uri("<keyIdentifier>"), new DefaultAzureCredential());

PersistKeysToFileSystem

Para almacenar claves en un recurso compartido UNC en lugar de en la ubicación predeterminada, %LOCALAPPDATA%, configure el sistema con PersistKeysToFileSystem:

builder.Services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\directory\"));

Advertencia

Si se cambia la ubicación de persistencia de claves, el sistema ya no cifra automáticamente las claves en reposo, ya que no sabe si DPAPI es un mecanismo de cifrado adecuado.

PersistKeysToDbContext

Para almacenar claves en una base de datos mediante EntityFramework, debe configurarse el sistema con el paquete Microsoft.AspNetCore.DataProtection.EntityFrameworkCore:

builder.Services.AddDataProtection()
    .PersistKeysToDbContext<SampleDbContext>();

El código anterior almacena las claves en la base de datos configurada. El contexto de base de datos que se use debe implementar IDataProtectionKeyContext. IDataProtectionKeyContext expone la propiedad DataProtectionKeys

public DbSet<DataProtectionKey> DataProtectionKeys { get; set; } = null!;

Esta propiedad representa la tabla en la que se almacenan las claves. Cree la tabla manualmente o con migraciones de DbContext. Para obtener más información, vea DataProtectionKey.

ProtectKeysWith*

Se puede configurar el sistema para proteger las claves en reposo mediante una llamada a cualquiera de las API de configuración ProtectKeysWith*. En el ejemplo siguiente, las claves se almacenan en un recurso compartido UNC y se cifran en reposo con un certificado X.509 específico:

builder.Services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\directory\"))
    .ProtectKeysWithCertificate(builder.Configuration["CertificateThumbprint"]);

Se puede proporcionar un X509Certificate2 para ProtectKeysWithCertificate, como un certificado cargado desde un archivo:

builder.Services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\directory\"))
    .ProtectKeysWithCertificate(
        new X509Certificate2("certificate.pfx", builder.Configuration["CertificatePassword"]));

Consulte Cifrado de claves en reposo para ver más ejemplos y un análisis de los mecanismos de cifrado de claves integrados.

UnprotectKeysWithAnyCertificate

Se pueden actualizar los certificados y descifrar claves en reposo mediante una matriz de certificados X509Certificate2 con UnprotectKeysWithAnyCertificate:

builder.Services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\directory\"))
    .ProtectKeysWithCertificate(
        new X509Certificate2("certificate.pfx", builder.Configuration["CertificatePassword"]))
    .UnprotectKeysWithAnyCertificate(
        new X509Certificate2("certificate_1.pfx", builder.Configuration["CertificatePassword_1"]),
        new X509Certificate2("certificate_2.pfx", builder.Configuration["CertificatePassword_2"]));

SetDefaultKeyLifetime

Para configurar el sistema para que la vigencia de las claves sea de 14 días en lugar de los 90 días predeterminados, se puede usar SetDefaultKeyLifetime:

builder.Services.AddDataProtection()
    .SetDefaultKeyLifetime(TimeSpan.FromDays(14));

SetApplicationName

De forma predeterminada, el sistema de protección de datos aísla las aplicaciones entre sí en función de sus rutas de acceso raíz de contenido, incluso aunque compartan el mismo repositorio de claves físicas. Este aislamiento impide que las aplicaciones reconozcan las cargas protegidas de las demás.

Para compartir cargas protegidas entre aplicaciones:

builder.Services.AddDataProtection()
    .SetApplicationName("<sharedApplicationName>");

SetApplicationName establece DataProtectionOptions.ApplicationDiscriminator internamente. Con fines de solución de problemas, el valor asignado por el marco al discriminador puede registrarse con el código siguiente colocado después compilar WebApplication en Program.cs:

var discriminator = app.Services.GetRequiredService<IOptions<DataProtectionOptions>>()
    .Value.ApplicationDiscriminator;
app.Logger.LogInformation("ApplicationDiscriminator: {ApplicationDiscriminator}", discriminator);

Para obtener más información sobre cómo se usa el discriminador, consulte las secciones siguientes de este artículo:

Advertencia

En .NET 6, WebApplicationBuilder normaliza la ruta de acceso raíz del contenido para finalizar con un campo DirectorySeparatorChar. Por ejemplo, en Windows, la ruta de acceso raíz del contenido termina en \, mientras que en Linux termina en /. Otros hosts no normalizan la ruta de acceso. La mayoría de las aplicaciones que se migran desde HostBuilder o WebHostBuilder no tendrán el mismo nombre de aplicación porque no tendrán el campo DirectorySeparatorChar final. Para solucionar este problema, quite el carácter separador de directorios y establezca el nombre de la aplicación manualmente, como se muestra en el código siguiente:

using Microsoft.AspNetCore.DataProtection;
using System.Reflection;

var builder = WebApplication.CreateBuilder(args);
var trimmedContentRootPath = builder.Environment.ContentRootPath.TrimEnd(Path.DirectorySeparatorChar);
builder.Services.AddDataProtection()
 .SetApplicationName(trimmedContentRootPath);
var app = builder.Build();

app.MapGet("/", () => Assembly.GetEntryAssembly()!.GetName().Name);

app.Run();

DisableAutomaticKeyGeneration

Puede darse el escenario de que no interese que una aplicación implemente claves automáticamente (cree nuevas claves) a medida que se acerque la fecha de expiración. Por ejemplo, un escenario en el que las aplicaciones se configuren en una relación primaria o secundaria, donde únicamente la aplicación principal es responsable de la administración de las claves, mientras que las aplicaciones secundarias solo tienen una vista de solo lectura del anillo de claves. Para configurar las aplicaciones secundarias de manera que traten el anillo de claves como de solo lectura, configure el sistema con DisableAutomaticKeyGeneration:

builder.Services.AddDataProtection()
    .DisableAutomaticKeyGeneration();

Aislamiento por aplicación

Cuando un host de ASP.NET Core proporciona el sistema de protección de datos, aísla automáticamente las aplicaciones entre sí, incluso aunque las aplicaciones se ejecuten en la misma cuenta de proceso de trabajo y usen el mismo material de creación de claves maestras. Esto es similar al modificador IsolateApps del elemento <machineKey> de System.Web.

El mecanismo de aislamiento funciona teniendo en cuenta cada aplicación del equipo local como un inquilino único, por lo que la raíz de IDataProtector de cualquier aplicación determinada incluye automáticamente el id. de aplicación como discriminador (ApplicationDiscriminator). El id. único de la aplicación es la ruta de acceso física de la aplicación:

  • En el caso de las aplicaciones hospedadas en IIS, el id. único es la ruta de acceso física de IIS de la aplicación. Si una aplicación se implementa en un entorno de granja de servidores web, este valor es estable suponiendo que los entornos de IIS se configuren de forma similar en todas las máquinas de la granja de servidores web.
  • En el caso de las aplicaciones autohospedadas que se ejecutan en el servidorKestrel, el id. único es la ruta de acceso física a la aplicación en el disco.

El identificador único está diseñado para sobrevivir a los restablecimientos, tanto de la aplicación individual como de la propia máquina.

Este mecanismo de aislamiento supone que las aplicaciones no son malintencionadas. Una aplicación malintencionada siempre puede afectar a cualquier otra aplicación que se ejecute en la misma cuenta de proceso de trabajo. En un entorno de hospedaje compartido en el que las aplicaciones no son de confianza mutua, el proveedor de hospedaje debe tomar medidas para garantizar el aislamiento de nivel de sistema operativo entre las aplicaciones, incluida la separación de los repositorios de claves subyacentes de las aplicaciones.

Si un el sistema de protección de datos no lo proporciona un host de ASP.NET Core (por ejemplo, si se crea una instancia a través del tipo concreto DataProtectionProvider), el aislamiento de la aplicación está deshabilitado de forma predeterminada. Cuando el aislamiento de la aplicación está deshabilitado, todas las aplicaciones respaldadas por el mismo material de creación de claves pueden compartir cargas si proporcionan los fines adecuados. Para proporcionar el aislamiento de aplicaciones en este entorno, llame al método SetApplicationName en el objeto de configuración y proporcione un nombre único para cada aplicación.

Protección de datos y aislamiento de aplicaciones

Hay que tener en cuenta los siguientes puntos para el aislamiento de aplicaciones:

  • Cuando varias aplicaciones apuntan al mismo repositorio de claves, la intención es que las aplicaciones compartan el mismo material de claves maestras. La protección de datos se desarrolla bajo la suposición de que todas las aplicaciones que comparten un anillo de claves pueden acceder a todos los elementos de ese anillo de claves. El identificador único de la aplicación se usa para aislar las claves específicas de la aplicación derivadas de las claves proporcionadas por el anillo de claves. No espera que se usen permisos de nivel de elemento, como los proporcionados por Azure KeyVault para aplicar aislamiento adicional. El intento de permisos de nivel de elemento genera errores de aplicación. Si no quiere depender del aislamiento de aplicaciones integrado, debe usar ubicaciones de almacenamiento de claves independientes y no compartidas entre aplicaciones.

  • El discriminador de aplicaciones (ApplicationDiscriminator) se usa para permitir que varias aplicaciones compartan el mismo material de claves maestras, pero manteniendo sus cargas criptográficas distintas entre sí. Para que las aplicaciones puedan leer las cargas criptográficas entre sí, deben tener el mismo discriminador de aplicaciones, que se puede establecer mediante una llamada a SetApplicationName.

  • Si una aplicación está en riesgo (por ejemplo, por un ataque de RCE), todo el material de claves maestras al que tenga acceso la aplicación deberá considerarse también en riesgo, independientemente de su estado de protección en reposo. Esto implica que si dos aplicaciones apuntan al mismo repositorio, aunque usen discriminadores de aplicaciones diferentes, poner en riesgo una de ellas es funcionalmente equivalente a poner en riesgo las dos.

    La parte "funcionalmente equivalente a poner en riesgo las dos" es válida incluso aunque las dos aplicaciones usen mecanismos diferentes para la protección de claves en reposo. Normalmente, esta configuración no es la esperada. El mecanismo de protección en reposo tiene la finalidad de proporcionar protección en caso de que un adversario obtenga acceso de lectura al repositorio. Un adversario que obtenga acceso de escritura al repositorio (quizás porque obtuvo el permiso de ejecución de código en una aplicación) puede insertar claves malintencionadas en el almacenamiento. El sistema de protección de datos no proporciona deliberadamente protección contra los adversarios que obtienen acceso de escritura al repositorio de claves.

  • Si las aplicaciones tienen que permanecer realmente aisladas entre sí, deben usar repositorios de claves diferentes. Lógicamente, esto no se ajusta a la definición de "aislado". Las aplicaciones no están aisladas si todas ellas tienen acceso de lectura y escritura a los almacenes de datos de las demás.

Cambio de algoritmos con UseCryptographicAlgorithms

La pila de protección de datos permite cambiar el algoritmo predeterminado usado por las claves recién generadas. La manera más sencilla de hacerlo es llamar a UseCryptographicAlgorithms desde la devolución de llamada de configuración:

builder.Services.AddDataProtection()
    .UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration
    {
        EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
        ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
    });

El valor predeterminado de EncryptionAlgorithm es AES-256-CBC y el valor predeterminado de ValidationAlgorithm es HMACSHA256. La directiva predeterminada puede establecerla un administrador del sistema a través de una directiva a nivel de equipo, pero una llamada explícita a UseCryptographicAlgorithms invalida la directiva predeterminada.

La llamada a UseCryptographicAlgorithms permite especificar el algoritmo deseado de una lista integrada predefinida. No hay que preocuparse por los detalles de implementación del algoritmo. En el escenario anterior, el sistema de protección de datos intenta usar la implementación CNG de AES si se ejecuta en Windows. De lo contrario, recurre a la clase administrada System.Security.Cryptography.Aes.

Se puede especificar manualmente una implementación mediante una llamada a UseCustomCryptographicAlgorithms.

Sugerencia

El cambio de algoritmos no afecta a las claves existentes del anillo de claves. Solo afecta a las claves recién generadas.

Especificación de algoritmos administrados personalizados

Para especificar algoritmos administrados personalizados, cree una instancia de ManagedAuthenticatedEncryptorConfiguration que apunte a los tipos de implementación:

builder.Services.AddDataProtection()
    .UseCustomCryptographicAlgorithms(new ManagedAuthenticatedEncryptorConfiguration
    {
        // A type that subclasses SymmetricAlgorithm
        EncryptionAlgorithmType = typeof(Aes),

        // Specified in bits
        EncryptionAlgorithmKeySize = 256,

        // A type that subclasses KeyedHashAlgorithm
        ValidationAlgorithmType = typeof(HMACSHA256)
    });

Generalmente las propiedades *Type deben apuntar a implementaciones concretas e instanciables (a través de un ctor público sin parámetros) de SymmetricAlgorithm y KeyedHashAlgorithm, aunque el sistema aplica casos especiales a algunos valores, como typeof(Aes), por comodidad.

Nota:

SymmetricAlgorithm debe tener una longitud de clave de ≥ 128 bits y un tamaño de bloque de ≥ 64 bits, y debe admitir el cifrado en modo CBC con relleno PKCS #7. KeyedHashAlgorithm debe tener un tamaño de hash de >= 128 bits y debe admitir claves de longitud igual a la longitud de hash del algoritmo hash. No es estrictamente necesario que el KeyedHashAlgorithm sea HMAC.

Especificación de algoritmos CNG de Windows personalizados

Para especificar un algoritmo CNG de Windows personalizado mediante el cifrado en modo CBC con validación HMAC, cree una instancia de CngCbcAuthenticatedEncryptorConfiguration que contenga la siguiente información algorítmica:

builder.Services.AddDataProtection()
    .UseCustomCryptographicAlgorithms(new CngCbcAuthenticatedEncryptorConfiguration
    {
        // Passed to BCryptOpenAlgorithmProvider
        EncryptionAlgorithm = "AES",
        EncryptionAlgorithmProvider = null,

        // Specified in bits
        EncryptionAlgorithmKeySize = 256,

        // Passed to BCryptOpenAlgorithmProvider
        HashAlgorithm = "SHA256",
        HashAlgorithmProvider = null
    });

Nota:

El algoritmo de cifrado de bloques simétrico debe tener una longitud de clave de >= 128 bits y un tamaño de bloque de >= 64 bits, y debe admitir el cifrado en modo CBC con relleno PKCS #7. El algoritmo hash debe tener un tamaño de hash de >= 128 bits y debe admitir que se abra con la marca BCRYPT_ALG_HANDLE_HMAC_FLAG. Las propiedades *Provider se pueden establecer en null para usar el proveedor predeterminado para el algoritmo especificado. Para obtener más información, consulte la documentación de BCryptOpenAlgorithmProvider.

Para especificar un algoritmo CNG de Windows personalizado mediante el cifrado en modo Galois/Counter con validación, cree una instancia de CngGcmAuthenticatedEncryptorConfiguration que contenga la siguiente información algorítmica:

builder.Services.AddDataProtection()
    .UseCustomCryptographicAlgorithms(new CngGcmAuthenticatedEncryptorConfiguration
    {
        // Passed to BCryptOpenAlgorithmProvider
        EncryptionAlgorithm = "AES",
        EncryptionAlgorithmProvider = null,

        // Specified in bits
        EncryptionAlgorithmKeySize = 256
    });

Nota:

El algoritmo de cifrado de bloques simétrico debe tener una longitud de clave de >= 128 bits y un tamaño de bloque de exactamente 128 bits, y debe admitir el cifrado GCM. Se puede establecer la propiedad EncryptionAlgorithmProvider en null para usar el proveedor predeterminado para el algoritmo especificado. Para obtener más información, consulte la documentación de BCryptOpenAlgorithmProvider.

Especificación de otros algoritmos personalizados

Aunque no se expone como una API de primera clase, el sistema de protección de datos es lo suficientemente extensible como para permitir la especificación de casi cualquier tipo de algoritmo. Por ejemplo, es posible contener todas las claves en un módulo de seguridad de hardware (HSM) y proporcionar una implementación personalizada de las rutinas de cifrado y descifrado principales. Para obtener más información, consulte IAuthenticatedEncryptor en Extensibilidad de criptografía básica.

Persistencias de claves al hospedar en un contenedor Docker

Al hospedar en un contenedor Docker, las claves deben conservarse en una de las siguientes opciones:

  • Una carpeta que sea un volumen de Docker y que persista más allá de la duración del contenedor, como un volumen compartido o un volumen montado en host.
  • Un proveedor externo, como Azure Blob Storage (se muestra en la sección ProtectKeysWithAzureKeyVault) o Redis.

Claves persistentes con Redis

Para almacenar claves, solo deben usarse las versiones de Redis compatibles con la persistencia de datos en Redis. Azure Blob Storage es persistente y se puede usar para almacenar claves. Para más información, consulte este problema de GitHub.

Registro de DataProtection

Habilite el registro de nivel de Information de DataProtection para ayudar a diagnosticar problemas. El siguiente archivo appsettings.json habilita el registro de información de la API DataProtection:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.AspNetCore.DataProtection": "Information"
    }
  },
  "AllowedHosts": "*"
}

Para obtener más información sobre el registro, vea Registro en .NET Core y ASP.NET Core.

Recursos adicionales

Cuando se inicializa el sistema de protección de datos, este aplica la configuración predeterminada en función del entorno operativo. Esta configuración es adecuada para las aplicaciones que se ejecutan en una sola máquina. Sin embargo, hay casos en los que al desarrollador le sea más conveniente cambiar la configuración predeterminada:

  • La aplicación se distribuye entre varias máquinas.
  • Por motivos de cumplimiento.

En estos escenarios, el sistema de protección de datos ofrece una API de configuración enriquecida.

Advertencia

De forma similar a los archivos de configuración, el anillo de claves de protección de datos debe protegerse con los permisos adecuados. Se puede optar por cifrar las claves en reposo, pero eso no impedirá que los atacantes creen nuevas claves. Por lo tanto, la seguridad de la aplicación se ve afectada. La ubicación de almacenamiento configurada con la protección de datos debe tener el acceso limitado a la propia aplicación, de forma similar a como se protegerían los archivos de configuración. Por ejemplo, si el anillo de claves se almacena en el disco, deben usarse los permisos del sistema de archivos. Hay que asegurarse de que solo la identidad con la que se ejecuta la aplicación web tenga acceso de lectura, escritura y creación a ese directorio. Si se usa Azure Blob Storage, solo la aplicación web debería poder leer, escribir o crear nuevas entradas en el almacén de blobs, etc.

El método de extensión AddDataProtection devuelve un valor IDataProtectionBuilder. IDataProtectionBuilder expone métodos de extensión que se pueden encadenar para configurar las opciones de protección de datos.

Los siguientes paquetes NuGet son necesarios para las extensiones de protección de datos que se usan en este artículo:

ProtectKeysWithAzureKeyVault

Inicie sesión en Azure con la CLI, por ejemplo:

az login

Para almacenar claves en Azure Key Vault, configure el sistema con ProtectKeysWithAzureKeyVault en la clase Startup. blobUriWithSasToken es el URI completo donde debería almacenarse el archivo de claves. El URI debe contener el token de SAS como parámetro de cadena de consulta:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .PersistKeysToAzureBlobStorage(new Uri("<blobUriWithSasToken>"))
        .ProtectKeysWithAzureKeyVault(new Uri("<keyIdentifier>"), new DefaultAzureCredential());
}

Para que una aplicación pueda comunicarse y autorizarse a sí misma con KeyVault, debe agregarse el paquete AzureIdentity.

Establezca la ubicación de almacenamiento del anillo de claves (por ejemplo, PersistKeysToAzureBlobStorage). Hay que establecer la ubicación porque la llamada a ProtectKeysWithAzureKeyVault implementa un IXmlEncryptor que deshabilita la configuración automática de protección de datos, incluida la ubicación de almacenamiento del anillo de claves. En el ejemplo anterior se usa Azure Blob Storage para garantizar la persistencia del anillo de claves. Para obtener más información, consulte Proveedores de almacenamiento de claves: Azure Storage. También se puede facilitar la persistencia del anillo de claves localmente con PersistKeysToFileSystem.

keyIdentifier es el identificador de clave del almacén de claves que se usa para el cifrado de claves. Por ejemplo, una clave creada en el almacén de claves denominado dataprotection en contosokeyvault tiene el identificador de clave https://contosokeyvault.vault.azure.net/keys/dataprotection/. Proporcione a la aplicación los permisos Get, Unwrap Key y Wrap Key en el almacén de claves.

Sobrecargas de ProtectKeysWithAzureKeyVault:

Si la aplicación usa paquetes de Azure anteriores (Microsoft.AspNetCore.DataProtection.AzureStorage y Microsoft.AspNetCore.DataProtection.AzureKeyVault), se recomienda quitar estas referencias y actualizarlas a Azure.Extensions.AspNetCore.DataProtection.Blobs y Azure.Extensions.AspNetCore.DataProtection.Keys. Estos paquetes suministran las nuevas actualizaciones y abordan algunos problemas clave de seguridad y estabilidad de los paquetes anteriores.

services.AddDataProtection()
    //This blob must already exist before the application is run
    .PersistKeysToAzureBlobStorage("<storage account connection string", "<key store container name>", "<key store blob name>")
    //Removing this line below for an initial run will ensure the file is created correctly
    .ProtectKeysWithAzureKeyVault(new Uri("<keyIdentifier>"), new DefaultAzureCredential());

PersistKeysToFileSystem

Para almacenar claves en un recurso compartido UNC en lugar de en la ubicación predeterminada, %LOCALAPPDATA%, configure el sistema con PersistKeysToFileSystem:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\directory\"));
}

Advertencia

Si se cambia la ubicación de persistencia de claves, el sistema ya no cifra automáticamente las claves en reposo, ya que no sabe si DPAPI es un mecanismo de cifrado adecuado.

PersistKeysToDbContext

Para almacenar claves en una base de datos mediante EntityFramework, debe configurarse el sistema con el paquete Microsoft.AspNetCore.DataProtection.EntityFrameworkCore:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .PersistKeysToDbContext<DbContext>()
}

El código anterior almacena las claves en la base de datos configurada. El contexto de base de datos que se use debe implementar IDataProtectionKeyContext. IDataProtectionKeyContext expone la propiedad DataProtectionKeys

public DbSet<DataProtectionKey> DataProtectionKeys { get; set; }

Esta propiedad representa la tabla en la que se almacenan las claves. Cree la tabla manualmente o con migraciones de DbContext. Para obtener más información, vea DataProtectionKey.

ProtectKeysWith*

Se puede configurar el sistema para proteger las claves en reposo mediante una llamada a cualquiera de las API de configuración ProtectKeysWith*. En el ejemplo siguiente, las claves se almacenan en un recurso compartido UNC y se cifran en reposo con un certificado X.509 específico:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\directory\"))
        .ProtectKeysWithCertificate(Configuration["Thumbprint"]);
}

Se puede proporcionar un X509Certificate2 para ProtectKeysWithCertificate, como un certificado cargado desde un archivo:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\directory\"))
        .ProtectKeysWithCertificate(
            new X509Certificate2("certificate.pfx", Configuration["Thumbprint"]));
}

Consulte Cifrado de claves en reposo para ver más ejemplos y un análisis de los mecanismos de cifrado de claves integrados.

UnprotectKeysWithAnyCertificate

Se pueden actualizar los certificados y descifrar claves en reposo mediante una matriz de certificados X509Certificate2 con UnprotectKeysWithAnyCertificate:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\directory\"))
        .ProtectKeysWithCertificate(
            new X509Certificate2("certificate.pfx", Configuration["MyPasswordKey"));
        .UnprotectKeysWithAnyCertificate(
            new X509Certificate2("certificate_old_1.pfx", Configuration["MyPasswordKey_1"]),
            new X509Certificate2("certificate_old_2.pfx", Configuration["MyPasswordKey_2"]));
}

SetDefaultKeyLifetime

Para configurar el sistema para que la vigencia de las claves sea de 14 días en lugar de los 90 días predeterminados, se puede usar SetDefaultKeyLifetime:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .SetDefaultKeyLifetime(TimeSpan.FromDays(14));
}

SetApplicationName

De forma predeterminada, el sistema de protección de datos aísla las aplicaciones entre sí en función de sus rutas de acceso raíz de contenido, incluso aunque compartan el mismo repositorio de claves físicas. Este aislamiento impide que las aplicaciones reconozcan las cargas protegidas de las demás.

Para compartir cargas protegidas entre aplicaciones:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .SetApplicationName("shared app name");
}

SetApplicationName establece DataProtectionOptions.ApplicationDiscriminator internamente. Para obtener más información sobre cómo se usa el discriminador, consulte las secciones siguientes de este artículo:

DisableAutomaticKeyGeneration

Puede darse el escenario de que no interese que una aplicación implemente claves automáticamente (cree nuevas claves) a medida que se acerque la fecha de expiración. Por ejemplo, un escenario en el que las aplicaciones se configuren en una relación primaria o secundaria, donde únicamente la aplicación principal es responsable de la administración de las claves, mientras que las aplicaciones secundarias solo tienen una vista de solo lectura del anillo de claves. Para configurar las aplicaciones secundarias de manera que traten el anillo de claves como de solo lectura, configure el sistema con DisableAutomaticKeyGeneration:

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

Aislamiento por aplicación

Cuando un host de ASP.NET Core proporciona el sistema de protección de datos, aísla automáticamente las aplicaciones entre sí, incluso aunque las aplicaciones se ejecuten en la misma cuenta de proceso de trabajo y usen el mismo material de creación de claves maestras. Esto es similar al modificador IsolateApps del elemento <machineKey> de System.Web.

El mecanismo de aislamiento funciona teniendo en cuenta cada aplicación del equipo local como un inquilino único, por lo que la raíz de IDataProtector de cualquier aplicación determinada incluye automáticamente el id. de aplicación como discriminador (ApplicationDiscriminator). El id. único de la aplicación es la ruta de acceso física de la aplicación:

  • En el caso de las aplicaciones hospedadas en IIS, el id. único es la ruta de acceso física de IIS de la aplicación. Si una aplicación se implementa en un entorno de granja de servidores web, este valor es estable suponiendo que los entornos de IIS se configuren de forma similar en todas las máquinas de la granja de servidores web.
  • En el caso de las aplicaciones autohospedadas que se ejecutan en el servidorKestrel, el id. único es la ruta de acceso física a la aplicación en el disco.

El identificador único está diseñado para sobrevivir a los restablecimientos, tanto de la aplicación individual como de la propia máquina.

Este mecanismo de aislamiento supone que las aplicaciones no son malintencionadas. Una aplicación malintencionada siempre puede afectar a cualquier otra aplicación que se ejecute en la misma cuenta de proceso de trabajo. En un entorno de hospedaje compartido en el que las aplicaciones no son de confianza mutua, el proveedor de hospedaje debe tomar medidas para garantizar el aislamiento de nivel de sistema operativo entre las aplicaciones, incluida la separación de los repositorios de claves subyacentes de las aplicaciones.

Si un el sistema de protección de datos no lo proporciona un host de ASP.NET Core (por ejemplo, si se crea una instancia a través del tipo concreto DataProtectionProvider), el aislamiento de la aplicación está deshabilitado de forma predeterminada. Cuando el aislamiento de la aplicación está deshabilitado, todas las aplicaciones respaldadas por el mismo material de creación de claves pueden compartir cargas si proporcionan los fines adecuados. Para proporcionar el aislamiento de aplicaciones en este entorno, llame al método SetApplicationName en el objeto de configuración y proporcione un nombre único para cada aplicación.

Protección de datos y aislamiento de aplicaciones

Hay que tener en cuenta los siguientes puntos para el aislamiento de aplicaciones:

  • Cuando varias aplicaciones apuntan al mismo repositorio de claves, la intención es que las aplicaciones compartan el mismo material de claves maestras. La protección de datos se desarrolla bajo la suposición de que todas las aplicaciones que comparten un anillo de claves pueden acceder a todos los elementos de ese anillo de claves. El identificador único de la aplicación se usa para aislar las claves específicas de la aplicación derivadas de las claves proporcionadas por el anillo de claves. No espera que se usen permisos de nivel de elemento, como los proporcionados por Azure KeyVault para aplicar aislamiento adicional. El intento de permisos de nivel de elemento genera errores de aplicación. Si no quiere depender del aislamiento de aplicaciones integrado, debe usar ubicaciones de almacenamiento de claves independientes y no compartidas entre aplicaciones.

  • El discriminador de aplicaciones (ApplicationDiscriminator) se usa para permitir que varias aplicaciones compartan el mismo material de claves maestras, pero manteniendo sus cargas criptográficas distintas entre sí. Para que las aplicaciones puedan leer las cargas criptográficas entre sí, deben tener el mismo discriminador de aplicaciones, que se puede establecer mediante una llamada a SetApplicationName.

  • Si una aplicación está en riesgo (por ejemplo, por un ataque de RCE), todo el material de claves maestras al que tenga acceso la aplicación deberá considerarse también en riesgo, independientemente de su estado de protección en reposo. Esto implica que si dos aplicaciones apuntan al mismo repositorio, aunque usen discriminadores de aplicaciones diferentes, poner en riesgo una de ellas es funcionalmente equivalente a poner en riesgo las dos.

    La parte "funcionalmente equivalente a poner en riesgo las dos" es válida incluso aunque las dos aplicaciones usen mecanismos diferentes para la protección de claves en reposo. Normalmente, esta configuración no es la esperada. El mecanismo de protección en reposo tiene la finalidad de proporcionar protección en caso de que un adversario obtenga acceso de lectura al repositorio. Un adversario que obtenga acceso de escritura al repositorio (quizás porque obtuvo el permiso de ejecución de código en una aplicación) puede insertar claves malintencionadas en el almacenamiento. El sistema de protección de datos no proporciona deliberadamente protección contra los adversarios que obtienen acceso de escritura al repositorio de claves.

  • Si las aplicaciones tienen que permanecer realmente aisladas entre sí, deben usar repositorios de claves diferentes. Lógicamente, esto no se ajusta a la definición de "aislado". Las aplicaciones no están aisladas si todas ellas tienen acceso de lectura y escritura a los almacenes de datos de las demás.

Cambio de algoritmos con UseCryptographicAlgorithms

La pila de protección de datos permite cambiar el algoritmo predeterminado usado por las claves recién generadas. La manera más sencilla de hacerlo es llamar a UseCryptographicAlgorithms desde la devolución de llamada de configuración:

services.AddDataProtection()
    .UseCryptographicAlgorithms(
        new AuthenticatedEncryptorConfiguration()
    {
        EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
        ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
    });

El valor predeterminado de EncryptionAlgorithm es AES-256-CBC y el valor predeterminado de ValidationAlgorithm es HMACSHA256. La directiva predeterminada puede establecerla un administrador del sistema a través de una directiva a nivel de equipo, pero una llamada explícita a UseCryptographicAlgorithms invalida la directiva predeterminada.

La llamada a UseCryptographicAlgorithms permite especificar el algoritmo deseado de una lista integrada predefinida. No hay que preocuparse por los detalles de implementación del algoritmo. En el escenario anterior, el sistema de protección de datos intenta usar la implementación CNG de AES si se ejecuta en Windows. De lo contrario, recurre a la clase administrada System.Security.Cryptography.Aes.

Se puede especificar manualmente una implementación mediante una llamada a UseCustomCryptographicAlgorithms.

Sugerencia

El cambio de algoritmos no afecta a las claves existentes del anillo de claves. Solo afecta a las claves recién generadas.

Especificación de algoritmos administrados personalizados

Para especificar algoritmos administrados personalizados, cree una instancia de ManagedAuthenticatedEncryptorConfiguration que apunte a los tipos de implementación:

serviceCollection.AddDataProtection()
    .UseCustomCryptographicAlgorithms(
        new ManagedAuthenticatedEncryptorConfiguration()
    {
        // A type that subclasses SymmetricAlgorithm
        EncryptionAlgorithmType = typeof(Aes),

        // Specified in bits
        EncryptionAlgorithmKeySize = 256,

        // A type that subclasses KeyedHashAlgorithm
        ValidationAlgorithmType = typeof(HMACSHA256)
    });

Generalmente las propiedades *Type deben apuntar a implementaciones concretas e instanciables (a través de un ctor público sin parámetros) de SymmetricAlgorithm y KeyedHashAlgorithm, aunque el sistema aplica casos especiales a algunos valores, como typeof(Aes), por comodidad.

Nota:

SymmetricAlgorithm debe tener una longitud de clave de ≥ 128 bits y un tamaño de bloque de ≥ 64 bits, y debe admitir el cifrado en modo CBC con relleno PKCS #7. KeyedHashAlgorithm debe tener un tamaño de hash de >= 128 bits y debe admitir claves de longitud igual a la longitud de hash del algoritmo hash. No es estrictamente necesario que el KeyedHashAlgorithm sea HMAC.

Especificación de algoritmos CNG de Windows personalizados

Para especificar un algoritmo CNG de Windows personalizado mediante el cifrado en modo CBC con validación HMAC, cree una instancia de CngCbcAuthenticatedEncryptorConfiguration que contenga la siguiente información algorítmica:

services.AddDataProtection()
    .UseCustomCryptographicAlgorithms(
        new CngCbcAuthenticatedEncryptorConfiguration()
    {
        // Passed to BCryptOpenAlgorithmProvider
        EncryptionAlgorithm = "AES",
        EncryptionAlgorithmProvider = null,

        // Specified in bits
        EncryptionAlgorithmKeySize = 256,

        // Passed to BCryptOpenAlgorithmProvider
        HashAlgorithm = "SHA256",
        HashAlgorithmProvider = null
    });

Nota:

El algoritmo de cifrado de bloques simétrico debe tener una longitud de clave de >= 128 bits y un tamaño de bloque de >= 64 bits, y debe admitir el cifrado en modo CBC con relleno PKCS #7. El algoritmo hash debe tener un tamaño de hash de >= 128 bits y debe admitir que se abra con la marca BCRYPT_ALG_HANDLE_HMAC_FLAG. Las propiedades *Provider se pueden establecer en null para usar el proveedor predeterminado para el algoritmo especificado. Para obtener más información, consulte la documentación de BCryptOpenAlgorithmProvider.

Para especificar un algoritmo CNG de Windows personalizado mediante el cifrado en modo Galois/Counter con validación, cree una instancia de CngGcmAuthenticatedEncryptorConfiguration que contenga la siguiente información algorítmica:

services.AddDataProtection()
    .UseCustomCryptographicAlgorithms(
        new CngGcmAuthenticatedEncryptorConfiguration()
    {
        // Passed to BCryptOpenAlgorithmProvider
        EncryptionAlgorithm = "AES",
        EncryptionAlgorithmProvider = null,

        // Specified in bits
        EncryptionAlgorithmKeySize = 256
    });

Nota:

El algoritmo de cifrado de bloques simétrico debe tener una longitud de clave de >= 128 bits y un tamaño de bloque de exactamente 128 bits, y debe admitir el cifrado GCM. Se puede establecer la propiedad EncryptionAlgorithmProvider en null para usar el proveedor predeterminado para el algoritmo especificado. Para obtener más información, consulte la documentación de BCryptOpenAlgorithmProvider.

Especificación de otros algoritmos personalizados

Aunque no se expone como una API de primera clase, el sistema de protección de datos es lo suficientemente extensible como para permitir la especificación de casi cualquier tipo de algoritmo. Por ejemplo, es posible contener todas las claves en un módulo de seguridad de hardware (HSM) y proporcionar una implementación personalizada de las rutinas de cifrado y descifrado principales. Para obtener más información, consulte IAuthenticatedEncryptor en Extensibilidad de criptografía básica.

Persistencias de claves al hospedar en un contenedor Docker

Al hospedar en un contenedor Docker, las claves deben conservarse en una de las siguientes opciones:

  • Una carpeta que sea un volumen de Docker y que persista más allá de la duración del contenedor, como un volumen compartido o un volumen montado en host.
  • Un proveedor externo, como Azure Blob Storage (se muestra en la sección ProtectKeysWithAzureKeyVault) o Redis.

Claves persistentes con Redis

Para almacenar claves, solo deben usarse las versiones de Redis compatibles con la persistencia de datos en Redis. Azure Blob Storage es persistente y se puede usar para almacenar claves. Para más información, consulte este problema de GitHub.

Registro de DataProtection

Habilite el registro de nivel de Information de DataProtection para ayudar a diagnosticar problemas. El siguiente archivo appsettings.json habilita el registro de información de la API DataProtection:

{
  "Logging": {
    "LogLevel": {
      "Microsoft.AspNetCore.DataProtection": "Information"
    }
  }
}

Para obtener más información sobre el registro, vea Registro en .NET Core y ASP.NET Core.

Recursos adicionales