Click here to Skip to main content
Click here to Skip to main content
Go to top

String Encryption using DPAPI and Extension Methods

, 15 May 2009
Rate this:
Please Sign up or sign in to vote.
Extension methods that encrypt / decrypt strings through the Windows Data Protection API (DPAPI) with optional usage of secure strings that protect data in memory.
The Windows Data Protection API (DPAPI) is a great technology to securely encrypt user or machine specific data without having to worry about an encryption key. Since .NET 2.0, DPAPI is part of the .NET framework, so encrypting data is as easy as this:

public static byte[] Encrypt(byte[] data)
{
  var scope = DataProtectionScope.CurrentUser;
  return ProtectedData.Protect(data, null, scope);
} 

As you can see, the Protect method of the ProtectedData class takes binary input and returns a byte array that contains the encrypted data. This means that you’ll have to do some conversions when dealing with strings, and the result of the encryption is a byte array anyway.

I recently published NetDrives, a tool that relies on the DPAPI to encrypt user passwords that are stored on disk in XML format. Accordingly, I didn’t want to deal with binary data at all: Both input and output were supposed to be strings, which why I came up with a few extension methods that nicely wrap string encryptions for me:

image

 

Basic String Encryption

In case in-memory protection is not an issue and you just need to encrypt/decrypt strings (e.g. to store encrypted data in a configuration file), you just need two extension methods. First, in order to encrypt a string, just invoke the Encrypt extension method:

string password = "hello world";
string encrypted = password.Encrypt(); 

Encrypt returns you the encrypted data, represented as base64 encoded string. In order to get your password back, just invoke the Decrypt extension method:

string plainText = encrypted.Decrypt(); 

Managed Strings vs. SecureString

The above methods are convenient to encrypt sensitive data that is supposed to be serialized or transmitted in any way. They do, however, not protect data at runtime as the decrypted strings remain in memory. In case this is an issue, you should revert to the SecureString rather than using plain strings (but keep in mind that this may lure you into a false sense of security!).

Accordingly, I also created extension methods that use SecureString instances rather than managed strings and allow you to wrap / unwrap strings quite easily. Here’s a test that shows off the various conversions:

Attention: Always keep in mind that once you are dealing with a managed string (such as the plainText variable below), your code can be compromised! Accordingly, the ToSecureString / Unwrap methods should be treated carefully.

[Test]
public void Encryption_And_Decryption_Cycle_Should_Return_Original_Value()
{
  string plainText = "this is a password";

  //encrypt plain text
  string cipher = plainText.Encrypt();
  Assert.AreNotEqual(plainText, cipher);

  //decrypt cipher into managed string
  string decrypted = cipher.Decrypt();
  Assert.AreEqual(plainText, decrypted);

  //create a SecureString from the plain text
  SecureString plainSecure = plainText.ToSecureString();

  //test unwrapping of a SecureString
  Assert.AreEqual(plainText, plainSecure.Unwrap());

  //encrypt the string that is wrapped into the SecureString
  string cipherFromSecure = plainSecure.Encrypt();

  //decrypt the cipher that was created from the the SecureString
  Assert.AreEqual(plainText, cipherFromSecure.Decrypt());
} 

Implementation

Here’s the class that provides the extension methods including a few helper methods that facilitate dealing with SecureString (e.g. SecureString.IsNullOrEmpty).

Note that you need to set an assembly reference to the System.Security assembly. Also keep in mind that the class always performs DPAPI encryption with user scope. You might want to provide some additional overloads in order to support encryption that uses the context of the machine rather than the user’s. The same goes for the optional entropy that is not used at here all for simplicity. 

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography;
using System.Text;

namespace Hardcodet.NetDrives.Platform
{
  /// <span class="code-SummaryComment"><summary></span>
  /// Provides extension methods that deal with
  /// string encryption/decryption and
  /// <span class="code-SummaryComment"><see cref="SecureString"/> encapsulation.</span>
  /// <span class="code-SummaryComment"></summary></span>
  public static class SecurityExtensions
  {
    /// <span class="code-SummaryComment"><summary></span>
    /// Specifies the data protection scope of the DPAPI.
    /// <span class="code-SummaryComment"></summary></span>
    private const DataProtectionScope Scope = DataProtectionScope.CurrentUser;

