外业记录:在Windows Azure应用程序中使用基于证书的加密


这是一系列文章中首次集中讨论关于从Windows Azure社区分享“现实世界”技术信息。该文章的作者是Walter Myers III他是微软咨询服务的资深顾问。

问题

我浏览过各种Windows Azure相关的帖子,在这些帖子中开发人员选择了使用加密和解锁数据的对称密钥方案。一个重要的情形是,当开发者需要在SQL Azure中存储加密数据时,它将在一个Windows Azure应用程序中被解密,从而呈现给用户。另一种是数据同步的情形,非云端数据必须与SQL Azure里的数据保持同步,当部署到Windows Azure时这些数据就会被加密。

开发人员可能会将加密密钥作为一个blob存储在Windows Azure里,只要存储密钥所涉及的Windows Azure存储是安全的,这种方式就是安全的;但是这不是最佳的做法,因为开发人员必须访问对称密钥,这可能在非云端不知不觉地泄露了对称密钥。此外,如果Windows Azure 应用程序被泄露,那么有可能密钥就被泄露了。本文提供了Windows Azure应用程序的基于证书的数据加密和解密的模型和代码。

解决方案

首先,让我提供一些背景。使用一个基于证书(不对称的密钥)的方法,最好的做法是遵循用来保护私钥的“关注分离”协议。这样,它将通过Windows Azure应用程序负责任何带有私钥并被作为服务证书上传到Windows Azure Management Portal,以供使用(Windows Azure应用程序可用的服务证书必须被上传到相应的托管服务)。开发人员被提供一个公钥,这个公钥只能在应用程序部署时用在开发机器上。当在开发fabric里测试的时候,开发人员必须使用一个他们利用IIS7通过自我认证而创建的证书。在部署时,他们将做一个简单的替换,用上传到Windows Azure的服务证书替换他们加密/解密代码中的thumbprint,并且利用他们的应用程序来部署服务证书的公钥。

开发人员必须在他们的应用程序上部署公钥,当Windows Azure调用角色示例,它将使服务定义里的thumbprint与上传的服务证书匹配并将私钥部署到角色示例。私钥是有意设成的不可导出的.pfx格式,所以你不能通过RDC连接到一个角色实例来获取私钥。

解决方案的实现

到目前为止,我们讨论了一些理论,现在让我们来看看这些概念具体是怎样表现的。注意此解决方案使用了Visual Studio中为证书管理提供的功能。

如果你尚未准备好,那就现在开始吧,将公钥证书安装到你的个人证书存储区。使用Local Computer而不是使用Current User存储区,因此你的代码应该与将要部署证书的Windows Azure的代码 一致。注意,为了看见证书,你不能只是启动certmgr.msc,因为它会带你来到Current User存储区。你还要启动mmc.exe 并选择File| Add/Remove Snap-In…菜单选项,添加证书,如下面的截图所示。

因此你的证书控制台应该和下图类似:

在部署应用程序之前,先让我们看一看它在Visual Studio 2010所展现的样子,然后看看在Windows Azure角色实例中证书控制台是什么样的。下面的截图来自于我的web角色里的Properties页,并让Certificates标签处在选择的状态。我在该截图上为Certificates添加了高亮文本并将其重新命名为EncryptDecrypt。注意存储单元是LocalMachine,存储名称是My,这就是我们想要的。

一旦你在这里添加了证书,你马上可以进入到ServiceDefinition.csdef文件,类似于下图。你在ServiceDefinition.csdef里还会发现与thumbprint 一起的一个entry。

部署应用程序之后,你可以为任意实例建立一个Remote Desktop Connection(RTC)(假设你在发布应用程序时已经配置了RDC)。以上面所示的相同的方式,启动mmc.exe 并为Local Computer 和 Current User添加 Certificates snap-ins。你的RDC窗口应类似于下图。

请注意证书已经安装到Local Computer个人证书存储区,但是没有一个已安装到Current User个人存储区。这是将上传服务证书到托管服务与配置角色(它是Windows Azure需要在证书存储区安装证书的原因)证书结合到一起。现在,如果你右击certificate并试图导出,就像上面所讨论的那样,你将会看到私钥是不可导出的,这是期望中的事情,如下图所示。

因此现在我们知道了即将用来加密/解密的证书是如何处理的。下一步,让我们看一看加密/解密的例程。

