《數據加解密與哈希》演示了“數據保護”框架如何用來對數據進行加解密,而“數據保護”框架的核心是“密鑰管理”。數據保護框架以XML的形式來存儲密鑰,默認的IKeyManager實現類型為XmlKeyManager。接下來我們通過模擬代碼和實例演示的形式來介紹一下XmlKeyManager對象針對密鑰的創建、撤銷和回收的實現原理。(本篇提供的實例已經匯總到《ASP.NET Core 6框架揭秘-實例演示版》)
[S1308]基於本地文件系統的密鑰管理(密鑰創建)(源代碼)
[S1309]基於本地文件系統的密鑰管理(密鑰撤銷)(源代碼)
[S1308]基於本地文件系統的密鑰管理(密鑰創建)
接下來我們通過如下這個簡單的演示實例來看看創建出來的密鑰對應的XML具有怎樣的結構。如代碼片段所示,我們通過依賴注入容器得到IKeyManager對象,並用它創建了三個密鑰。在調用AddDataProtection擴展方法后,我們調用返回IDataProtectionBuilder對象的PersistKeysToFileSystem擴展方法,其目的是利用FileSystemXmlRepository對象將代表創建密鑰和密鑰撤銷操作的XML存儲在指定的目錄(“c:\keys”)下。
using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.DataProtection.KeyManagement; using Microsoft.Extensions.DependencyInjection; var directory = "c:\\keys"; var services = new ServiceCollection(); services .AddDataProtection() .PersistKeysToFileSystem(new DirectoryInfo(directory)); var keyManager = services .BuildServiceProvider() .GetRequiredService<IKeyManager>(); var key1 = keyManager.CreateNewKey(DateTimeOffset.Now, DateTimeOffset.Now.AddDays(1)); var key2 = keyManager.CreateNewKey(DateTimeOffset.Now ,DateTimeOffset.Now.AddDays(2)); var key3 = keyManager.CreateNewKey(DateTimeOffset.Now, DateTimeOffset.Now.AddDays(3)); Console.WriteLine(key1.KeyId); Console.WriteLine(key2.KeyId); Console.WriteLine(key3.KeyId);
演示程序運行后會將創建的三個密鑰的ID以如圖1的形式輸出到控制台上。與此同時,目錄“c:\keys\”下會出現三個對應的XML文件。從圖中可以看出,表示密鑰文件的名稱正是調用IXmlRepository對象的StoreElement方法時指定的名稱。
如下所示的是其中一個密鑰對應的XML文件的內容。通過IAuthenticatedEncryptorDescriptor對象導出的XML體現在<key>/<descriptor>節點上。可以看出當前環境下默認采用的加密和哈希算法分別是AES_256_CBC和HMACSHA256,AuthenticatedEncryptorDescriptorDeserializer為采用的反序列化器類型。
<?xml version="1.0" encoding="utf-8"?> <key id="3f8769f0-78c8-42f2-9f13-102d83bb298c" version="1"> <creationDate>2022-03-12T09:47:38.4677311Z</creationDate> <activationDate>2022-03-12T17:47:38.4630578+08:00</activationDate> <expirationDate>2022-03-13T17:47:38.4674829+08:00</expirationDate> <descriptor deserializerType="Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60"> <descriptor> <encryption algorithm="AES_256_CBC" /> <validation algorithm="HMACSHA256" /> <masterKey p4:requiresEncryption="true" xmlns:p4="http://schemas.asp.net/2015/03/dataProtection"> <!-- Warning: the key below is in an unencrypted form. --> <value>oFjUpRP4cKwp0QQctjibDN8QK3m76uQAlkGZ1V4E5sb9l9Yn4zzgjHyxpoJ7qENqBGwdD0A11U90YzGRb53p+g==</value> </masterKey> </descriptor> </descriptor> </key>
[S1309]基於本地文件系統的密鑰管理(密鑰撤銷)
我們接着來看看密鑰的撤銷。如果是針對單個密鑰的撤銷,該密鑰的ID會通過名為“key”的子元素保存下來。如果需要撤銷現有的所有密鑰,這個key元素的值會設置為“*”。兩者采用的文件名稱也不相同,格式分別為“revocation-{KeyId}”和“revocation-{RevocationDate}”。我們接下來演示針對密鑰的撤銷。如下面的代碼片段所示,在得到IKeyManager對象之后,我們調用其GetAllKeys方法得到所有密鑰。在調用RevokeKey方法撤銷第一個得到密鑰之后,我們調用RevokeAllKeys方法將現有密鑰全部撤銷掉。
using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.DataProtection.KeyManagement; using Microsoft.Extensions.DependencyInjection; var directory = "c:\\keys"; var services = new ServiceCollection(); services .AddDataProtection() .PersistKeysToFileSystem(new DirectoryInfo(directory)); var keyManager = services .BuildServiceProvider() .GetRequiredService<IKeyManager>(); var key = keyManager.GetAllKeys().First(); keyManager.RevokeKey(key.KeyId); keyManager.RevokeAllKeys(DateTimeOffset.Now, "Revocation Test");
演示程序執行后會在密鑰存儲目錄(c:\keys\)下生成兩個名稱前綴為“revocation-”的XML文件。與上面的代碼進行比較,我們會發現XML文件的名稱依然是調用IXmlRepository對象的StoreElement方法指定的名稱。
如下所示的是這兩個描述密鑰撤銷的XML文件的內容,可以看出XML結構與前面提供的RevokeKey和RevokeAllKeys方法的定義是匹配的。
revocation-184d9274-78f1-4352-bbed-1d0d6803ddad.xml
<?xml version="1.0" encoding="utf-8"?> <revocation version="1"> <revocationDate>2022-03-12T09:54:48.1157691Z</revocationDate> <key id="184d9274-78f1-4352-bbed-1d0d6803ddad" /> <reason /> </revocation>
revocation-20220312T0954481194781Z.xml
<?xml version="1.0" encoding="utf-8"?> <revocation version="1"> <revocationDate>2022-03-12T17:54:48.1194781+08:00</revocationDate> <!-- All keys created before the revocation date are revoked. --> <key id="*" /> <reason>Revocation Test</reason> </revocation>