ASP.NET Core Data Protection [medium]

Keywords: ASP.NET C#

Last article It mainly introduces the Data Protection of ASP.NET Core. This article mainly introduces the API and usage.

API interface

ASP.NET Core Data Protectio mainly provides two interfaces to ordinary developers, IDataProtectionProvider   and   IDataProtector.
Let's first look at the relationship between the two interfaces:

namespace Microsoft.AspNetCore.DataProtection
{
    //
    // Summary:
    //     An interface that can provide data protection services.
    public interface IDataProtector : IDataProtectionProvider
    {

        byte[] Protect(byte[] plaintext);

        byte[] Unprotect(byte[] protectedData);
    }
}

As you can see, IDataProtector inherits from IDataProtectionProvider  , Two methods are provided   Protect   and   Unprotect  , In terms of naming, one is encryption and the other is decryption. Their signatures are passed into a byte array, which means that they can encrypt and decrypt all objects. The returned is also a byte array, that is, in the actual use process, we should add or use some extension methods of the system to specify our requirements.

Let's take another look at the IDataProtectionProvider interface:

namespace Microsoft.AspNetCore.DataProtection
{
    public interface IDataProtectionProvider
    {

        IDataProtector CreateProtector(string purpose);
    }
}

IDataProtectionProvider provides a method by passing in a   purpose string (see details later) to generate an IDataProtector interface object.
From the naming of this interface, it ends with Provider, which means that we can implement our own set of encryption and decryption.

When we read the source code of Microsoft projects, we often see some objects ending with xxxprovider. What are its responsibilities and roles?
In fact, this is a design pattern specially designed by Microsoft for ASP.NET, called Provider Model design pattern. It can also be said that it was invented by Microsoft. It does not belong to one of the 23 design patterns. From the perspective of function, it should be a combination of factory and strategy. Since ASP.NET 2.0, Microsoft has introduced this design pattern, which is mainly used to realize multiple implementations of application configuration. For example, in web.config, which developers are most familiar with, there are many configurations for database connection strings, as well as binary, such as XML, and so on. Now this mode is also used more and more in other places.

Let's talk about the signature in the CreateProtector method   purpose   In the last blog post, for the sake of readers' understanding, I said that the incoming purpose can be understood as a public key. In fact, this statement is not rigorous. It can be understood as an identifier indicating the purpose of the current Protector.

When using IDataProtector, you will find that it also has some extension methods under the Microsoft.AspNetCore.DataProtection namespace:

public static class DataProtectionCommonExtensions
{
    public static IDataProtector CreateProtector(this IDataProtectionProvider provider, IEnumerable<string> purposes);

    public static IDataProtector CreateProtector(this IDataProtectionProvider provider, string purpose, params string[] subPurposes);

    public static IDataProtector GetDataProtector(this IServiceProvider services, IEnumerable<string> purposes);

    public static IDataProtector GetDataProtector(this IServiceProvider services, string purpose, params string[] subPurposes);

    public static string Protect(this IDataProtector protector, string plaintext);

    public static string Unprotect(this IDataProtector protector, string protectedData);
}

You can see that CreateProtector also provides methods that can pass multiple purpose s (IEnumerable, params string []). Why is there such a requirement?

In fact, DataProtector has a hierarchical structure. Take another look at the IDataProtector interface. It also implements the IDataProtectionProvider interface, that is, IDataProtector itself can also create IDataProtector.

For example, we are working on a message communication system. In the process of message communication, we need to encrypt the user's session. We use CreateProtector("Security.BearerToken") for encryption. However, encryption does not guarantee that the message is sent by an untrusted client, so we thought of CreateProtector("username") to encrypt. At this time, if a user's username is "Security.BearerToken", it will conflict with another Protector marked with Security.BearerToken, so we can use it
CreateProtector(["Security.BearerToken", "User: username"]) this way. It is equivalent to
provider.CreateProtector("Security.BearerToken).CreateProtector(" User: username "). This means to create a Protector called" Security.BearerToken "first, and then create a Protector named" User: username "under purpose1.

User password hash

A KeyDerivation.Pbkdf2 method is provided under the Microsoft.AspNetCore.Cryptography.KeyDerivation namespace to hash user passwords.

Encryption with lifecycle constraints

Sometimes, we need some encrypted strings with expiration or expiration time. For example, when a user retrieves his password, we send an email with a reset command to the user's mailbox. The reset command needs to have an expiration time. It will expire after the expiration time. In the past, we may need to store an hour in the database Mark the sending time, and then decrypt and compare with the time difference between the database to verify.

Now we don't need to do this. ASP.NET Core provides an interface called   ITimeLimitedDataProtector  , Let's first look at the definition of this interface:

CreateProtector(string purpose) : ITimeLimitedDataProtector This API is similar to the existing IDataProtectionProvider.CreateProtector in that it can be used to create purpose chains from a root time-limited protector.
Protect(byte[] plaintext, DateTimeOffset expiration) : byte[]
Protect(byte[] plaintext, TimeSpan lifetime) : byte[]
Protect(byte[] plaintext) : byte[]
Protect(string plaintext, DateTimeOffset expiration) : string
Protect(string plaintext, TimeSpan lifetime) : string
Protect(string plaintext) : string

ITimeLimitedDataProtector provides several overloaded methods to set the encryption method with life cycle. Users can set the time through date, timeoffset, TimeSpan and other parameters.

If there is a corresponding encryption, there is a corresponding decryption method, which will not be introduced in detail here. Interested students can go to see the official documents.

Configure data protection

When our ASP.NET Core is running, the system will configure some Data Protection by default based on the running environment of the current machine, but sometimes it may be necessary to make some changes to these configurations. For example, during distributed deployment, it was mentioned at the end of the previous blog post. Let's take a look at the specific configuration.

As mentioned in the previous article, we register Data Protection with the service in the following ways:

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

Where AddDataProtection   Returns a   IDataProtectionBuilder   Interface, which provides an extension method PersistKeysToFileSystem()   To store the private key. You can pass in a path to specify the location where the private key is stored:

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

}

A shared folder can be passed in to store the private key, so that the private keys of different machines can be saved to one location. In this way, the differentiation of machines can be separated during distributed deployment.
If you feel insecure, you can also configure an X.509 certificate for encryption:

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

As mentioned in the previous article, the default save time of Data Protection is 90 days. You can modify the default save time in the following ways:

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

By default, even if the same physical keystore is used, Data Protection will separate different applications, because this can prevent obtaining the key of another application from one application. Therefore, if it is the same application, you can set the same application name:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .SetApplicationName("my application");
}

Sometimes it is necessary to disable the application to generate keys, or if I have only one program to generate or manage keys, and other programs are only responsible for reading, then this can be done:

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

Modify encryption algorithm

The default encryption algorithm of ASP.NET Core Data Protection can be modified by using the usecryptgraphicalalgorithms method, as follows:

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

summary

This chapter mainly introduces some common API s, and the next chapter introduces some advanced usage.

Posted by rmurdo on Wed, 13 Oct 2021 21:41:55 -0700