public static class X509CertificateHelper
{
    public static X509Certificate2 LoadCertificate(StoreName storeName, StoreLocation
    storeLocation, string thumbprint)
    {
        // The following code gets the cert from the keystore
        X509Store store = new X509Store(storeName, storeLocation);
        store.Open(OpenFlags.ReadOnly);
        X509Certificate2Collection certCollection =
                 store.Certificates.Find(X509FindType.FindByThumbprint,
                 thumbprint, false);
        X509Certificate2Enumerator enumerator = certCollection.GetEnumerator();
        X509Certificate2 cert = null;
        while (enumerator.MoveNext())
        {
            cert = enumerator.Current;
        }
        return cert;
    }
 
    public static byte[] Encrypt(byte[] plainData, bool fOAEP,
             X509Certificate2 certificate)
    {
        if (plainData == null)
        {
            throw new ArgumentNullException("plainData");
        }
 
        if (certificate == null)
        {
            throw new ArgumentNullException("certificate");
        }
 
        using (RSACryptoServiceProvider provider = new RSACryptoServiceProvider())
        {
            provider.FromXmlString(GetPublicKey(certificate));
            // We use the public key to encrypt.
            return provider.Encrypt(plainData, fOAEP);
        }
    }
 
    public static byte[] Decrypt(byte[] encryptedData, bool fOAEP,
             X509Certificate2 certificate)
    {
        if (encryptedData == null)
        {
            throw new ArgumentNullException("encryptedData");
        }
 
        if (certificate == null)
        {
            throw new ArgumentNullException("certificate");
        }
 
        using (RSACryptoServiceProvider provider = (RSACryptoServiceProvider)
certificate.PrivateKey)
        {
            // We use the private key to decrypt.
            return provider.Decrypt(encryptedData, fOAEP);
        }
    }
 
    public static string GetPublicKey(X509Certificate2 certificate)
    {
        if (certificate == null)
        {
            throw new ArgumentNullException("certificate");
        }
        return certificate.PublicKey.Key.ToXmlString(false);
    }
 
    public static string GetXmlKeyPair(X509Certificate2 certificate)
    {
        if (certificate == null)
        {
            throw new ArgumentNullException("certificate");
        }
 
        if (!certificate.HasPrivateKey)
        {
            throw new ArgumentException("certificate does not have a PK");
        }
        else
        {
            return certificate.PrivateKey.ToXmlString(true);
        }
    }
}

注意在上面的加密和解密例程中,我们需要为加密获取公钥但必须获取解密的私钥。这很重要,因为Public Key Infrastructure (PKI)使任何拥有公钥的人执行加密,但只有拥有私钥的人才有权限来解密加密字符串。一个显著的差异是,当我们获取密钥,我们可以将公钥导出到XML,如加密例程中所示的那样,但是我们在解密例程中不能将私钥导出到XML,因为证书是与私钥一起部署的,Windows Azure上私钥设为 non-exportable,这就是之前我们所讲到的。

让我们来看看我写的一段代码,使用上面的X509 encrypt/decrypt helper class来加密和解密一个字符串:

string myText = "Encrypt me.";
 
X509Certificate2 certificate = X509CertificateHelper.LoadCertificate( 
         StoreName.My,
         StoreLocation.LocalMachine,
        "D3E6F7F969546ED620A255794CAB31D8C07E9F31"); 
if (certificate == null) 
{
         Response.Write("Certificate is null.");
         return;
} 
byte[] encoded = System.Text.UTF8Encoding.UTF8.GetBytes(myText)
byte[] encrypted; 
byte[] decrypted;
try
{
         encrypted = X509CertificateHelper.Encrypt(encoded, true, certificate); 
} 
catch (Exception ee) 
{ 
         Response.Write("Encrypt failed with error: " + ee.Message + "<br>"); 
         return;
}
try
{
         decrypted = X509CertificateHelper.Decrypt(encrypted, true, certificate);
}
catch (Exception ed) 
{ 
         Response.Write("Decrypt failed with error: " + ed.Message + "<br>"); 
         return;
}

这样,在上面的代码中我装载了我的证书,使用个人存储区放在本地计算机上。X509 encrypt/decrypt class 的LoadCertificate方法的最后一个参数保存了角色的property页上Certificates标签里获取的thumbprint。作为练习,你可以编写一些代码从ServiceConfiguration.cscfg文件中检索此字符串。

参考文献: http://www.josefcobonnin.com/post/2007/02/20/Encrypting-with-Certificates.aspx

本文翻译自:http://blogs.msdn.com/b/windowsazure/archive/2011/09/07/field-note-using-certificate-based-encryption-in-windows-azure-applications.aspx

Comments (0)

Skip to main content