加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

c# 创建 DPAPI 库(一)

(2014-12-10 16:16:55)
标签:

股票




最近要使用DPAPI类库,网上找了资料,但是写的都不是很全。整理了一下。
目标
本章的目标是:

创建一个使用 DPAPI 来加密和解密数据的托管库。


适用范围
本章适用于以下产品和技术:

Microsoft® Windows® XP 或 Windows 2000 Server (Service Pack 3) 以及更高版本的操作系统

Microsoft 数据保护 API

Microsoft .NET Framework 版本 1.0 (Service Pack 2) 以及更高版本

Microsoft Visual C#® .NET


如何使用本章内容
本章详细介绍了使用 Visual C# 创建托管 DPAPI 库的步骤和所需的代码。若要学好本章内容:

您必须具有使用 Visual C# .NET 和 Microsoft Visual Studio® .NET 的经验。

请阅读 MSDN 文章“Windows Data Protection”(Windows 数据保护),网址为:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsecure/html/windataprotection-dpapi.asp。您可以通过其中的内容基本了解 DPAPI。

了解如何使用 P/Invoke 来调用非托管 DLL 中的函数;要使用 DPAPI,您必须执行此操作。有关详细信息,请在 .NET Framework 软件开发工具包 (SDK) 文档中搜索“platform invoke”(平台调用)。

阅读如何在 ASP.NET 中使用 DPAPI(计算机存储)。这一章提供了如何利用本章中创建的库通过计算机密钥存储来加密和解密数据的示例。

阅读如何在具有企业服务的 ASP.NET 中使用 DPAPI(用户存储)。这一章提供了如何利用本章中创建的库使用基于用户的密钥存储来加密和解密数据的示例。


摘要
Web 应用程序经常需要在应用程序配置文件中存储与安全性密切相关的数据,如数据库连接字符串和服务帐户凭据。出于安全性考虑,决不要以明文形式存储此类信息,而一定要在存储之前进行加密。

本章介绍如何创建一个托管类库,它用于封装对数据保护 API (DPAPI) 的调用以使用基于计算机或用户的密钥存储来加密和解密数据。可随后从其他托管应用程序使用该库,如 ASP.NET Web 应用程序、Web 服务以及企业服务应用程序。


您必须了解的背景知识
在开始学习本章之前,您应该知道:

Windows 2000 操作系统和更高版本的操作系统提供了用于加密和解密数据的 Win32® 数据保护 API (DPAPI)。

DPAPI 是加密 API (Crypto API) 的一部分并且是在 crypt32.dll 中实现的。它包含两个方法:CryptProtectData 和CryptUnprotectData。

DPAPI 特别有用,因为它能够消除使用密码的应用程序所带来的密钥管理问题。虽然加密能确保数据安全,但您必须采取额外的步骤来确保密钥的安全。DPAPI 使用与 DPAPI 函数的调用代码关联的用户帐户的密码,以便派生加密密钥。因此,是操作系统(而非应用程序)管理着密钥。

DPAPI 能够与计算机存储或用户存储(需要一个已加载的用户配置文件)配合使用。DPAPI 默认情况下用于用户存储,但您可以通过将 CRYPTPROTECT_LOCAL_MACHINE 标志传递给 DPAPI 函数来指定使用计算机存储。

这 种用户配置文件方式提供了一个额外的安全层,因为它限制了哪些用户能访问机密内容。只有加密该数据的用户才能解密该数据。但是,当通过 ASP.NET Web 应用程序使用 DPAPI 时,使用用户配置文件需要您执行额外的开发工作,因为您需要采取明确的步骤来加载和卸载用户配置文件(ASP.NET 不会自动加载用户配置文件)。

计 算机存储方式更容易开发,因为它不需要管理用户配置文件。但是,除非使用一个附加的熵参数,否则并不安全,因为该计算机的任何用户都可以解密数据。(熵是 一个设计用来使解密机密内容更为困难的随机值)。使用附加的熵参数出现的问题在于它必须由应用程序安全地存储起来,这带来了另一个密钥管理问题。

注意:如果您将 DPAPI 和计算机存储一起使用,那么加密字符串仅适用于给定的计算机,因此您必须在每台计算机上生成加密数据。不要在场或群集中将加密数据从一台计算机复制到另一台计算机。

如果将 DPAPI 和用户存储一起使用,则可以用一个漫游的用户配置文件在任何一台计算机上解密数据。


创建 Visual C# 类库
此过程创建一个 Visual C# 类库以公开 Encrypt 和 Decrypt 方法。它封装对 Win32 DPAPI 函数的调用。

创建 Visual C# 类库

1.启动 Visual Studio .NET,并创建一个名为 DataProtection 的新 Visual C# 类库项目。
2.使用解决方案资源管理器将 class1.cs 重命名为 DataProtection.cs。
3.在 DataProtection.cs 中,将 Class1 重命名为 DataProtector 并相应地重命名默认的构造函数。
4.在解决方案资源管理器中,右键单击“DataProtection”,然后单击“属性”。
5.编译不安全代码前要设置编译器选项,方法为:
vs2005及以上环境中,项目属性页-->选择“生成”选项-->勾选“允许不安全代码”项。
vs2003环境中,项目属性页-->配置属性-->生成-->允许不安全代码块属性改为true.
除了局部变量不能定义为unsafe以外,几乎所有的代码都可以。
类内容如下:

