|
Beginning with Windows 2000, the operating system began to provide a
data protection application-programming interface called the Data Protection
API (DPAPI). DPAPI is a very simple API consisting of only a pair of function
calls that provide OS-level data protection services to user and system
processes. Since the data protection is part of the OS, every application
can secure data without needing any specific cryptographic code other
than the necessary function calls to DPAPI. These calls are two simple
functions with various options to modify DPAPI behavior. Overall, DPAPI
is a very easy-to-use service that will benefit developers that must provide
protection for sensitive application data, such as passwords and private
keys.
DPAPI is a password-based data protection service: it requires a password
to provide protection. The drawback, of course, is that all protection
provided by DPAPI rests on the password provided. This is offset by DPAPI
using proven cryptographic routines, specifically the strong Triple-DES
algorithm, and strong keys. Since DPAPI is focused on providing protection
for users and requires a password to provide this protection, it logically
uses the user's logon password for protection.
DPAPI Architecture
The public DPAPI interfaces are part of crypt32.dll and are available
for any user process that has loaded it. This DLL is part of CryptoAPI;
application developers can assume that all Windows systems have this DLL
available.
Applications either pass plaintext data to DPAPI and receive an opaque
protected data blob back, or pass the protected data blob to DPAPI and
receive the plaintext data back.
The protected data blob is an opaque structure, because in addition to
the encrypted data, it also contains data to allow DPAPI to unprotect
it. Since it is opaque, developers do not need to parse or understand
the format at all. An important point to remember is that DPAPI only applies
cryptographic protection to the data. It does not store any of the protected
data; applications calling DPAPI must implement their own storage. In
this example I'll show how the Isolated Storage classes can be combined
with the calls to DPAPI to provide an easy - to - use, robust encrypted
local data store.
When an application calls one of the DPAPI functions, the functions make
a local RPC call to the Local Security Authority (LSA). The LSA is a system
process that starts on boot and runs until the computer is shut down.
These local RPC calls never traverse the network, so all data remains
on the local machine. The endpoints of these RPC calls then call DPAPI
private functions to protect or unprotect the data. These functions then
call back into CryptoAPI, via crypt32.dll, for the actual encryption or
decryption of the data in the security context of the LSA. The functions
run in the security context of the LSA so that security audits can be
generated.
The LSA also contains system-level functions for using DPAPI. These functions
are available to any thread running inside the LSA and only to those threads.
This is because the functions provide additional options for LSA threads,
specifically to allow LSA threads to protect user data that cannot be
unprotected by non-LSA threads using DPAPI such as user applications.
Keys and Passwords in DPAPI
DPAPI is focused on providing data protection for users. Since it requires
a password to provide protection, the logical step is for DPAPI to use
a user's logon password. Actually, DPAPI uses the user's logon credential.
In a typical system, in which the user logs on with a password, the logon
credential is simply a hash of the user's password. In a system in which
the user logs on with a smart card, however, the credential would be different.
A small drawback to using the logon password is that all applications
running under the same user can access any protected data that they know
about. Since applications must store their own protected data,
gaining access to the data could be somewhat difficult for other applications,
but certainly not impossible. To counteract this, DPAPI allows an application
to use an additional secret when protecting data. This additional secret
is then required to unprotect the data.
Technically, this "secret" should be called secondary entropy.
It is secondary, because, while it doesn't strengthen the key used to
encrypt the data, it does increase the difficulty of one application,
running under the same user, to compromise another application's encryption
key. Applications should be careful about how they use and store this
entropy. If it is simply saved to a file, unprotected, then adversaries
could access the entropy and use it to unprotect an application's data.
Additionally the application can pass in a data structure that will be
used by DPAPI to prompt the user. This "prompt structure" allows
the user to specify an additional password for this particular data. I
won't go into the details of using the prompt structure here, as we want
to develop a simple class library that can be used with ASP.NET to encrypt
passwords, database connection strings and similar information that can
then be safely stored in web.config. We don't want any "prompts"
popping up on our web server!
DPAPI initially generates a strong key called a MasterKey, which is protected
by the user's password. DPAPI uses a standard cryptographic process called
Password-Based Key Derivation, described in PKCS #5, to generate a key
from the password. This password-derived key is then used with Triple-DES
to encrypt the MasterKey, which is finally stored in the user's profile
directory.
Using DPAPI
There are two sets of interfaces to DPAPI, the user interfaces and the
system interfaces. There are also two data structures, the CRYPTPROTECT_PROMPTSTRUCT,
which is the "prompt structure" mentioned earlier, and the protected
data blob that holds the protected data.
The user interfaces contain the only two functions application developers
need to know to use DPAPI. The protect function: CryptProtectData()
and the unprotect function: CryptUnprotectData(). The
only requirement for applications to use these functions is to either
link with crypt32.lib or dynamically load crypt32.dll. The protect function
takes a plaintext data as input and returns an opaque protected data blob.
The unprotect function takes this opaque data blob and returns the plaintext
data.
In order to use the DPAPI in .NET code, we need to wrap the correct P/Invoke
calls as follows:
// CryptProtectData
[DllImport("crypt32",
CharSet=System.Runtime.InteropServices.CharSet.Unicode, SetLastError=true,
ExactSpelling=true)]
public static extern bool CryptProtectData
(
ref DATA_BLOB dataIn
, string szDataDescr
, ref DATA_BLOB optionalEntropy
, IntPtr pvReserved
, ref CRYPTPROTECT_PROMPTSTRUCT pPromptStruct
, int dwFlags
, ref DATA_BLOB pDataOut
);
// CryptUnprotectData
[DllImport("crypt32", CharSet=System.Runtime.InteropServices.CharSet.Unicode,
SetLastError=true, ExactSpelling=true)]
public static extern bool CryptUnprotectData
(
ref DATA_BLOB dataIn
, StringBuilder ppszDataDescr
, ref DATA_BLOB optionalEntropy
, IntPtr pvReserved
, ref CRYPTPROTECT_PROMPTSTRUCT pPromptStruct
, int dwFlags
, ref DATA_BLOB pDataOut
);
//
DataBlob struct
[StructLayout(LayoutKind.Sequential,
CharSet=CharSet.Unicode)]
public struct DATA_BLOB
{
public int cbData; // count of bytes
public IntPtr pbData; // pointer to block of data bytes
}
// CRYPTPROTECT_PROMPTSTRUCT
struct
[StructLayout(LayoutKind.Sequential,
CharSet=CharSet.Unicode)]
public struct CRYPTPROTECT_PROMPTSTRUCT
{
public int cbSize;
public int dwPromptFlags;
public IntPtr hwndApp;
public string szPrompt;
}
|
There are some constants and helper API calls that you'll find in the
downloadable solution file at the bottom of this article. There is also
a small collection of articles in MSDN online that go into more detail
on DPAPI. What I've done here is to simplify the use of DPAPI by accepting
the default store and setting up a utility class library that takes care
of all the Base64 encoding and decoding internally. In this manner, you
can, for example, use the library to handle encryption of sensitive database
connection strings or even user passwords for placement in the web.config
file for ASP.NET applications, and easily decode these with the optional
secret entropy "password" parameter. In addition, since the
DPAPI does not provide for storage of data, I've implemented a mechanism
to optionally use an Isolated storage file to securely store the encrypted
data and retrieve it on demand:
private void Encrypt_Click(object
sender, System.EventArgs e)
{
string strResult=String.Empty;
try
{
strResult=PAB.Security.DPAPI.ProtectData(SecretText.Text,EntropyText.Text);
CipherText.Text=strResult;
if (chkIsoStore.Checked)
{
IsolatedStorageFile isoStore = IsolatedStorageFile.GetStore(IsolatedStorageScope.User
| IsolatedStorageScope.Assembly, null, null);
writeToIsoFile(isoStore,strResult);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void writeToIsoFile(IsolatedStorageFile isoStore,string
strContent)
{
string[] fileNames = isoStore.GetFileNames("DPAPI.txt");
foreach (string file in fileNames)
{
if(file == "DPAPI.txt")
{
isoStore.DeleteFile("DPAPI.txt");
}
}
StreamWriter writer = null;
writer = new StreamWriter(new IsolatedStorageFileStream("DPAPI.txt",
FileMode.CreateNew,isoStore));
writer.Write( strContent);
writer.Close();
}
private string readFromIsoFile(IsolatedStorageFile
isoStore)
{
StreamReader reader = new StreamReader(new IsolatedStorageFileStream("DPAPI.txt",
FileMode.Open,isoStore));
String sb = reader.ReadToEnd();
reader.Close();
return sb.ToString();
}
private void Decrypt_Click(object sender, System.EventArgs e)
{
string strResult=String.Empty;
try
{
if(chkIsoStore.Checked)
{
IsolatedStorageFile isoStore = IsolatedStorageFile.GetStore(IsolatedStorageScope.User
| IsolatedStorageScope.Assembly, null, null);
strResult=readFromIsoFile(isoStore);
strResult=PAB.Security.DPAPI.UnProtectData(strResult,EntropyText.Text);
}
else
{
strResult=PAB.Security.DPAPI.UnProtectData(CipherText.Text,EntropyText.Text);
}
DecryptResults.Text = strResult;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
} |
The "front end" Winforms - based test application illistrates
how data can be encrypted, decryopted and optionally stored in IsolatedStorage:

If you have run this one time and encrypted some data with
the "Save / Retrieve" checkbox checked, then the next time you
run it you can retrieve and unprotect your data with the checkbox checked
again, loading it from the Isolated storage file by pressing the "Decrypt"
button.
Summary
While it is relatively easy to duplicate the functionality
of DPAPI in .NET with the use of the built - in .NET cryptographic classes,
DPAPI is a simple and reliable mechanism that is built into the OS for
secure encryption of sensitive data. By combining this with the use of
the .NET IsolatedStorage class, developers have a simple and effective
mechanism for data protection in the enterprise.
Download
the code that accompanies this article
| Peter Bromberg is a C# MVP, MCP, and .NET consultant who has worked in the banking and financial industry for 20 years. He has architected and developed web - based corporate distributed application solutions since 1995, and focuses exclusively on the .NET Platform. Pete's samples at GotDotNet.com have been downloaded over 41,000 times. You can read Peter's UnBlog Here. --><-- NOTE: Post QUESTIONS on FORUMS! |  |
|