Using the DPAPI through ProtectedData Class in .Net Framework 2.0

Introduction:

The objective of this tutorial is to show how the DPAPI can be used to encrypt and decrypt data:

  1. Encrypt some data using ProtectedData Class in System.Security.Cryptography namespace and save it to a file.

  2. Show that the data can be decrypted using the same class but deferent method.

  3. Login as a different user, and show that the data cannot be decrypted.

Encrypting Data:

Here we will encrypt some data, and write it to a file.

  1. Open the attached project WriteSecretData which you will find in the WriteSecretData folder. This is a skeleton C# console application that you will use to encrypt data using the DPAPI support in .NET 2.0.

  2. Add using directives for System.Security.Cryptography and System.IO to the other usings at the top of the file:

    // Needed for encryption
    using System.Security.Cryptography;
    // Needed for file I/O
    using System.IO;

    Note you may need to add a reference to System.Security.dll, do that if needed.

  3. Note how the code asks for some code to encrypt, and a string to use as an extra encryption key (the 'entropy').

    If an entropy string isn't used, anyone logging in with the same user ID will be able to decrypt the secret data.

    At the first TODO comment, insert code to turn the strings into byte arrays:

    byte[] plainBytes = Encoding.Unicode.GetBytes(plainText);
    byte[] entropyBytes = Encoding.Unicode.GetBytes(entropyText);

  4. At the second TODO comment in the code, add a call to encrypt the data so that it can only be retrieved by the current user:

    byte[] encryptedBytes = ProtectedData.Protect(plainBytes, entropyBytes, DataProtectionScope.CurrentUser);

  5. The call to PrintBytes method prints the encrypted bytes as text, so that you can compare the effect of providing different entropy strings.

    The call to WriteBytesToFile method writes the encrypted bytes to a file called Secret.dat in a folder that all users can access.

    Compile and run the program, providing a suitable string as the secret data, and your first name as an entropy string.

Complete Code:

// Needed for encryption
using System.Security.Cryptography;
// Needed for file I/O
using System.IO;

class
Program
{

static void Main(string[] args)
{

// Get the text to encrypt
Console.Write("Text to encrypt: ");
String plainText = Console.ReadLine();

// Get a string to use as entropy
Console.Write("Enter your first name: ");
String entropyText = Console.ReadLine();

// TODO: Turn strings into byte arrays
byte[] plainBytes = Encoding.Unicode.GetBytes(plainText);
byte[] entropyBytes = Encoding.Unicode.GetBytes(entropyText);

// TODO: Encrypt the data
byte[] encryptedBytes = ProtectedData.Protect(plainBytes, entropyBytes, DataProtectionScope.CurrentUser);

// Print out the encoded data
PrintBytes(encryptedBytes);

// Save the bytes to a file
WriteBytesToFile(encryptedBytes);

Console.WriteLine("Hit Enter to continue...");
Console.ReadLine();

}

// Print an array of bytes in a nice way
static void PrintBytes(byte[] bytes)
{

int count = 0;
foreach (byte b in bytes)
{

Console.Write("{0:x2}, ", b);
if (++count % 5 == 0) Console.WriteLine();

}
Console.WriteLine();

}

// Write an array of bytes to a file
static void WriteBytesToFile(byte[] bytes)
{

string myDocs = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
string path = myDocs + @"\Secret.dat";

// Delete the file if it exists
if (File.Exists(path))
File.Delete(path);

//Create the file and write the bytes. Use of 'using' will close
// the stream at the end of the block

using (FileStream fs = File.Create(path))
{

fs.Write(bytes, 0, bytes.Length);

}

}

}

Decrypting Data

