Building Secure ASP.NET Applications: Authentication, Authorization, and Secure Communication
Retired Content |
---|
This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. |
How To: Create a DPAPI Library in .NET 1.1
J.D. Meier, Alex Mackman, Michael Dunner, and Srinath Vasireddy
Microsoft Corporation
Publisher: November 2002
Last Reviewed: January 2006
Applies to:
- .NET Framework 1.1
See the "patterns & practices Security Guidance for Applications Index" for links to additional security resources.
See the Landing Page for a starting point and complete overview of Building Secure ASP.NET Applications.
Summary: This How To shows you how to create a managed class library that exposes DPAPI functionality to applications that want to encrypt data, for example, database connection strings and account credentials. (11 pages)
Contents
Notes
Summary of Steps Step 1. Create a C# Class Library Step 2. Strong Name the Assembly (Optional)
Additional Resources
Web applications often need to store security sensitive data, such as database connection strings and service account credentials in application configuration files. For security reasons, this type of information should never be stored in plain text and should always be encrypted prior to storage.
This How To describes how to create a managed class library that encapsulates calls to the Data Protection API (DPAPI) to encrypt and decrypt data. This library can then be used from other managed applications such as ASP.NET Web applications, Web services and Enterprise Services applications.
For related How To articles that use the DPAPI library created in this article, see the following articles in the Reference section of this guide:
- How To: Use DPAPI (Machine Store) from ASP.NET 1.1
- How To: Use DPAPI (User Store) from ASP.NET 1.1 with Enterprise Services
Notes
Microsoft® Windows® 2000 operating system and later operating systems provide the Win32® Data Protection API (DPAPI) for encrypting and decrypting data.
DPAPI is part of the Cryptography API (Crypto API) and is implemented in crypt32.dll. It consists of two methods, CryptProtectData and CryptUnprotectData.
DPAPI is particularly useful in that it can eliminate the key management problem exposed to applications that use cryptography. While encryption ensures the data is secure, you must take additional steps to ensure the security of the key. DPAPI uses the password of the user account associated with the code that calls the DPAPI functions in order to derive the encryption key. As a result, the operating system (and not the application) manages the key.
DPAPI can work with either the machine store or user store (which requires a loaded user profile). DPAPI defaults to the user store, although you can specify that the machine store be used by passing the CRYPTPROTECT_LOCAL_MACHINE flag to the DPAPI functions.
The user profile approach affords an additional layer of security because it limits who can access the secret. Only the user who encrypts the data can decrypt the data. However, use of the user profile requires additional development effort when DPAPI is used from an ASP.NET Web application because you need to take explicit steps to load and unload a user profile (ASP.NET does not automatically load a user profile).
The machine store approach is easier to develop because it does not require user profile management. However, unless an additional entropy parameter is used, it is less secure because any user on the computer can decrypt data. (Entropy is a random value designed to make deciphering the secret more difficult). The problem with using an additional entropy parameter is that this must be securely stored by the application, which presents another key management issue.
Note If you use DPAPI with the machine store, the encrypted string is specific to a given computer and therefore you must generate the encrypted data on every computer. Do not copy the encrypted data across computers in a farm or cluster.
If you use DPAPI with the user store, you can decrypt the data on any computer with a roaming user profile.
Summary of Steps
This How To includes the following steps:
- Step 1. Create a C# Class Library
- Step 2. Strong Name the Assembly (Optional)
Step 1. Create a C# Class Library
This procedure creates a C# class library that exposes Encrypt and Decrypt methods. It encapsulates calls to the Win32 DPAPI functions.
To create a C# class library
Start Visual Studio .NET and create a new Visual C# Class Library project called DataProtection.
Use Solution Explorer to rename class1.cs as DataProtection.cs.
Within DataProtection.cs, rename class1 as DataProtector and rename the default constructor accordingly.
In Solution Explorer, right-click DataProtection, and then click Properties.
Click the ConfigurationProperties folder and set Allow unsafe code blocks to True.
Click OK to close the Properties dialog box.
Add the following using statements to the top of DataProtection.cs beneath the existing using statement.
using System.Text; using System.Runtime.InteropServices;
Add the following DllImport statements to the top of the DataProtector class to allow the Win32 DPAPI functions together with the FormatMessage utility function to be called through P/Invoke.
[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);
Add the following structure definitions and constants used by the DPAPI functions.
[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;
Add a public enumerated type called Store to the class. This is used to indicate whether DPAPI should be used in conjunction with the machine or user stores.
public enum Store {USE_MACHINE_STORE = 1, USE_USER_STORE};
Add a private member variable of type Store to the class.
private Store store;
Replace the class' default constructor with the following constructor that accepts a Store parameter and places the supplied value in the store private member variable.
public DataProtector(Store tempStore) { store = tempStore; }
Add the following public Encrypt method to the class.
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())); } //Free the blob and entropy. if(IntPtr.Zero != plainTextBlob.pbData) { Marshal.FreeHGlobal(plainTextBlob.pbData); } if(IntPtr.Zero != entropyBlob.pbData) { Marshal.FreeHGlobal(entropyBlob.pbData); } } catch(Exception ex) { throw new Exception("Exception encrypting. " + ex.Message); } byte[] cipherText = new byte[cipherTextBlob.cbData]; Marshal.Copy(cipherTextBlob.pbData, cipherText, 0, cipherTextBlob.cbData); Marshal.FreeHGlobal(cipherTextBlob.pbData); return cipherText; }
Add the following public Decrypt method to the class.
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); Marshal.FreeHGlobal(plainTextBlob.pbData); return plainText; }
Add the following private helper methods to the class.
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; }
On the Build menu, click BuildSolution.
Step 2. Strong Name the Assembly (Optional)
If the managed DPAPI class library is to be called by an Enterprise Services application (which must be strong named), then the DPAPI class library must also be strong named. This procedure creates a strong name for the class library.
If the managed DPAPI class library is to be called directly from an ASP.NET Web application (which is not strong named), you can skip this procedure.
To strong name the assembly
Open a command window and change directory to the DataProtection project folder.
Use the sn.exe utility to generate a key pair used to sign the assembly.
sn -k dataprotection.snk
Return to Visual Studio .NET and open Assemblyinfo.cs.
Locate the AssemblyKeyFile attribute and add a path to the key file within the project folder.
[assembly: AssemblyKeyFile(@"..\..\dataprotection.snk")]
On the Build menu, click BuildSolution.
Additional Resources
For more information, see the following related How Tos:
- How To: Use DPAPI (Machine Store) from ASP.NET 1.1 in the Reference section of this guide
- How To: Use DPAPI (User Store) from ASP.NET 1.1 with Enterprise Services in the Reference section of this guide
Retired Content |
---|
This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. |