最近要使用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;
}
}
}
在“生成”菜单中,单击“生成解决方案”。