Szenarien ohne Unterstützung der Abhängigkeitsinjektion für den Schutz von Daten in ASP.NET Core

Von Rick Anderson

Das System zum Schutz von Daten in ASP.NET wird normalerweise einem Dienstcontainer hinzugefügt und von abhängigen Komponenten über Abhängigkeitsinjektion (Dependency Injection, DI) genutzt. Es gibt jedoch Fälle, in denen dies nicht möglich oder gewünscht ist, insbesondere wenn das System in eine vorhandene App importiert wird.

Zur Unterstützung dieser Szenarien stellt das Paket Microsoft.AspNetCore.DataProtection.Extensions einen konkreten Typ namens DataProtectionProvider bereit, der eine einfache Möglichkeit zur Verwendung des Schutzes von Daten bietet, ohne sich auf DI zu stützen. Der Typ DataProtectionProvider implementiert IDataProtectionProvider. Zum Erstellen von DataProtectionProvider muss nur eine DirectoryInfo-Instanz bereitgestellt werden, um anzugeben, wo die kryptografischen Schlüssel des Anbieters gespeichert werden sollen, wie im folgenden Codebeispiel gezeigt:

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...
*/

Standardmäßig verschlüsselt der konkrete Typ DataProtectionProvider rohes Schlüsselmaterial nicht, bevor es im Dateisystem gespeichert wird. Damit sollen Szenarien unterstützt werden, in denen Entwickler*innen auf eine Netzwerkfreigabe verweisen und das Datensicherungssystem nicht automatisch einen geeigneten Verschlüsselungsmechanismus für ruhende Daten ableiten kann.

Überdies sorgt der konkrete Typ DataProtectionProvider standardmäßig nicht für die Isolierung von Apps. Alle Apps, die dasselbe Schlüsselverzeichnis verwenden, können Nutzdaten gemeinsam nutzen, sofern ihre Zweckparameter übereinstimmen.

Der DataProtectionProvider-Konstruktor akzeptiert einen optionalen Konfigurationsrückruf, mit dem sich das Verhalten des Systems anpassen lässt. Im folgenden Beispiel wird die Wiederherstellung der Isolation durch einen expliziten Aufruf von SetApplicationName veranschaulicht. Im Beispiel wird außerdem gezeigt, wie das System so konfiguriert wird, dass persistente Schlüssel mithilfe von Windows DPAPI automatisch verschlüsselt werden. Wenn das Verzeichnis auf eine UNC-Freigabe verweist, können Sie ein freigegebenes Zertifikat auf alle relevanten Computer verteilen und das System so konfigurieren, dass die zertifikatbasierte Verschlüsselung mit einem Aufruf von ProtectKeysWithCertificate verwendet wird.

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();
    }
}

Tipp

Die Erstellung von Instanzen des konkreten Typs DataProtectionProvider ist kostspielig. Wenn eine App mehrere Instanzen dieses Typs verwaltet und alle dasselbe Schlüsselspeicherverzeichnis verwenden, kann sich die Leistung der Anwendung verschlechtern. Wenn Sie den DataProtectionProvider-Typ verwenden, empfiehlt es sich, diesen Typ einmal zu erstellen und so oft wie möglich wiederzuverwenden. Der DataProtectionProvider-Typ und alle daraus erstellten IDataProtector-Instanzen sind für mehrere Aufrufer threadsicher.