    /// <span class="code-SummaryComment"><summary></span>
    /// Encrypts a given password and returns the encrypted data
    /// as a base64 string.
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><param name="plainText">An unencrypted string that needs</span>
    /// to be secured.<span class="code-SummaryComment"></param></span>
    /// <span class="code-SummaryComment"><returns>A base64 encoded string that represents the encrypted</span>
    /// binary data.
    /// <span class="code-SummaryComment"></returns></span>
    /// <span class="code-SummaryComment"><remarks>This solution is not really secure as we are</span>
    /// keeping strings in memory. If runtime protection is essential,
    /// <span class="code-SummaryComment"><see cref="SecureString"/> should be used.</remarks></span>
    /// <span class="code-SummaryComment"><exception cref="ArgumentNullException">If <paramref name="plainText"/></span>
    /// is a null reference.<span class="code-SummaryComment"></exception></span>
    public static string Encrypt(this string plainText)
    {
      if (plainText == null) throw new ArgumentNullException("plainText");

      //encrypt data
      var data = Encoding.Unicode.GetBytes(plainText);
      byte[] encrypted = ProtectedData.Protect(data, null, Scope);

      //return as base64 string
      return Convert.ToBase64String(encrypted);
    }

    /// <span class="code-SummaryComment"><summary></span>
    /// Decrypts a given string.
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><param name="cipher">A base64 encoded string that was created</span>
    /// through the <span class="code-SummaryComment"><see cref="Encrypt(string)"/> or</span>
    /// <span class="code-SummaryComment"><see cref="Encrypt(SecureString)"/> extension methods.</param></span>
    /// <span class="code-SummaryComment"><returns>The decrypted string.</returns></span>
    /// <span class="code-SummaryComment"><remarks>Keep in mind that the decrypted string remains in memory</span>
    /// and makes your application vulnerable per se. If runtime protection
    /// is essential, <span class="code-SummaryComment"><see cref="SecureString"/> should be used.</remarks></span>
    /// <span class="code-SummaryComment"><exception cref="ArgumentNullException">If <paramref name="cipher"/></span>
    /// is a null reference.<span class="code-SummaryComment"></exception></span>
    public static string Decrypt(this string cipher)
    {
      if (cipher == null) throw new ArgumentNullException("cipher");

      //parse base64 string
      byte[] data = Convert.FromBase64String(cipher);

      //decrypt data
      byte[] decrypted = ProtectedData.Unprotect(data, null, Scope);
      return Encoding.Unicode.GetString(decrypted);
    }

    /// <span class="code-SummaryComment"><summary></span>
    /// Encrypts the contents of a secure string.
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><param name="value">An unencrypted string that needs</span>
    /// to be secured.<span class="code-SummaryComment"></param></span>
    /// <span class="code-SummaryComment"><returns>A base64 encoded string that represents the encrypted</span>
    /// binary data.
    /// <span class="code-SummaryComment"></returns></span>
    /// <span class="code-SummaryComment"><exception cref="ArgumentNullException">If <paramref name="value"/></span>
    /// is a null reference.<span class="code-SummaryComment"></exception></span>
    public static string Encrypt(this SecureString value)
    {
      if (value == null) throw new ArgumentNullException("value");

      IntPtr ptr = Marshal.SecureStringToCoTaskMemUnicode(value);
      try
      {
        char[] buffer = new char[value.Length];
        Marshal.Copy(ptr, buffer, 0, value.Length);

        byte[] data = Encoding.Unicode.GetBytes(buffer);
        byte[] encrypted = ProtectedData.Protect(data, null, Scope);

        //return as base64 string
        return Convert.ToBase64String(encrypted);
      }
      finally
      {
        Marshal.ZeroFreeCoTaskMemUnicode(ptr);
      }
    }

