未能使用提供者 "RsaProtectedConfigurationProvider" 進行解密 的解決辦法 如何使用 Enterprise Library 3.0 的Cryptography Application Block 處理加密
本文不示範如何使用組態工具對配置區進行加密,也不示範如何匯出匯入 key 檔案,而主要解決兩個問題:
1.我們使用組態工具對配置區進行加密後,(通常 configProtectionProvider有兩種選擇,一個是RsaProtectedConfigurationProvider,一個是DataProtectionConfigurationProvider),在本機使用正常,但是如果換到其它機器上,再用組態工具,就無法開啟。使用RsaProtectedConfigurationProvider的,報的錯誤如下:未能使用提供者"RsaProtectedConfigurationProvider" 進行解秘密。
2.我們使用Cryptography Application Block 的加密處理塊後,建立了一個Symmetric Providers,並使用了 DESCryptoServiceProvider 提供的演算法,這個過程中產生了一個key 檔案。但是這個 key 檔案複製的其他機器是無效的(後面解釋原因)。當然,我們可以通過現在本機匯出的key的方式,然後在其他機器上匯入 key 檔案。但是這樣只解決了開發人員之間要使用相同的key檔案的問題。對於部署到用戶端,則不行。因為我們不大可能去用戶端上安裝 Enterprise Library 3.0,更不可能在安裝程式後,再來手工配置這個 key。(因為這樣明顯增加了安裝程式的複雜性,而安裝程式的人很可能就不是專業人員。)
首先聲明一點,打算在 protectedKeyFilename 中使用相對路徑的想法不是太行的通!為什麼這麼說呢?因為key 檔案無法直接複製到其他機器上。當我們去每台機器上配置 key 檔案(包括建立新的key或者匯入key,均需要儲存 key的絕對路徑的位置),我們已經知道了絕對路徑,所以沒有必要去用相對路徑! David Hayden在回答一位網友關於為什麼不使用相對路徑中說到:I would have thought that relative paths wouldbe fine, but I pulled this from the documentation within the sectionManaging and Distributing Keys which is pretty specific about the needfor absolute paths .... The Cryptography Application Block stores eachkey in a separate file on the local computer....他的意思說:雖然我本來認為相對路徑將會是巧妙的,(實際上卻不是這樣),但是我將它從文檔中“管理和分發 key檔案”的部分移開了,並在這個部分中相當明確地說明需要使用絕對路徑 ... 加密應用程式塊為每個 key檔案在本機電腦中儲存了一個獨立的檔案。(原文見http://www.codeplex.com/entlib/Thread/View.aspx?ThreadId=9834)
上面一大段廢話濃縮成成一句:既然是 key檔案都與本機電腦相關了,那相對路徑自然意義就不大了(不敢說絕對沒有意義). key檔案無法直接複製到其他機器上的原因是對這個密鑰檔案又使用 DPAPI (Windows 資料保護) 進行加密。使用了 DPAPI進行加密的資料只能在特定的電腦上使用,換個機器當然就不行了!因為你加密的時候使用了當前電腦和登陸到該電腦的使用者的資訊。
所以順便提一點:產生 key 檔案的過程中有個選擇項, 一個是使用者模式,一個是機器模式。使用者模式是登陸到該電腦的用
戶才能使用這個 key檔案,機器模式當然就是只要是這個電腦上的使用者都可以使用這個 key 檔案。比如部署 WebForms 的
WebSite 需要使用機器模式,至於WinForms的部署,如果不能確認電腦上只有一個帳戶會使用你部署的軟體,還是使用機器模式吧
!
針對第一個問題的解決方案:
如果想將一個設定檔(比如web.config, App.config)在一台電腦加密後,在其他電腦上都能使用,就達到了我們的目的。
說到對設定檔加密,自然要提到它們兩個:
DPAPIProtectedConfigurationProvider。使用 Windows 資料保護 API (DPAPI) (DPAPI)對資料進行加密和解密。
RsaProtectedConfigurationProvider。使用 RSA 密碼編譯演算法對資料進行加密和解密。
這兩個提供者都提供對資料的強加密;但是,如果打算在多台電腦上使用同一個加密設定檔,則只有使用RsaProtectedConfigurationProvider。因為這個才能匯出用於對資料進行加密的加密金鑰,並在另一台電腦上匯入它們。
我們使用組態工具對設定檔的配置區進行加密時可以指定哪個ProtectedConfigurationProvider。很顯然,在解決這個問題上,我們只能使用RsaProtectedConfigurationProvider。由於不同的電腦上RSA密鑰容器是各不相同的,所以我們需要從一台電腦上匯出一個RSA 密鑰容器,然後匯入其他電腦上!由於匯入方式是可以用 DOS 命令實現,所以在部署上不存在問題!另外,由於.NETFramework 有個預設的RSA密鑰容器,名稱為 "NetFrameworkConfigurationKey",在machine.config檔案可以看到這個預設的參數。
先提示以下:aspnet_regiis.exe 在C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727 下。C 盤是我的安裝路徑。所以使用 DOS命令要注意路徑。(可以通過 開始菜單 -> 所有程式 -> Microsoft Visual Studio 2005 ->Visual Studio Tools -> Visual Studio 2005 命令提示 來開啟使用 aspnet_regiis命令的 cmd 介面。)
步驟如下:
(1)在當前電腦上,匯出 RSA密鑰容器。(可以在匯出檔案前指定路徑。)
aspnet_regiis -px "NetFrameworkConfigurationKey" D:\RSAkeys.xml -pri
(2)在當前電腦上,用企業庫工具加密設定檔。ProtectedConfigurationProvider 只能選擇
RsaProtectedConfigurationProvider。
(3)在部署的電腦上, 匯入名為 "NetFrameworkConfigurationKey" 的 RSA 密鑰容器。
aspnet_regiis -pi "NetFrameworkConfigurationKey" D:\RSAkeys.xml
(1)和(2)可以在當前電腦直接操作,(3)的 DOS 命令可以在部署時由程式中完成。
這樣就完成了!
提示一下:有可能步驟(1)匯出失敗,它不能使用 -pri 這個參數,而這個參數是匯出解密使用的資訊!如果沒有它,在其他機器上只能加密新的東西。那麼解決方案如下:
(1)在當前電腦上, 建立新的 RSA 密鑰容器,它的名稱為: CustomKeys 。(這裡有個使用者層級和電腦層級的,下面的方式是建立電腦層級的,名字按照你的要求取)
aspnet_regiis -pc "CustomKeys" -exp
(2) 在當前電腦上, 向帳戶授予存取權限
aspnet_regiis -pa "CustomKeys" "NT AUTHORITY\NETWORK SERVICE"
其他需要存取權限的帳戶也應該被授權。
(3)在當前電腦上,匯出 RSA密鑰容器。(可以在匯出檔案前指定路徑。)
aspnet_regiis -px "CustomKeys" D:\RSAkeys.xml -pri
(4)在當前電腦上,將machine.config(在C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\CONFIG) 中keyContainerName = "NetFrameworkConfigurationKey" 替換為我們新建立的名稱:keyContainerName = "CustomKeys"。
(5)在當前電腦上,用企業庫工具加密設定檔。ProtectedConfigurationProvider只能選擇 RsaProtectedConfigurationProvider。
(6)在部署的電腦上, 匯入名為 "NetFrameworkConfigurationKey" 的 RSA 密鑰容器。
aspnet_regiis -pi "NetFrameworkConfigurationKey" D:\RSAkeys.xml
順便提一下刪除 RSA密鑰容器 的命令:
aspnet_regiis -pz "CustomKeys"
當你輸入一個錯誤的命令,會顯示協助命令資訊。另外提示: 以上命令如果使用複製的方式,一定要注意檢查底線和空格的位置!
針對第二個問題,提出兩種解決方案:
方案一:
前面提到過,可以使用先匯出key檔案,然後再匯入 key檔案的方法解決,僅解決開發人員之間的需要,卻不符合我們部署的實際需要!當然,如果你願意在部署的機器上安裝 Enterprise Library3.0,然後用它來重新設定加密檔案,是非常簡便的。下面提出符合實際部署需求的解決方案。
既然可以Enterprise Library 3.0通過手工的方式可以在部署的機器上匯入 key 檔案。這也意味我們可以通過編程的方式來部署的機器上匯入 key 檔案。因為手工匯入也要通過程式來實現的。
那麼,這部分代碼去哪裡找呢?Enterprise Library3.0不是提供了原始碼的嘛,而且包括組態工具的原始碼。在 C:\EntLib3Src\App Blocks 目錄(我的安裝目錄)下EnterpriseLibrary 解決方案件檔案,就是這個東西,我們可以從裡面尋找一些蛛絲馬跡。
這裡舉例只針對 symmetricCryptoProviders 部署 key 檔案!
一、部署的新的key 檔案步驟如下:
(1)建立一個新的 key 檔案。
//-- 產生新的 key 檔案
byte[] key = KeyManager.GenerateSymmetricKey(typeof(DESCryptoServiceProvider));
byte[] encryptedKey = ProtectedData.Protect(key, null, DataProtectionScope.CurrentUser);
Stream fs = null;
try
{
fs = new FileStream(keyFilePath, FileMode.Create);
KeyManager.Write(fs, encryptedKey, DataProtectionScope.CurrentUser);
fs.Flush();
}
finally
{
if (fs != null)
fs.Close();
}
(2)修改設定檔中 key 檔案的路徑。就是那個 protectedKeyFilename 屬性。
/**//// <summary>
/// 修改設定檔中 key 路徑,就是 protectedKeyFilename 的屬性值
/// </summary>
/// <param name="keyFilePath">自訂 key 檔案的路徑</param>
private void ModifProtectedKeyFilename(string keyFilePath)
{
//從組節點讀取
Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
if (config.HasFile)
{
ConfigurationSection configurationSection = config.Sections["securityCryptographyConfiguration"];
CryptographySettings cryptographySettings = configurationSection as CryptographySettings;
NameTypeConfigurationElementCollection<SymmetricProviderData,CustomSymmetricCryptoProviderData> elementCollection =cryptographySettings.SymmetricCryptoProviders;
SymmetricProviderData symmetricProviderData = elementCollection.Get("DESCryptoServiceProvider");
symmetricProviderData.ElementInformation.Properties["protectedKeyFilename"].Value = keyFilePath;
config.Save(ConfigurationSaveMode.Minimal);
}
}
但是,有人會問,用匯出的密鑰檔案可以恢複開發時的 key 檔案嗎? 雖然建立一個新的 key檔案的方式可以解決部署問題,但是就想使用通過編程的方式來恢複開發時的 key檔案,比如我,就認這個死理。因為在部署前,我已經有了一些資料用這個 key加密了!當然,可以把所以資料放在部署後來初始化加密!:)所以開了玩笑!只是想開闊一下思路!進入正題:
二、恢複 key 檔案步驟如下:
(1)恢複那個 key 檔案。
//-- 恢複 key 檔案
Stream fs = null;
//分發密鑰檔案名稱:ExportKey.txt,密碼:123456
using (Stream fileOut = File.OpenRead("ExportKey.txt"))
{
ProtectedKey protectedKey = KeyManager.RestoreKey(fileOut, "123456", DataProtectionScope.LocalMachine);
try
{
fs = new FileStream(keyFilePath, FileMode.Create);
KeyManager.Write(fs, protectedKey.EncryptedKey, DataProtectionScope.CurrentUser);
fs.Flush();
}
finally
{
if (fs != null)
fs.Close();
}
}
(2)修改設定檔中 key 檔案的路徑。就是那個 protectedKeyFilename 屬性。
這個與(一)中的是一樣的!
總結解決這個方案,思想很簡單:建立一個新的或是恢複 key 檔案,這樣該 key 檔案與部署的機器關聯起來了,然後修改設定檔中 key 檔案的路徑。
我們現在來說說第二個解決方案!
方案二:
最初, 可能有這麼一個想法,如果產生一個 key 檔案,直接copy到任何電腦上都可以使用,那該多方便。下面,我就給出這種解決方案。
思想是:把隨機產生的密鑰放在 xml 檔案中,然後xml檔案通過嵌入的方式加入到程式集中,然後我們可以在一個通用加密解密類中通過反射的方式把密鑰從 xml檔案讀出來,然後用來加密和解密。另外,我們可以通過一般的可逆加密方式對放在xml檔案中的密鑰進行加密解密。但是強調的是,這個方式的安全性不如上面的,畢竟放在 xml檔案的密鑰有被破解的風險。當然,這種方式在安全性要求不是特別高的系統還是可以採用的。
//-----------------------------------------------------------------------------------------
// 模組編號:
// 檔案名稱: CustomCryptography.cs
// 描述: CustomCryptography 資料對稱式加密解密類
// 作者:ChenJie
// 編寫日期:2007-5-30
// Copyright 2007
//-----------------------------------------------------------------------------------------
using System;
using System.Text;
using System.Reflection;
using System.IO;
using System.Xml;
using System.Security.Cryptography;
namespace CryptographyLib
{
/**//// <summary>
/// 自訂的資料對稱式加密解密類
/// </summary>
public class CustomCryptography : ICryptography
{
私人常量#region 私人常量
/**//// <summary>
/// 嵌入到本項目程式集中的 XML 檔案
/// </summary>
private const string KEY_FILE = "CryptographyLib.SystemKey.config";
#endregion
實現介面的方法#region 實現介面的方法
/**//// <summary>
/// 加密字串
/// </summary>
/// <param name="plainText">明文</param>
/// <returns>密文</returns>
public string EncryptData(string plainText)
{
string encryptData = string.Empty;
//建立一個新的 DES key.
DESCryptoServiceProvider key = new DESCryptoServiceProvider();
//設定資料加密標準 (DES) 演算法的機密密鑰和對稱演算法的初始化向量的值
byte[] Key = null;
byte[] IV = null;
GetKEYAndIV(out Key, out IV);
key.Key = Key;
key.IV = IV;
return EncryptData(plainText, key);
}
/**//// <summary>
/// 通過 DESCryptoServiceProvider 解密字串
/// </summary>
/// <param name="decryptedText">密文</param>
/// <returns>明文</returns>
public string DecryptData(string decryptedText)
{
string decryptData = string.Empty;
//建立一個新的 DES key.
DESCryptoServiceProvider key = new DESCryptoServiceProvider();
//設定資料加密標準 (DES) 演算法的機密密鑰和對稱演算法的初始化向量的值
byte[] Key = null;
byte[] IV = null;
GetKEYAndIV(out Key, out IV);
key.Key = Key;
key.IV = IV;
return DecryptData(decryptedText, key);
}
/**//// <summary>
/// 使用 MD5 加密字串
/// </summary>
/// <param name="plainText">明文</param>
/// <returns>密文</returns>
public string EncryptDataByHash(string plainText)
{
HashAlgorithm hashCryptoService = new SHA1Managed();
byte[] bytIn = UTF8Encoding.UTF8.GetBytes(plainText);
byte[] bytOut = hashCryptoService.ComputeHash(bytIn);
return Convert.ToBase64String(bytOut);
}
/**//// <summary>
/// 比較 MD5 加密字串
/// </summary>
/// <param name="plainText">明文</param>
/// <param name="hashedText">要比較的 hash 值</param>
/// <returns>是否相同,是則返回 true, 否則返回 false</returns>
public bool CompareHash(string plainText, string hashedText)
{
bool compare = false;
string encryptData = EncryptDataByHash(plainText);
compare = encryptData.Equals(hashedText);
return compare;
}
#endregion
私人方法#region 私人方法
/**//// <summary>
/// 加密字串
/// </summary>
/// <param name="plainText">明文</param>
/// <param name="key">對稱演算法的的抽象基類</param>
/// <returns>密文</returns>
private string EncryptData(string plainText, SymmetricAlgorithm key)
{
string encryptData = string.Empty;
byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
/**/////---------- 方法一 ----------------------------
////建立一個 MemoryStream 對象
//MemoryStream ms = new MemoryStream();
/**///// 建立一個加密流
//CryptoStream encStream = new CryptoStream(ms, key.CreateEncryptor(), CryptoStreamMode.Write);
/**///// 建立一個 StreamWriter 對象
//StreamWriter sw = new StreamWriter(encStream);
//sw.WriteLine(plainText);
//sw.Close();
//encryptData = Convert.ToBase64String(ms.ToArray());
//ms.Close();
/**/////---------- 方法一 結束 ----------------------------
//---------- 方法二 ----------------------------
MemoryStream ms = new MemoryStream();
CryptoStream cs = new CryptoStream(ms, key.CreateEncryptor(), CryptoStreamMode.Write);
cs.Write(plainTextBytes, 0, plainTextBytes.Length);
cs.FlushFinalBlock();
encryptData = Convert.ToBase64String(ms.ToArray());
//---------- 方法二 結束 ----------------------------
//清空數組中的內容
Array.Clear(plainTextBytes, 0, plainTextBytes.Length);
return encryptData;
}
/**//// <summary>
/// 解密字串
/// </summary>
/// <param name="decryptedText">密文</param>
/// <param name="key">對稱演算法的的抽象基類</param>
/// <returns>明文</returns>
private string DecryptData(string decryptedText, SymmetricAlgorithm key)
{
string decryptData = string.Empty;
byte[] decryptedByteArray = Convert.FromBase64String(decryptedText);
/**/////---------- 方法一 ----------------------------
////為解密字串建立一個 MemoryStream 對象
//MemoryStream ms = new MemoryStream(decryptedByteArray);
/**/////建立一個 CryptoStream 對象
//CryptoStream encStream = new CryptoStream(ms, key.CreateDecryptor(), CryptoStreamMode.Read);
/**///// 建立一個 StreamReader 對象
//StreamReader sr = new StreamReader(encStream);
//decryptData = sr.ReadLine();
//sr.Close();
//encStream.Close();
//ms.Close();
/**/////---------- 方法一 結束 ----------------------------