using System;

using System.Text;
using System.Runtime.InteropServices;


namespace DataProtection
{
    ///
    /// Summary description for DataProtector.
    ///
    public class DataProtector
    {
    
        [DllImport("Crypt32.dll", SetLastError=true,CharSet=System.Runtime.InteropServices.CharSet.Auto)] 
        private static extern bool CryptProtectData(ref DATA_BLOB pDataIn, 
            String szDataDescr, 
            ref DATA_BLOB pOptionalEntropy,
            IntPtr pvReserved, 
            ref CRYPTPROTECT_PROMPTSTRUCT pPromptStruct, 
            int dwFlags, 
            ref DATA_BLOB pDataOut);

        [DllImport("Crypt32.dll", SetLastError=true, 
             CharSet=System.Runtime.InteropServices.CharSet.Auto)]
        private static extern bool CryptUnprotectData(ref DATA_BLOB pDataIn, 
            String szDataDescr, 
            ref DATA_BLOB pOptionalEntropy, 
            IntPtr pvReserved, 
            ref CRYPTPROTECT_PROMPTSTRUCT pPromptStruct, 
            int dwFlags, 
            ref DATA_BLOB pDataOut);
        [DllImport("kernel32.dll", 
             CharSet=System.Runtime.InteropServices.CharSet.Auto)]
        private unsafe static extern int FormatMessage(int dwFlags, 
            ref IntPtr lpSource, 
            int dwMessageId,
            int dwLanguageId, 
            ref String lpBuffer, int nSize, 
            IntPtr *Arguments);