    /// <span class="code-SummaryComment"><summary></span>
    /// Decrypts a base64 encrypted string and returns the decrpyted data
    /// wrapped into a <span class="code-SummaryComment"><see cref="SecureString"/> instance.</span>
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><param name="cipher">A base64 encoded string that was created</span>
    /// through the <span class="code-SummaryComment"><see cref="Encrypt(string)"/> or</span>
    /// <span class="code-SummaryComment"><see cref="Encrypt(SecureString)"/> extension methods.</param></span>
    /// <span class="code-SummaryComment"><returns>The decrypted string, wrapped into a</span>
    /// <span class="code-SummaryComment"><see cref="SecureString"/> instance.</returns></span>
    /// <span class="code-SummaryComment"><exception cref="ArgumentNullException">If <paramref name="cipher"/></span>
    /// is a null reference.<span class="code-SummaryComment"></exception></span>
    public static SecureString DecryptSecure(this string cipher)
    {
      if (cipher == null) throw new ArgumentNullException("cipher");

      //parse base64 string
      byte[] data = Convert.FromBase64String(cipher);

      //decrypt data
      byte[] decrypted = ProtectedData.Unprotect(data, null, Scope);

      SecureString ss = new SecureString();

      //parse characters one by one - doesn't change the fact that
      //we have them in memory however...
      int count = Encoding.Unicode.GetCharCount(decrypted);
      int bc = decrypted.Length/count;
      for (int i = 0; i < count; i++)
      {
        ss.AppendChar(Encoding.Unicode.GetChars(decrypted, i*bc, bc)[0]);
      }

      //mark as read-only
      ss.MakeReadOnly();
      return ss;
    }

    /// <span class="code-SummaryComment"><summary></span>
    /// Wraps a managed string into a <span class="code-SummaryComment"><see cref="SecureString"/> </span>
    /// instance.
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><param name="value">A string or char sequence that </span>
    /// should be encapsulated.<span class="code-SummaryComment"></param></span>
    /// <span class="code-SummaryComment"><returns>A <see cref="SecureString"/> that encapsulates the</span>
    /// submitted value.<span class="code-SummaryComment"></returns></span>
    /// <span class="code-SummaryComment"><exception cref="ArgumentNullException">If <paramref name="value"/></span>
    /// is a null reference.<span class="code-SummaryComment"></exception></span>
    public static SecureString ToSecureString(this IEnumerable<char> value)
    {
      if (value == null) throw new ArgumentNullException("value");

      var secured = new SecureString();

      var charArray = value.ToArray();
      for (int i = 0; i < charArray.Length; i++)
      {
        secured.AppendChar(charArray[i]);
      }

      secured.MakeReadOnly();
      return secured;
    }

    /// <span class="code-SummaryComment"><summary></span>
    /// Unwraps the contents of a secured string and
    /// returns the contained value.
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><param name="value"></param></span>
    /// <span class="code-SummaryComment"><returns></returns></span>
    /// <span class="code-SummaryComment"><remarks>Be aware that the unwrapped managed string can be</span>
    /// extracted from memory.<span class="code-SummaryComment"></remarks></span>
    /// <span class="code-SummaryComment"><exception cref="ArgumentNullException">If <paramref name="value"/></span>
    /// is a null reference.<span class="code-SummaryComment"></exception></span>
    public static string Unwrap(this SecureString value)
    {
      if (value == null) throw new ArgumentNullException("value");

      IntPtr ptr = Marshal.SecureStringToCoTaskMemUnicode(value);
      try
      {
        return Marshal.PtrToStringUni(ptr);
      }
      finally
      {
        Marshal.ZeroFreeCoTaskMemUnicode(ptr);
      }
    }

    /// <span class="code-SummaryComment"><summary></span>
    /// Checks whether a <span class="code-SummaryComment"><see cref="SecureString"/> is either</span>
    /// null or has a <span class="code-SummaryComment"><see cref="SecureString.Length"/> of 0.</span>
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><param name="value">The secure string to be inspected.</param></span>
    /// <span class="code-SummaryComment"><returns>True if the string is either null or empty.</returns></span>
    public static bool IsNullOrEmpty(this SecureString value)
    {
      return value == null || value.Length == 0;
    }