Here we will retrieve and decrypt the data that we stored in the Secret.dat file.

  1. Open the attached ReadSecretData project, which you will find in the ReadSecretData folder. This is another C# console application, in which you will try to decrypt the secret data stored in the data file.

  2. Add using directives for System.Security.Cryptography and System.IO to the other usings at the top of the file, as you did in the previous part of this tutorial. Try to examine the rest of the skeleton code, noting how the data is read back from the file.

  3. After the first TODO comment, add a try block, and arrange for it to catch CryptographicException and Exception:

    try
    {

    }
    catch(CryptographicException ce)
    {
    Console.WriteLine("CryptographicException: {0}", ce.Message);

    }
    catch(Exception e)
    {

    Console.WriteLine("Exception: {0}", e.Message);

    }

  4. Inside the try block, add code to decrypt the data and print it out:

    byte[] decryptedData = ProtectedData.Unprotect( encryptedData, entropyBytes, DataProtectionScope.CurrentUser);
    Console.WriteLine(Encoding.Unicode.GetString(decryptedData));

  5. Build and run the program, making sure that you enter your first name exactly the same as you did when encrypting. You should see the original text displayed when the bytes are decoded.

  6. Run the program again, and this time give a different name.

    A CryptographicException will be thrown, showing that the decryption failed.

Complete Code:

class Program
{

static void Main(string[] args)
{

// Get the entropy value and turn it into bytes
Console.Write("Enter your first name: ");
String entropyText = Console.ReadLine();
byte[] entropyBytes = Encoding.Unicode.GetBytes(entropyText);

// Read the secret data from the file
byte[] encryptedData = ReadBytesFromFile();
Console.WriteLine("Length: {0}", encryptedData.Length);
// Print it out
PrintBytes(encryptedData);

// TODO: Decrypt the data
try
{

byte[] decryptedData = ProtectedData.Unprotect(encryptedData, entropyBytes, DataProtectionScope.CurrentUser);
Console.WriteLine(Encoding.Unicode.GetString(decryptedData));

}
catch (CryptographicException ce)
{

Console.WriteLine("CryptographicException: {0}", ce.Message);

}
catch (Exception e)
{

Console.WriteLine("Exception: {0}", e.Message);

}
Console.WriteLine("Hit Enter to continue...");
Console.ReadLine();

}

// Print an array of bytes in a nice way
static void PrintBytes(byte[] bytes)
{

int count = 0;
foreach (byte b in bytes)
{

Console.Write("{0:x2}, ", b);
if (++count % 5 == 0) Console.WriteLine();

}
Console.WriteLine();

}

// Reads an array of bytes from a file
static byte[] ReadBytesFromFile()
{

string myDocs = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
string path = myDocs + @"\Secret.dat";

// Delete the file if it exists
if (!File.Exists(path))
{

Console.WriteLine("Error: input file not found");
Environment.Exit(1);

}

//Create the file and write the bytes. Use of 'using' will close
// the stream at the end of the block

int nRead = 0;
byte[] b = new byte[1024];
using (FileStream fs = File.OpenRead(path))
{

nRead = fs.Read(b, 0, b.Length);

}

if (nRead > 0)
{

byte[] b2 = new byte[nRead];
Array.Copy(b, b2, nRead);
return b2;

}
else

return null;

}

}


If you logged out of you of your PC, and then logged in with deferent user account, and run the decrypting application trying to decrypt Secret.dat previously created by the other user account, you will receive CryptographicException as you are not logged with the correct user.

Conclusion:

ProtectedData is a new class in .Net Framework 2.0 under System.Security.Cryptography. And it exposes DPAPI encryption functionality. In .Net Framewok 1.x we used to expose the unmanaged code to use DPAPI crypto services, but now .Net Framework 2.0 give it to us out of the box.

This class is useful to protect sensitive data, such us connection strings in configuration files. But in such a case, instead of using DataProtectionScope.CurrentUser as a third parameter to Protect or Unprotect methods, you will use  DataProtectionScope.LocalMachine for both methods.

Hope you enjoyed this basic tutorial.

Note: This tutorial based on one of the hands on labs held on the (MDC 2005), Microsoft Middle East Developers Conference in Cairo 2005


Similar Articles