        [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
            internal struct DATA_BLOB
        {
            public int cbData;
            public IntPtr pbData;
        }

        [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
            internal struct CRYPTPROTECT_PROMPTSTRUCT
        {
            public int cbSize;
            public int dwPromptFlags;
            public IntPtr hwndApp;
            public String szPrompt;
        }
        static private IntPtr NullPtr = ((IntPtr)((int)(0)));
        private const int CRYPTPROTECT_UI_FORBIDDEN = 0x1;
        private const int CRYPTPROTECT_LOCAL_MACHINE = 0x4;


        public enum Store {USE_MACHINE_STORE = 1, USE_USER_STORE};


        private Store store;

        public DataProtector(Store tempStore)
        {
            store = tempStore;
        }

        public byte[] Encrypt(byte[] plainText, byte[] optionalEntropy)
        {
            bool retVal = false;

            DATA_BLOB plainTextBlob = new DATA_BLOB();
            DATA_BLOB cipherTextBlob = new DATA_BLOB();
            DATA_BLOB entropyBlob = new DATA_BLOB();

            CRYPTPROTECT_PROMPTSTRUCT prompt = new CRYPTPROTECT_PROMPTSTRUCT();
            InitPromptstruct(ref prompt);

            int dwFlags;
            try
            {
                try
                {
                    int bytesSize = plainText.Length;
                    plainTextBlob.pbData = Marshal.AllocHGlobal(bytesSize);
                    if(IntPtr.Zero == plainTextBlob.pbData)
                    {
                        throw new Exception("Unable to allocate plaintext buffer.");
                    }
                    plainTextBlob.cbData = bytesSize;
                    Marshal.Copy(plainText, 0, plainTextBlob.pbData, bytesSize);
                }
                catch(Exception ex)
                {
                    throw new Exception("Exception marshalling data. " + ex.Message);
                }
                if(Store.USE_MACHINE_STORE == store)
                {//Using the machine store, should be providing entropy.
                    dwFlags = CRYPTPROTECT_LOCAL_MACHINE|CRYPTPROTECT_UI_FORBIDDEN;
                    //Check to see if the entropy is null
                    if(null == optionalEntropy)
                    {//Allocate something
                        optionalEntropy = new byte[0];
                    }
                    try
                    {
                        int bytesSize = optionalEntropy.Length;
                        entropyBlob.pbData = Marshal.AllocHGlobal(optionalEntropy.Length);;
                        if(IntPtr.Zero == entropyBlob.pbData)
                        {
                            throw new Exception("Unable to allocate entropy data buffer.");
                        }
                        Marshal.Copy(optionalEntropy, 0, entropyBlob.pbData, bytesSize);
                        entropyBlob.cbData = bytesSize;
                    }
                    catch(Exception ex)
                    {
                        throw new Exception("Exception entropy marshalling data. " + 
                            ex.Message);
                    }
                }
                else
                {//Using the user store
                    dwFlags = CRYPTPROTECT_UI_FORBIDDEN;
                }
                retVal = CryptProtectData(ref plainTextBlob, "", ref entropyBlob, 
                    IntPtr.Zero, ref prompt, dwFlags, 
                    ref cipherTextBlob);
                if(false == retVal)
                {
                    throw new Exception("Encryption failed. " + 
                        GetErrorMessage(Marshal.GetLastWin32Error()));
                }
            }
            catch(Exception ex)
            {
                throw new Exception("Exception encrypting. " + ex.Message);
            }
            byte[] cipherText = new byte[cipherTextBlob.cbData];
            Marshal.Copy(cipherTextBlob.pbData, cipherText, 0, cipherTextBlob.cbData);
            return cipherText;
        }

        public byte[] Decrypt(byte[] cipherText, byte[] optionalEntropy)
        {
            bool retVal = false;
            DATA_BLOB plainTextBlob = new DATA_BLOB();
            DATA_BLOB cipherBlob = new DATA_BLOB();
            CRYPTPROTECT_PROMPTSTRUCT prompt = new CRYPTPROTECT_PROMPTSTRUCT();
            InitPromptstruct(ref prompt);
            try
            {
                try
                {
                    int cipherTextSize = cipherText.Length;
                    cipherBlob.pbData = Marshal.AllocHGlobal(cipherTextSize);
                    if(IntPtr.Zero == cipherBlob.pbData)
                    {
                        throw new Exception("Unable to allocate cipherText buffer.");
                    }
                    cipherBlob.cbData = cipherTextSize;  
                    Marshal.Copy(cipherText, 0, cipherBlob.pbData, cipherBlob.cbData);
                }
                catch(Exception ex)
                {
                    throw new Exception("Exception marshalling data. " + ex.Message);
                }
                DATA_BLOB entropyBlob = new DATA_BLOB();
                int dwFlags;
                if(Store.USE_MACHINE_STORE == store)
                {//Using the machine store, should be providing entropy.
                    dwFlags = CRYPTPROTECT_LOCAL_MACHINE|CRYPTPROTECT_UI_FORBIDDEN;
                    //Check to see if the entropy is null
                    if(null == optionalEntropy)
                    {//Allocate something
                        optionalEntropy = new byte[0];
                    }
                    try
                    {
                        int bytesSize = optionalEntropy.Length;
                        entropyBlob.pbData = Marshal.AllocHGlobal(bytesSize);
                        if(IntPtr.Zero == entropyBlob.pbData)
                        {
                            throw new Exception("Unable to allocate entropy buffer.");
                        }
                        entropyBlob.cbData = bytesSize;
                        Marshal.Copy(optionalEntropy, 0, entropyBlob.pbData, bytesSize);
                    }
                    catch(Exception ex)
                    {
                        throw new Exception("Exception entropy marshalling data. " + 
                            ex.Message);
                    }
                }
                else
                {//Using the user store
                    dwFlags = CRYPTPROTECT_UI_FORBIDDEN;
                }
                retVal = CryptUnprotectData(ref cipherBlob, null, ref entropyBlob, 
                    IntPtr.Zero, ref prompt, dwFlags, 
                    ref plainTextBlob);
                if(false == retVal)
                {
                    throw new Exception("Decryption failed. " + 
                        GetErrorMessage(Marshal.GetLastWin32Error()));
                }
                //Free the blob and entropy.
                if(IntPtr.Zero != cipherBlob.pbData)
                {
                    Marshal.FreeHGlobal(cipherBlob.pbData);
                }
                if(IntPtr.Zero != entropyBlob.pbData)
                {
                    Marshal.FreeHGlobal(entropyBlob.pbData);
                }
            }
            catch(Exception ex)
            {
                throw new Exception("Exception decrypting. " + ex.Message);
            }
            byte[] plainText = new byte[plainTextBlob.cbData];
            Marshal.Copy(plainTextBlob.pbData, plainText, 0, plainTextBlob.cbData);
            return plainText;
        }


        private void InitPromptstruct(ref CRYPTPROTECT_PROMPTSTRUCT ps) 
        {
            ps.cbSize = Marshal.SizeOf(typeof(CRYPTPROTECT_PROMPTSTRUCT));
            ps.dwPromptFlags = 0;
            ps.hwndApp = NullPtr;
            ps.szPrompt = null;
        }

        private unsafe static String GetErrorMessage(int errorCode)
        {
            int FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100;
            int FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
            int FORMAT_MESSAGE_FROM_SYSTEM  = 0x00001000;
            int messageSize = 255;
            String lpMsgBuf = "";
            int dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | 
                FORMAT_MESSAGE_IGNORE_INSERTS;
            IntPtr ptrlpSource = new IntPtr();
            IntPtr prtArguments = new IntPtr();
            int retVal = FormatMessage(dwFlags, ref ptrlpSource, errorCode, 0, 
                ref lpMsgBuf, messageSize, &prtArguments);
            if(0 == retVal)
            {
                throw new Exception("Failed to format message for error code " + 
                    errorCode + ". ");
            }
            return lpMsgBuf;
        }


    }
}

在“生成”菜单中,单击“生成解决方案”。

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有