    /// <span class="code-SummaryComment"><summary></span>
    /// Performs bytewise comparison of two secure strings.
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><param name="value"></param></span>
    /// <span class="code-SummaryComment"><param name="other"></param></span>
    /// <span class="code-SummaryComment"><returns>True if the strings are equal.</returns></span>
    public static bool Matches(this SecureString value, SecureString other)
    {
      if (value == null && other == null) return true;
      if (value == null || other == null) return false;
      if (value.Length != other.Length) return false;
      if (value.Length == 0 && other.Length == 0) return true;

      IntPtr ptrA = Marshal.SecureStringToCoTaskMemUnicode(value);
      IntPtr ptrB = Marshal.SecureStringToCoTaskMemUnicode(other);
      try
      {
        //parse characters one by one - doesn't change the fact that
        //we have them in memory however...
        byte byteA = 1;
        byte byteB = 1;

        int index = 0;
        while (((char)byteA) != '\0' && ((char)byteB) != '\0')
        {
          byteA = Marshal.ReadByte(ptrA, index);
          byteB = Marshal.ReadByte(ptrB, index);
          if (byteA != byteB) return false;
          index += 2;
        }

        return true;
      }
      finally
      {
        Marshal.ZeroFreeCoTaskMemUnicode(ptrA);
        Marshal.ZeroFreeCoTaskMemUnicode(ptrB);
      }
    }
  }
}

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Philipp Sumi
Architect I'm a gun for hire
Switzerland Switzerland
Philipp is an independent software engineer with great love for all things .NET.
He lives in Winterthur, Switzerland and his home on the web is at http://www.hardcodet.net.

Comments and Discussions

 
Generalgive me a idea Pinmemberwadeliu21-May-09 19:18 
GeneralRe: give me a idea PinmemberPhilipp Sumi21-May-09 20:43 
GeneralMachine Independant Encryption. PinmemberRuchit S.15-May-09 23:13 
GeneralRe: Machine Independant Encryption. PinmemberPhilipp Sumi15-May-09 23:34 
GeneralRe: Machine Independant Encryption. PinmemberRuchit S.16-May-09 0:06 
GeneralRe: Machine Independant Encryption. PinmemberPhilipp Sumi16-May-09 0:28 
Ruchit,
 
Ruchit S. wrote:
I delegate the Encryption to Base MembershipProvider class's method EncryptPassword. This method also uses DPAPI for Encryption and Decryption, And it takes the machinekey from my web.config do The encryption work very much machine independantly.

 
This sounds as if you had already written that code - I'm really not sure what you expect of me Wink | ;) . However, if you look at MembershipProvider it in Reflector, you will see the following code:
 
protected virtual byte[] EncryptPassword(byte[] password)
{
    if (MachineKeySection.IsDecryptionKeyAutogenerated)
    {
        throw new ProviderException(SR.GetString("Can_not_use_encrypted_passwords_with_autogen_keys"));
    }
    return MachineKeySection.EncryptOrDecryptData(true, password, null, 0, password.Length, IVType.None);
}
 

As you can see, you can only encrypt user-defined passwords this way. And if you look at the MSDN docs for MachineKeySection, there's the following remark:
 
The "AutoGenerate" value does not work for Web farms, because it relies on a cryptographically random secret, which is persisted using machine-local protection and will not be coherent across more than one computer
 
I could be wrong, but that sounds exactly like the characterics of DPAPI to me - you cannot move DPAPI-encrypted cyphers across machines. Accordingly, user-defined keys use another encryption mechanism - have a look at it in Reflector.
 
Cheers,
Philipp
 
NetDrives - Open Source Network Share Management Awesomeness

Generaloverflow... PinmemberDaniel M. Camenzind15-May-09 4:21 
GeneralRe: overflow... PinmemberPhilipp Sumi15-May-09 4:30 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140926.1 | Last Updated 15 May 2009
Article Copyright 2009 by Philipp Sumi
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid