Windows Data Protection API


Windows Data Protection API (DPAPI) appeared with the release of Windows 2000. It was obvious at the time that developers and users needed the ability to protect their data through the use of encryption. The DPAPI interface is extremely simple, and provides two functions for users: protect data and unprotect data.

Because of the way key-based encryption works, there needs to be some way to protect one user's data from another user. Because DPAPI is part of the operating system, and it requires a password in order to perform data protection operations, it only makes sense that it would use your Windows credential information.

This brings up another problem. There are cases where many different people use the same account to log in to a machine. In this case, data protection based on a user's credentials would be pretty meaninglesseveryone who logged in as that user would be able to unprotect that user's data. To deal with this, DPAPI enables you to provide an additional secret that is used in combination with the user's password to allow an application or a user to provide additional security on the data. The same additional secret used to protect the data is required to unprotect the data.

Another chapter could be written on the details on the DPAPI, but to keep it short, only one more additional detail will be mentioned before beginning a discussion of how to use DPAPI. As you now know, DPAPI data protection is based on user credentials. Through a fairly complicated process, encryption keys are rebuilt when users change their passwords and the data is reprotected so that when the user attempts to unprotect data, the correct password will be used. Older keys based on older user passwords are kept around in order to allow data to remain protected. As has already been said, it's a fairly complex process. Rather than burden you with all the details, you can be content in knowing that the operating system handles all the details associated with keeping DPAPI safe, backed up, reliable, and backwards compatible with user password changes.

The last point to be made before getting into the code is this: DPAPI provides only protection for your data; it does not provide a means to store protected data. You have to provide your own storage mechanism for protected and unprotected data. All of DPAPI's functions operate on in-memory data only.

Using DPAPI

The API for data protection is very simple. There are only two main things that you can do with it: protect data and unprotect data. The first method that you need to learn how to use is CryptProtectData. The DPAPI is not implemented in managed code, so you will need to use some P/Invoke conventions and marshalling to get the job done. Thankfully, the API is fairly simple and is pretty easy to wrap.

Here is the C++ prototype for the CryptProtectData function:

 BOOL WINAPI CryptProtectData (    DATA_BLOB                  *pDataIn,    LPCWSTR                     szDataDescr,    DATA_BLOB                  *pOptionalEntropy,    PVOID                        pvReserved,    CRYPTPROTECT_PROMPTSTRUCT   *pPromptStruct,    DWORD                        dwFlags,    DATA_BLOB                  *pDataOut) 

There are two structs in the preceding API call: DATA_BLOB, which contains the actual data to be protected, and the output return value of the function. The following is a description of the parameters of the method call:

  • pDataIn A pointer to a DATA_BLOB struct containing the plain text that is to be protected by encryption.

  • szDataDescr A string describing the data to be protected. This description is placed into the protected output blob. Optional.

  • pOptionalEntropy A pointer to an additional blob of data that will be used to add more randomness to the data, further separating it from data encrypted by the same Windows logon. Optional

  • pvReserved This will always be NULL.

  • pPromptStruct A pointer to a CRYPTPROTECT_PROMPTSTRUCT structure that is used to indicate where and when prompts occur, and the contents of those prompts. The CRYPTPROTECT_PROMPTSTRUCT is defined later.

  • dwFlags A list of possible flags for further configuration of data protection. Possible flags are as follows:

    CRYPTPROTECT_LOCALMACHINE Uses a machine key to encrypt.

    CRYPTPROTECT_UI_FORBIDDEN Cannot display UI during protection.

    CRYPTPROTECT_AUDIT Records an audit of protection.

    CRYPTPROTECT_CRED_SYNC Causes a re-encryption of master keys, more than likely after a password change.

    CRYPTPROTECT_SYSTEM Causes the call to fail.

  • pDataOut A pointer to a blob of data that contains the protected data.

Now the C++ prototype for CryptUnprotectData:

 BOOL WINAPI CryptUnprotectData (    DATA_BLOB                  *pDataIn,    LPCWSTR                     *ppszDataDescr,    DATA_BLOB                  *pOptionalEntropy,    PVOID                        pvReserved,    CRYPTPROTECT_PROMPTSTRUCT   *pPromptStruct,    DWORD                        dwFlags,    DATA_BLOB                  *pDataOut) 

You can probably guess what these parameters mean given the previous parameter description. If you need even more detail than what is described here, you can always consult the Windows Platform SDK documentation; these functions are part of the Windows 2000, Windows XP, and Windows Server 2003 operating systems.

The following is the C++ prototype for the CRYPTPROTECT_PROMPTSTRUCT structure. This structure gives the operating system information that allows it to prompt the user to configure the data protection that will take place, including the security level and the additional entropy password.

 typedef struct _CRYPTPROTECT_PROMPTSTRUCT {    DWORD      cbSize;    DWORD      dwPromptFlags;    HWND      hwndApp;    LPCWSTR   szPrompt; } CRYPTPROTECT_PROMPTSTRUCT, *PCRYPTPROTECT_PROMTPSTRUCT; 

The fields in this structure are as follows:

  • cbSize Contains the size of the structure in bytes

  • dwPromptFlags When used to encrypt data, any of the following flags may be passed:

    CRYPTPROTECT_PROMPT_ON_PROTECT Prompts the user for information when the data is protected

    CRYPTPROTECT_PROMPT_ON_UNPROTECT Prompts the user for information when the data is unprotected

    CRYPTPROTECT_STRONG Uses strong security by default

  • hwndApp Handle to the parent window

  • szPrompt String containing the text for the security prompt

Creating a DPAPI Wrapper

To create a DPAPI wrapper class, you must create definitions for all the unmanaged functions that you need to use. In addition, you'll have to define the data structures that you will use to pass data back and forth between the API.

To do this, create a simple Console Application and add a class called DPAPIWrapper to it. Inside the DPAPI namespace, outside the class wrapper, add the following structs:

 [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] public struct DATA_BLOB {   public int cbData;   public IntPtr pbData; } [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] public struct CRYPTPROTECT_PROMPTSTRUCT {   public int cbSize;   public int dwPromptFlags;   public IntPtr hwndApp;   public string szPrompt; } 

With these structs in hand, you can build a wrapper class that wraps around the functionality of the CryptProtectData and CryptUnprotectData functions available in crypt32.dll. You can also statically link to crypt32.lib if you are writing C++ or managed C++ code. Listing 35.6 shows the DPAPIWrapper class. The code in Listing 35.6 was inspired by an article you can find on MSDN entitled "How to Create a DPAPI Library" (msdn.microsoft.com/library/default.asp?url=/library/en-us/secmod/html/secmod21.asp). The code comes from the security patterns and practices recommendations. Because Microsoft recommends that you utilize DPAPI from .NET in this manner, code similar to the article is used in Listing 35.6.

Listing 35.6. The DPAPIWrapper Class
 using System; using System.Runtime.InteropServices; namespace DPAPI {   public enum EncryptionKeyType   {     User = 1,     Machine   }   [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]   public struct DATA_BLOB   {     public int cbData;     public IntPtr pbData;   }   [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]   public struct CRYPTPROTECT_PROMPTSTRUCT   {     public int cbSize;     public int dwPromptFlags;     public IntPtr hwndApp;     public string szPrompt;   }   /// <summary>   /// Summary description for DPAPIWrapper.   /// </summary>   public class DPAPIWrapper   {     private const int CRYPTPROTECT_UI_FORBIDDEN  = 0x1;     private const int CRYPTPROTECT_LOCAL_MACHINE = 0x4;     [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,       ref string ppszDataDescr,       ref DATA_BLOB pOptionalEntropy,       IntPtr pvReserved,       ref CRYPTPROTECT_PROMPTSTRUCT pPromptStruct,       int dwFlags,       ref DATA_BLOB pDataOut);     private static CRYPTPROTECT_PROMPTSTRUCT CreateEmptyPromptStruct()     {        CRYPTPROTECT_PROMPTSTRUCT cpps = new CRYPTPROTECT_PROMPTSTRUCT();       cpps.cbSize = Marshal.SizeOf( typeof( CRYPTPROTECT_PROMPTSTRUCT) );       cpps.dwPromptFlags = 0;       cpps.hwndApp = IntPtr.Zero;       cpps.szPrompt = null;       return cpps;     }     private static DATA_BLOB InitBlobFromByteArray( byte[] data )     {       DATA_BLOB blob = new DATA_BLOB();       blob.pbData = Marshal.AllocHGlobal( data.Length );       if (blob.pbData == IntPtr.Zero)         throw new Exception("Could not allocate BLOB buffer.");       blob.cbData = data.Length;       Marshal.Copy(data, 0, blob.pbData, data.Length);       return blob;     }     public static string Protect(       string dataToProtect,       string entropy,       string dataDescription )     {       return Convert.ToBase64String(         Protect(           EncryptionKeyType.User,           System.Text.Encoding.UTF8.GetBytes( dataToProtect ),           System.Text.Encoding.UTF8.GetBytes( entropy ),           dataDescription ) );     }     // Start overloads of Protect     public static byte[] Protect(       EncryptionKeyType keyType,       byte[] inputData,       byte[] entropyData,       string dataDescription )     {       DATA_BLOB inputBlob = InitBlobFromByteArray( inputData );       DATA_BLOB entropyBlob = InitBlobFromByteArray( entropyData );       DATA_BLOB outputBlob = new DATA_BLOB();       try        {         CRYPTPROTECT_PROMPTSTRUCT prompt = CreateEmptyPromptStruct();         int dwFlags = CRYPTPROTECT_UI_FORBIDDEN;         if (keyType == EncryptionKeyType.Machine)           dwFlags |= CRYPTPROTECT_LOCAL_MACHINE;         // Finally, invoke DPAPI         bool result = CryptProtectData(           ref inputBlob,           dataDescription,           ref entropyBlob,           IntPtr.Zero,           ref prompt,           dwFlags,           ref outputBlob );        if (result == false)        {          throw new Exception("Failed to Protect Data.");        }        byte[] outputBytes = new byte[outputBlob.cbData];        Marshal.Copy( outputBlob.pbData,          outputBytes,          0,           outputBlob.cbData );        return outputBytes;     }     catch (Exception ex)     {       throw new Exception("DPAPI Failure: " + ex.ToString());     }     finally     {       if ( inputBlob.pbData != IntPtr.Zero)         Marshal.FreeHGlobal( inputBlob.pbData );       if ( entropyBlob.pbData != IntPtr.Zero )         Marshal.FreeHGlobal( entropyBlob.pbData );       if ( outputBlob.pbData != IntPtr.Zero )         Marshal.FreeHGlobal( outputBlob.pbData );     }   }   public static string Unprotect(     string protectedString,     string entropyData,     out string dataDescription )   {     return System.Text.Encoding.UTF8.GetString(       Unprotect( Convert.FromBase64String( protectedString ),       System.Text.Encoding.UTF8.GetBytes( entropyData ),        out dataDescription ) );   }   public static byte[] Unprotect(     byte[] protectedData,     byte[] entropyData,     out string dataDescription )   {     DATA_BLOB unprotectedBlob = new DATA_BLOB();     DATA_BLOB protectedBlob = InitBlobFromByteArray( protectedData );     DATA_BLOB entropyBlob = InitBlobFromByteArray( entropyData );     CRYPTPROTECT_PROMPTSTRUCT prompt = CreateEmptyPromptStruct();       dataDescription = string.Empty;     try     {        int dwFlags = CRYPTPROTECT_UI_FORBIDDEN;        bool result = CryptUnprotectData(        ref protectedBlob,        ref dataDescription,        ref entropyBlob,        IntPtr.Zero,        ref prompt,        dwFlags,        ref unprotectedBlob );        if (!result)          throw new Exception("CryptUnprotectData Failure.");        byte[] unprotectedBytes = new byte[ unprotectedBlob.cbData ];        Marshal.Copy(          unprotectedBlob.pbData, unprotectedBytes,          0, unprotectedBlob.cbData );        return unprotectedBytes;     }     catch (Exception ex)     {       throw new Exception("Failed to decrypt via DPAPI: " + ex.ToString());     }     finally     {       if (unprotectedBlob.pbData != IntPtr.Zero)         Marshal.FreeHGlobal( unprotectedBlob.pbData );       if (protectedBlob.pbData != IntPtr.Zero)         Marshal.FreeHGlobal( protectedBlob.pbData );       if (entropyBlob.pbData != IntPtr.Zero)         Marshal.FreeHGlobal( entropyBlob.pbData );     }   }  } } 

It might look like a lot of up-front work, but putting the effort up front gives you a very powerful, very reusable class that you can use in all of your applications that might need DPAPI support for data protection.

When dealing with the pbData field in the DATA_BLOB struct, it is important to remember that it is an allocated HGlobal. Without going too deep into the Win32 API or unmanaged C++, an HGlobal is a global handle that is allocated to an application by the operating system. When the application is done with that handle, it needs to make sure that it gets the operating system to release the handle; otherwise, there will be a slow leak in the application that could eventually cause a crash.

Protecting Data in .NET with DPAPI

Now that you have a reusable class that you can use to protect and unprotect data using DPAPI, look at the following few lines of code to see how incredibly easy it now is to use DPAPI in your application:

 [STAThread] static void Main(string[] args) {   string dataToProtect = "The quick brown fox got ran over by a reindeer.";   string entropy = "Entropic Entropicality";   string encryptedData =      DPAPIWrapper.Protect( dataToProtect, entropy, "My Data" );     Console.WriteLine("Successfully protected the string : {0}\nNow encrypted into : {1}",     dataToProtect, encryptedData );   string dataDescription;   string decryptedData =      DPAPIWrapper.Unprotect( encryptedData, entropy, out dataDescription );   Console.WriteLine("Successfully unprotected the string : {0}\n" +     "Now descrypted into : {1}\nDescription : << {2} >>",     encryptedData, decryptedData, dataDescription );   Console.ReadLine(); } 

As you can see, it's pretty simple to make use of the new wrapper class. As you find new needs for the wrapper class and new needs for DPAPI interaction, you can simply add method overrides to support your required functionality. The output of the DPAPI protection sample is shown in Figure 35.1.

Figure 35.1. A console application using the newly created DPAPI wrapper class.




    Visual C#. NET 2003 Unleashed
    Visual C#. NET 2003 Unleashed
    ISBN: 672326760
    EAN: N/A
    Year: 2003
    Pages: 316

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net