未能使用提供程序 "RsaProtectedConfigurationProvider" 进行解密 的解决办法
未能使用提供程序 "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 would be fine, but I pulled this from the documentation within the section Managing and Distributing Keys which is pretty specific about the need for absolute paths .... The Cryptography Application Block stores each key 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)对数据进行加密和解密。
RsaProtectedConfigurationProvider。使用 RSA 加密算法对数据进行加密和解密。
这两个提供程序都提供对数据的强加密;但是,如果打算在多台计算机上使用同一个加密配置文件,则只有使用 RsaProtectedConfigurationProvider。因为这个才能导出用于对数据进行加密的加密密钥,并在另一台计算机上导入它们。
我们使用配置工具对配置文件的配置区进行加密时可以指定哪个 ProtectedConfigurationProvider。很显然,在解决这个问题上,我们只能使用RsaProtectedConfigurationProvider。由于不同的计算机上RSA密钥容器是各不相同的,所以我们需要从一台计算机上导出一个RSA 密钥容器,然后导入其他计算机上!由于导入方式是可以用 DOS 命令实现,所以在部署上不存在问题!另外,由于.NET Framework 有个默认的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 Library 3.0,然后用它来重新配置加密文件,是非常简便的。下面提出符合实际部署需求的解决方法。
既然可以Enterprise Library 3.0通过手工的方式可以在部署的机器上导入 key 文件。这也意味我们可以通过编程的方式来部署的机器上导入 key 文件。因为手工导入也要通过程序来实现的。
那么,这部分代码去哪里找呢?Enterprise Library 3.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();
/**/////---------- 方法一 结束 ----------------------------
//---------- 方法二 ---------------------------------
MemoryStream ms = new MemoryStream();
CryptoStream cs = new CryptoStream(ms, key.CreateDecryptor(), CryptoStreamMode.Write);
cs.Write(decryptedByteArray, 0, decryptedByteArray.Length);
cs.FlushFinalBlock();
decryptData = Encoding.UTF8.GetString(ms.ToArray());
//---------- 方法二 结束 ----------------------------
//清空数组中的内容
Array.Clear(decryptedByteArray, 0, decryptedByteArray.Length);
return decryptData;
}
/**//// <summary>
/// 产生对称算法的初始化向量(IV和数据加密标准 (DES) 算法的机密密钥(key)
/// </summary>
private void GenerateIVAndKey()
{
//创建一个新的 DES key.
DESCryptoServiceProvider key = new DESCryptoServiceProvider();
byte[] Key = key.Key;
byte[] IV = key.IV;
UnicodeEncoding converter = new UnicodeEncoding();
string KeyValue = Convert.ToBase64String(Key);
string IVValue = Convert.ToBase64String(IV);
}
/**//// <summary>
/// 获得数据加密标准 (DES) 算法的机密密钥和对称算法的初始化向量
/// </summary>
/// <param name="Key">数据加密标准 (DES) 算法的机密密钥</param>
/// <param name="IV">对称算法的初始化向量</param>
private void GetKEYAndIV(out byte[] Key, out byte[] IV)
{
Key = null;
IV = null;
using (Stream xmlInputStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(KEY_FILE))
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(xmlInputStream);
XmlNodeList nodeList = xmlDoc.SelectSingleNode("Root").ChildNodes;
foreach (XmlNode xn in nodeList)
{
if (xn.NodeType != XmlNodeType.Element)
{
continue;
}
switch (xn.Name)
{
case "IV":
IV = Convert.FromBase64String(xn.InnerXml);
break;
case "KEY":
Key = Convert.FromBase64String(xn.InnerXml);
break;
default:
break;
}
}
}
}
#endregion
}
}
XML文件就很简单了:
<?xml version="1.0" encoding="utf-8" ?>
<Root>
<IV>L7h6oi25zn4=</IV>
<KEY>vsbq4KMhR9I=</KEY>
</Root>
为什么 Enterprise Library 3.0 不把 key 文件弄成一个可以到处 copy 到任何计算机都能用的呢?David Hayden 说了这么一番话:企业库开发团队要求帮助使 key 文件安全可靠,因此使用了 DPAPI 加密了 key 文件,DPAPI 在不需要提供 key 文件就可以提供一种加密的方式,这是非常妙的。它的“坏处”就是它被限制在(机器的)当前用户或者(当前)机器的范围内。(The EntLib Team was required to help secure the key and hence used DPAPI to encrypt the key. DPAPI is great in that it is a way to provide encryption without requiring a key file. It is "bad" in that it is scoped to either the current user or machine. )
说来说去,使用 DPAPI 加密的文件限制了我们在任何地方 copy 被加密的文件,但是却是从安全性上考虑必须这样。
整个解决方案下载
全文结束!
参考资料:
(1)加密 web.config
(2)asp.net2.0中的数据保护
/************************************************/
本博客内容如果是原著都会在标题后加上(原著)字样,未加者多数为转载.
/************************************************/