Objective
"Medium", in some scenarios, we need to extend the encryption method of ASP. NET core to suit our needs, this time we need to use some of the advanced features provided by the core.
This article also lists some of the ways we need to implement our own methods to distribute data protection in a clustered scenario.
Encryption Extensions Iauthenticatedencryptor and Iauthenticatedencryptordescriptor
IAuthenticatedEncryptor
is a basic interface of Data Protection in building its cryptographic encryption system.
In general, a key corresponds to one IAuthenticatedEncryptor
, which IAuthenticatedEncryptor
encapsulates the key material and the necessary cryptographic algorithm information needed to be used in the cryptographic operation.
Here are IAuthenticatedEncryptor
the two API methods provided by the interface:
Decrypt(ArraySegment<byte> ciphertext, ArraySegment<byte> additionalAuthenticatedData) : byte[]Encrypt(ArraySegment<byte> plaintext, ArraySegment<byte> additionalAuthenticatedData) : byte[]
The parameters in the interface additionalAuthenticatedData
indicate some of the ancillary information provided when the encryption is built.
IAuthenticatedEncryptorDescriptor
The interface provides a way to create an instance of the containing type information IAuthenticatedEncryptor
.
CreateEncryptorInstance() : IAuthenticatedEncryptorExportToXml() : XmlSerializedDescriptorInfo
Key Management Extensions
In key System management, a basic interface is provided IKey
that contains the following properties:
Activationcreationexpiration datesRevocation statusKey identifier (a GUID)
IKey
It also provides a IAuthenticatedEncryptor
method for creating an instance, Createencryptorinstance.
IKeyManager
The interface provides a series of methods for manipulating keys, including storage, retrieval operations, and so on. The advanced operations he provides are:
- Create a key and persist storage
- Get all keys from the repository
- Undo one or more keys saved to the store
Xmlkeymanager
Typically, developers do not need to implement IKeyManager
a keymanager to customize. We can use the classes provided by the system by default XmlKeyManager
.
Xmlkeymanager is a concrete implementation IKeyManager
class that provides some very useful methods.
public sealedClass Xmlkeymanager:ikeymanager, iinternalxmlkeymanager{PublicXmlkeymanager(ixmlrepository repository, iauthenticatedencryptorconfiguration configuration, IServiceProvider services);Public IKeycreatenewkey (DateTimeOffset activationdate, DateTimeOffset ExpirationDate); public ireadonlycollection<ikey> Getallkeys (); public cancellationtoken Getcacheexpirationtoken (); public void revokeallkeys (DateTimeOffset revocationdate, string reason = null); public void revokekey (Guid keyId, string Reason = null);}
- Iauthenticatedencryptorconfiguration is primarily the algorithm used to specify the new key.
- Ixmlrepository primarily controls where the key is persisted for storage.
Ixmlrepository
IXmlRepository
The interface primarily provides a way to persist and retrieve XML as long as it provides two APIs:
- Getallelements (): ireadonlycollection
- StoreElement (XElement element, string friendlyname)
We can IXmlRepository
define the storage location of the data protection XML by implementing the StoreElement method of the interface.
Getallelements to retrieve all existing encrypted XML files.
The interface section is written here, because this one I want to focus on the following, more interface introduction people still go to the official documents to see ~
Cluster scenario
The above API estimates look a bit dull, so let's take a look at what we need to do with the data protection in the cluster scenario.
As I mentioned at the end of the "previous article" summary, some of the mechanisms of Data protection we need to know when doing a distributed cluster, because if you don't know what these might cause some trouble for your deployment, let's take a look.
When doing a cluster, we must know and understand the three things about ASP. NET Core Data Protection:
1. Program identifier
"Application Discriminator", which is used to identify the uniqueness of an application.
Why do you need this thing? Because in a clustered environment, if not constrained by a specific hardware machine environment, it is necessary to exclude some of the differences that run the machine, you need to abstract out some specific identities to identify the application itself and use that identity to differentiate between different applications. This time, we can specify ApplicationDiscriminator
.
At services.AddDataProtection(DataProtectionOptions option)
the time, ApplicationDiscriminator
you can pass as a parameter and take a look at the code:
PublicvoidConfigureservices (Iservicecollection services) {services. Adddataprotection (); Services. Adddataprotection (dataprotectionoptions option);}The =========== extension method is as follows:PublicStaticClassdataprotectionservicecollectionextensions{PublicStatic Idataprotectionbuilderadddataprotection (this Iservicecollection services); //overload with transitive parameters that need to be configured in a clustered environment static idataprotectionbuilder adddataprotection (this iservicecollection Services, action<dataprotectionoptions > Setupaction);} //dataprotectionoptions properties: public class dataprotectionoptions{ Public string applicationdiscriminator {get; set; }}
You can see that this extension returns one IDataProtectionBuilder
, in IDataProtectionBuilder
addition to an extension method called Setapplicationname, the extension method internally or modifies the value of Applicationdiscriminator. It is also said that the following notation is equivalent:
"my_app_sample_identity");services.AddDataProtection().SetApplicationName("my_app_sample_identity");
This means that the same application in the cluster environment needs to be set to the same value (applicationname or applicationdiscriminator).
2. Primary encryption Key
"Master encryption Key", which is mainly used for encrypting and decrypting, includes some session data, status, etc. of a client server during the request process. There are several options that can be configured, such as using certificates or Windows DPAPI or the registry. If you are a non-Windows platform, the registry and Windows DPAPI will not work.
public void configureservices (iservicecollection services) {services. Adddataprotection () //windows Dpaip as the primary encryption key. Protectkeyswithdpapi () //if it is Windows 8+ or Windows server2012+ can use this option (based on Windows Dpapi-ng). Protectkeyswithdpaping ( "sid={current account SID}", Dpapingprotectiondescriptorflags.none) //if it is Windows 8+ or Windows server2012+ You can use this option (certificate-based). Protectkeyswithdpaping ( "certificate=hashid:3bce558e2ad3e0e34a7743eab5aea2a9bd2575a0" , Dpapingprotectiondescriptorflags.none) //use the certificate as the primary encryption key, currently only widnows support, Linux is not supported. Protectkeyswithcertificate ();}
If they are in a clustered environment, they need to have the same primary encryption key configured.
3. Storage location after encryption
As I said in the previous article, by default, data protection generates an XML file for storing session or State key files. These files are used to encrypt or decrypt state data such as session.
That's the private key in the previous article. Storage location:
1. If the program is hosted under Microsoft Azure, it is stored in the "%home%\asp.net\dataprotection-keys" folder.
2. If the program is hosted under IIS, it is saved in the Acled Special registry key of the HKLM Registry, and only the worker process can access it using the Windows DPAPI encryption.
3. If the current user is available, that is, win10 or win7, it is stored in the "%localappdata%\asp.net\dataprotection-keys" folder, and the same uses the DPAPI encryption of Windows.
4, if these are not met, then that is, the private key is not persisted, that is, when the process is closed, the generated private key is lost.
Under cluster environment:
The simplest way to do this is through file sharing, DPAPI, or the registry, which means that the encrypted XML files are stored in the same place. Why is the simplest, because the system has been packaged well, do not need to write redundant code, but to ensure that the file sharing related ports are open. As follows:
public void ConfigureServices(IServiceCollection services) { services.AddDataProtection() //windows、Linux、macOS 下可以使用此种方式 保存到文件系统 .PersistKeysToFileSystem(new System.IO.DirectoryInfo("C:\\share_keys\\")) //windows 下可以使用此种方式 保存到注册表 .PersistKeysToRegistry(Microsoft.Win32.RegistryKey.FromHandle(null)) }
You can also expand your own methods to define some of your own storage, such as using a database or Redis.
Usually, however, if you deploy on Linux, you need to expand it. Let's take a look at what we want to do with Redis storage.
How do I extend the storage location of the cryptographic key collection?
First, define a IXmlRepository
Redis implementation class for the interface RedisXmlRepository.cs
:
PublicClass Redisxmlrepository:ixmlrepository, idisposable{PublicStatic readonlyString Redishashkey ="Dataprotectionxmlrepository";Private Iconnectionmultiplexer _connection;private bool _disposed =FalsePublic Redisxmlrepository (String connectionString, ilogger<redisxmlrepository> logger):This (Connectionmultiplexer.connect (connectionString), logger) {}Public Redisxmlrepository (iconnectionmultiplexer connection, ilogger<redisxmlrepository> logger) {if (connection = =NULL) {ThrowNew ArgumentNullException (nameof (connection)); }if (logger = =NULL) {ThrowNew ArgumentNullException (nameof (logger)); }This._connection = connection;This. Logger = Logger;var configuration = Regex.Replace (This._connection. Configuration, @"Password\s*=\s*[^,]*","password=****", regexoptions.ignorecase);This. Logger.logdebug ("Storing data protection keys in Redis: {redisconfiguration}", configuration); }Public ilogger<redisxmlrepository> Logger {GetPrivateSet }Publicvoid Dispose () {This. Dispose (true); }Public ireadonlycollection<xelement> getallelements () {var database =This._connection. Getdatabase ();var hash = database. Hashgetall (Redishashkey);var elements =New List<xelement> ();if (hash = =null | | Hash. Length = =0) {return elements. AsReadOnly (); } foreach (VAR itemIn Hash. Tostringdictionary ()) {elements. ADD (Xelement.parse (item. Value)); }This. Logger.logdebug ("Read {Xmlelementcount} XML elements from Redis.", Elements. Count);return elements. AsReadOnly (); }Publicvoid StoreElement (XElement element,String friendlyname) {if (element = =NULL) {ThrowNew ArgumentNullException (nameof (Element)); }if (String. IsNullOrEmpty (FriendlyName)) {friendlyname = Guid.NewGuid (). ToString (); }This. Logger.logdebug ("Storing XML element with friendly name {xmlelementfriendlyname}.", FriendlyName); this._connection. Getdatabase (). HashSet (Redishashkey, friendlyname, element. ToString ()); } protected virtual void Dispose (bool disposing) { if (! this._disposed) { if (disposing) { if (this._connection ! = null) { this._connection. Close (); this._connection. Dispose (); }} this._connection = null; this._disposed = true; }}}
Then an extension method is defined in either extension class first:
PublicStatic IdataprotectionbuilderPersistkeystoredis (This Idataprotectionbuilder builder,String redisconnectionstring) {if (builder = =NULL) {ThrowNew ArgumentNullException (nameof (builder)); }if (redisconnectionstring = =NULL) {ThrowNew ArgumentNullException (Nameof (redisconnectionstring)); }if (redisconnectionstring.length = =0) {ThrowNew ArgumentException ( "Redis connection string may not be empty.", Nameof (redisconnectionstring)); } //because of the services. Adddataprotection (), has been injected into the ixmlrepository, so should first remove //here should be encapsulated as a method to invoke, for the reader to understand, I wrote directly for (int i = Builder. Services.count-1; I >= 0; i--) {var descriptor = servicedescriptor.singleton<ixmlrepository> (Services = Span class= "Hljs-keyword" >new redisxmlrepository (redisconnectionstring, services. Getrequiredservice<ilogger<redisxmlrepository>> ())) builder. Services.add (descriptor); return builder. Use ();}
In the final services about DataProtection:
PublicvoidConfigureservices (Iservicecollection services) {services. Adddataprotection ()================ The following is a unique identifier ==============Sets the application unique identity. Setapplicationname ("My_app_sample_identity");============= The following is the primary encryption key ===============Windows DPAIP as the primary encryption key. Protectkeyswithdpapi ()If Windows 8+ or Windows server2012+ can use this option (based on Windows Dpapi-ng). Protectkeyswithdpaping ("Sid={current account SID}", Dpapingprotectiondescriptorflags.none)//if Windows 8+ or Windows server2012+ can use this option (certificate-based). Protectkeyswithdpaping ( "certificate=hashid:3bce558e2ad3e0e34a7743eab5aea2a9bd2575a0" , Dpapingprotectiondescriptorflags.none) //use the certificate as the primary encryption key, currently only widnows support, Linux is not supported. Protectkeyswithcertificate (); //============== The following is the storage location ================= //windows, Linux, MacOS can be saved to the file system using this method. Persistkeystofilesystem (new System.IO.DirectoryInfo ( "c:\\ Share_keys\\ ")) //windows can be saved to the registry using this method. Persistkeystoregistry (Microsoft.Win32.RegistryKey.FromHandle (null)) //Storage to Redis. Persistkeystoredis (Configuration.section[ "redisconnection"])}
In the above configuration, I have all the available configurations are listed, the actual project should be selected according to the actual situation.
Summarize
About the ASP. Protection Series finally finished, this part of it took a lot of time, for Data Protection I am also a follow-up learning process, I hope to help some people.
If you feel that this article is useful to you, you may wish to order a "recommendation".
This address: http://www.cnblogs.com/savorboard/p/dotnetcore-data-protected-farm.html
Author Blog: Savorboard
Welcome reprint, please give the source and link in obvious position
NET Core Data Protection 2