中央密钥管理






4.63/5 (17投票s)
2006年3月8日
10分钟阅读

66696

1249
针对Web服务器群中多个Web服务器客户端的中央密钥管理器。
引言
我担心一些事情。例如,我从客户端收集敏感数据,并且我想对其进行加密。在我的网络中以明文形式传递这些数据,甚至可能将其存储在会话数据中,这让我感到不安。在分布式应用程序中保护数据非常困难,涉及几个障碍和问题。当我想要加密数据时,我遇到的一个常见障碍是Web服务器群。如果我在一个Web服务器上创建密钥,那么在将后续请求负载均衡到不同的服务器时就会遇到麻烦。问题是初始Web服务器包含加密密钥,而其他服务器在服务器群中无法访问该密钥。
下图显示了一个潜在的Web应用程序配置。客户端连接到Web服务器群中的一组Web服务器。Web服务器访问一组提供业务功能的内部Web服务。此外,Web服务器将长时间运行事务的会话数据存储在中央数据库中。数据在此配置中传递,并且该数据可能包含敏感信息。
我需要一个中央位置来创建和检索密钥。这个中央位置需要可以从Web服务器群中的任何服务器访问。它还需要使用良好的加密方案,因为以明文形式存储原始密钥不是一个好主意。此外,我希望使用证书,因为证书为我提供了一个与非对称密钥对相对应的名称。此代码示例创建了一个可用于创建和检索对称加密密钥的中央Web服务。
背景
SOAP消息和数据的加密最近受到了很多关注。WSE 3.0包含重要的基础设施来支持客户端和服务器之间签名和加密数据的能力。本示例不能替代任何这些功能。相反,它提出了一种将临时密钥存储在中央位置的方法。这个中央位置允许数据在一个服务器上加密并传递到不同的服务器。如果某个特定服务器需要数据,它可以检索会话密钥并解密数据,而不需要数据的服务器则可以将其保持加密状态。
密钥管理器的目的是提供一种简单的方法来生成和检索会话密钥。密钥必须以安全的方式存储和传输。由于密钥仅由内部客户端使用,因此我无需依赖标准化结构(例如PCKS)。相反,数据存储在XML文档中,并提供了一个核心库以方便序列化。
密钥管理器创建Rijndael 密钥,一种对称分组密码。选择Rijndael密钥是因为它们在加密和解密大量数据块方面的性能。密钥使用与X.509 证书关联的非对称密钥进行封装和解封装。使用X.509证书是因为它们为密钥对提供了一个可读的名称。密钥可以通过索引(或其他技术)存储,但证书允许围绕信任模型和身份验证进行未来的增强。当前示例未使用任何这些功能,而是仅依赖证书作为密钥的引用。
要求
KeyManager 演示有以下要求:
- Visual Studio 2005
- .NET 2.0
- SQL Server 2005(普通版或Express版)
安装
KeyManager 应用程序依赖于几个基础设施组件。特别是,以下组件必须进行配置:
- 客户端和服务器证书
- SQL Server 2005 数据库
这两个要求都可以使用Visual Studio 2005附带的标准工具进行配置。我列出了执行设置所需的步骤。
证书 – MakeCert.exe
本示例中使用的证书是使用Visual Studio 2005的一部分提供的MakeCert工具设置的。如果您打开Visual Studio 2005命令提示符,MakeCert工具是可用的。具体来说,在用户的个人(我的)存储中创建证书。以下行将起作用:
makecert -n "CN=KeyClient" -ss My
makecert -n "CN=KeyManager" -ss My
如果成功,将返回“Succeeded”。至少应设置两个证书,一个用于服务器,一个用于客户端。测试客户端和服务器可以设置在不同的机器上。
证书存储查看器
可以使用Internet Explorer查看可用的证书。有几种可用的工具(WSE有一个),但Internet Explorer几乎总是存在。它们可以通过选择“工具”->“Internet选项”菜单来查看。在“Internet选项”对话框中,“内容”选项卡下有一个“证书”按钮,可以打开“证书”对话框。执行上述语句后,证书应该出现在个人列表中。可以通过选择证书并单击“查看”来查找证书详细信息。在详细信息下,复制服务器KeyManager证书的“指纹”属性,并将其粘贴到KeyManager的Web.Config文件中,替换此处显示的当前设置:
<add key="WrappingCert" value="1a 7d 00 57 a6 27 37 77
aa ff 48 92 34 04 32 78 e2 a9 83 38"/>
SQL Server 2005 Express 版本
SQL Server 数据库用于存储封装的密钥。该数据库非常简单,包含一个表和两个存储过程。表和存储过程都是使用Visual Studio 2005设置的。存储过程和表布局如下所示:
CREATE TABLE [SessionKeyXml] (
[KeyId] [int] IDENTITY (1, 1) NOT NULL ,
[SessionKeyData] [Xml] NOT NULL
) ON [PRIMARY]
CREATE PROCEDURE dbo.GetSessionKey
(
@keyId int
)
AS
SELECT SessionKeyData
FROM SessionKeyXml
WHERE keyId = @keyId
CREATE PROCEDURE dbo.InsertSessionKey
(
@xml XML
)
AS
INSERT INTO SessionKeyXml(SessionKeyData)
VALUES (@xml)
SELECT @@IDENTITY
数据库配置完成后,必须将数据库连接字符串复制到KeyManager项目的web.config文件中。编辑以下行:
<connectionStrings>
<add name="Crypto" connectionString="YOUR CONNECTION STRING"/>
项目概述
提供的代码包含三个项目:
- 核心库
- KeyManager Web 服务
- TestClient 应用程序
核心库包含数据结构和加密辅助函数。它在KeyManager服务和TestClient之间共享。
核心库
核心库是一个通用库,提供客户端和服务器之间可共享的通用数据结构和通用算法。该库提供便利,允许客户端和服务器不重复代码。定义的主要数据结构如下:
/// WrappedData contains a piece of data that
/// has been encrypted. The clear text data was
/// encrypted using a temporary session key.
/// This temporary session key is encrypyted
/// with the requestor's public key.
/// This class provides a convienient way to pass
/// encrypted information along with
// the data needed to decrypt it by the requestor.
public class WrappedData :
System.Xml.Serialization.IXmlSerializable
{
// Certificate used to encrypt the data
string Certificate;
// The secret encrypted
// by the temporary Symmetric key
string EncryptedData;
// IV used in the symmetric key encryption
string IV;
// The encrypted temporary Symmetric key
string EncryptedKey;
}
// WrappedKey is an ecrypted session key.
// It contians the information needed to decrypt
// the key by the consumer.
public class SessionKeyInfo
{
// WrappedKey is an ecrypted session key.
WrappedData WrappedKey;
// The KeyId that can be used
// to retrieve the key from the KeyManager
int KeyId;
}
KeyManager Web 服务
KeyManager Web 服务是本项目的主要部分。它包含安全地创建、存储和导出会话密钥的逻辑。KeyManager Web 服务公开了两个方法:
public SessionKeyInfo GetSessionKey(int keyId, byte[] rawCertData)
public SessionKeyInfo CreateSessionKey(byte[] rawCertData)
这两种方法都采用二进制证书来封装会话密钥,以便安全传输。`CreateSessionKey` 执行以下逻辑:
- 创建一个新的会话密钥。
- 使用 KeyManager 证书封装会话密钥。
- 将封装的会话密钥存储在数据库中。
- 使用请求者的证书第二次封装会话密钥。
- 将封装的会话密钥返回给请求者。
`GetSessionKey` 执行以下逻辑:
- 从数据库中获取封装的会话密钥。
- 使用 KeyManager 的私钥解封装会话密钥。
- 使用请求者的证书封装会话密钥。
- 将封装的会话密钥返回给请求者。
测试客户端
Test Client 是一个 WinForms 应用程序,可用于测试 KeyManager 的功能。它允许用户:
- 选择要使用的客户端证书。
- 创建和/或获取会话密钥。
- 使用会话密钥加密和解密数据。
运行多个测试客户端将模拟不同的进程或机器加密和解密数据。请注意,测试客户端可以在与 Key Manager 不同的机器上运行,因为它使用 Web 服务进行通信,尽管 Key Manager 端点必须进行配置。
加密函数
此示例中执行的加密和解密是使用标准 .NET 2.0 库完成的。涉及的主要类有:
System.Security.Cryptography.X509Certificates.X509Store
System.Security.Cryptography.X509Certificates.X509Certificate2
System.Security.Cryptography.RSACryptoServiceProvider
System.Security.Cryptography.RijndaelManaged
有几个函数可以方便地使用证书、RSA非对称密钥和Rijndael对称密钥,这使得处理加密函数变得非常容易。此外,新的.NET 2.0新增功能包括几个新方法,使序列化变得非常简单。
证书
使用`X509Store`类从证书存储中检索证书。打开存储并允许枚举证书所需的只是以下几行代码:
System.Security.Cryptography.X509Certificates.X509Store
store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
本示例通过证书的`Thumbnail`属性(证书公开的属性)查找证书。使用通用名称也可以很容易地实现,但缩略图确保了证书的唯一性。
二进制序列化证书使用
byte[] data =
certificate.Export(X509ContentType.SerializedCert)
而二进制可以通过其中一个构造函数转换回证书
X509Certificate2 certificate =
new X509Certificate2(rawCertificateData);
密钥序列化
.NET 2.0 库包含一组新的“`ToXml`”和“`FromXml`”函数,这些函数确实使非对称密钥的序列化变得容易。从证书中获取公钥并创建`RSACryptoServiceProvider`只需以下一行代码:
csp.FromXmlString(certificate.PublicKey.Key.ToXmlString(false));
讽刺的是,会话密钥需要做更多的工作,因为密钥非常简单。由于不存在辅助函数来促进密钥的导出和导入,因此本示例使用简单的XML结构来存储会话密钥数据。XML包括以B64编码二进制形式存储的密钥数据和IV。RijndaelManaged 密钥是通过获取二进制数据并设置`Key`和`IV`属性生成的。
加密和解密
加密和解密使用封装数据方法。明文使用临时对称密钥加密。然后,临时对称密钥使用公共非对称密钥加密。客户端(并且只有原始客户端)可以解密临时对称密钥,因为它有权访问私有非对称密钥。以下代码显示了此过程:
public static WrappedData AsymmetricEncrypt(
RSACryptoServiceProvider csp,
string plainText)
{
// Generate the temporary Session Key
RijndaelManaged symmetric = new RijndaelManaged();
symmetric.GenerateIV();
symmetric.GenerateKey();
// encrypt the data using the temporary session Key
byte[] inputBuffer = Encoding.Unicode.GetBytes(plainText);
byte[] encryptedData =
symmetric.CreateEncryptor().TransformFinalBlock(
inputBuffer, 0, inputBuffer.Length);
// encrypt the session key using the Asymmetric CSP
byte[] encryptedKeyData = csp.Encrypt(symmetric.Key, true);
// Create the wrapped data structure
WrappedData wrappedData = new WrappedData();
wrappedData.EncryptedData =
Convert.ToBase64String(encryptedData);
wrappedData.EncryptedKey =
Convert.ToBase64String(encryptedKeyData);
wrappedData.IV = Convert.ToBase64String(symmetric.IV);
return wrappedData;
}
解密则正好相反,如下面代码所示:
public static string AsymmetricDecrypt(WrappedData wrappedData)
{
RSACryptoServiceProvider csp =
GetAsymmetricKey(wrappedData.Certificate, true);
byte[] clearKey = csp.Decrypt(Convert.FromBase64String(
wrappedData.EncryptedKey), true);
byte[] iv = Convert.FromBase64String(wrappedData.IV);
byte[] encryptedData = Convert.FromBase64String(
wrappedData.EncryptedData);
RijndaelManaged symmetric = new RijndaelManaged();
byte[] clearData = symmetric.CreateDecryptor(
clearKey, iv).TransformFinalBlock(encryptedData,
0, encryptedData.Length);
return Encoding.Unicode.GetString(clearData);
}
测试步骤
执行上述设置步骤后,可以使用测试客户端验证所有设置是否正确。此外,还可以查看执行操作所需的时间。如果测试客户端执行两次,则第一个实例可用于生成密钥并加密一些明文测试。加密文本可以通过剪贴板复制到第二个实例。第二个实例可以检索会话密钥并解密数据。本文开头的示例截图正是这样做的。
最终想法
KeyManager 身份验证
本示例中的证书仅用于将可读名称与非对称密钥对绑定。未来的增强功能将包括证书链和根证书信任,以限制允许的客户端。目前,任何客户端都可以访问存储的会话密钥。
密钥过期
会话数据库中的密钥目前未进行清理。加密数据可以作为长时间运行的会话的一部分存储,因此很难知道何时不再需要密钥。但是,由于数据应该是临时的,因此应该有某种类型的密钥过期机制。
多个 KeyManager 服务器
KeyManager 不能轻易部署到多个服务器,因为封装证书在每台机器上都不可用。要支持多个密钥服务器,必须在每个服务器上导入封装证书和密钥对。创建证书时,可以将其标记为“可导出”,从而允许将其从原始机器导出并导入到新机器。
问题
ASP Web 项目
我发现 Key Manager 服务在解密数据时会遇到问题,因为用于解密的 CSP 会不知何故进入错误状态。实际的错误是“OAEP 填充”失败,表明用于解密的密钥与用于加密的密钥不一致。对于生产代码,这需要进行一些调查,因为启动问题是一个麻烦,但对于示例,我只是重新构建了 web 项目并重新启动了它。一旦我这样做,一切就会奇迹般地开始工作(去琢磨吧)。