TryCatchFinally.net Some SQL, some .NET, and whatever else

2Jul/090

Encrypting data per-user in your .NET application without asking for a secret

When you encrypt something, you need some kind of secret to do so - a password that's used to encrypt and decrypt your message, or a public/private key like a certificate. In order to get a secret, you usually have to ask the user for one - in most cases, a password - before they can decrypt their data and access it.

I had an application where I wanted to encrypt some database connection details between sessions. This is easy enough, but I wanted to do it without having to ask the user for a password - I just wanted it done transparently. I thought this meant storing a secret key somewhere, but there's no safe place to keep a key -even if it's embedded in your code, somebody who really wants it will always be able to get it. What I needed was a private place to store a key, where no other Windows user on the same computer would be able to access it.

That's when I discovered the DPAPI - it's been around since .NET 2.0, and it's available through the System.Security.Cryptography namespace. What's great about this cryptographic feature is that you can have Windows encrypt something with the same key it uses to encrypt files with EFS - a private user key that's based on a hash of their Windows password. This means that you don't need to ask the user for a secret - you've already got one handy, and Windows will prevent other users of the system from being able to decrypt your data. The downside of this is that if the user resets their password, you're toast - if you're only keep the data as a convenience (as I am), that's no problem, but if you can't afford to lose the data, you'll need to ask the user for the secret instead of relying on this method.

This MSDN article that detailed its use, and after adding some code that made it easy to encrypt and decrypt strings, here's what I ended up with:

Imports System
Imports System.Security.Cryptography

''' <summary>
''' Encrypts and Decrypts information using the current Windows user key
''' </summary>
''' <remarks></remarks>
Public Class DPAPI

    Private Shared EntropyString As String = "Some value I made up"

    ' Create byte array for additional entropy when using Protect/Unprotect method.
    Private Shared Function AdditionalEntropy() As Byte()
        Dim encoder As New System.Text.ASCIIEncoding
        Return encoder.GetBytes(EntropyString)
    End Function

#Region " Encrypt "

    Public Shared Function Protect(ByVal data() As Byte) As Byte()
        Try
            ' Encrypt the data using DataProtectionScope.CurrentUser. The result can be decrypted
            ' only by the same current user.
            Return ProtectedData.Protect(data, AdditionalEntropy, DataProtectionScope.CurrentUser)
        Catch e As CryptographicException
            Console.WriteLine("Data was not encrypted. An error occurred.")
            Console.WriteLine(e.Message.ToString())
            Throw
        End Try
    End Function

    Public Shared Function ProtectString(ByVal data As String) As String

        Dim encoder As New System.Text.ASCIIEncoding
        Return Convert.ToBase64String(Protect(encoder.GetBytes(data)))

    End Function

#End Region

#Region " Decrypt "

    Public Shared Function Unprotect(ByVal data() As Byte) As Byte()
        Try
            'Decrypt the data using DataProtectionScope.CurrentUser.
            Return ProtectedData.Unprotect(data, AdditionalEntropy, DataProtectionScope.CurrentUser)
        Catch e As CryptographicException
            Console.WriteLine("Data was not decrypted. An error occurred.")
            Console.WriteLine(e.Message.ToString())
            Throw
        End Try

    End Function

    Public Shared Function UnprotectString(ByVal data As String) As String

        Dim encoder As New System.Text.ASCIIEncoding
        Dim b() As Byte = Convert.FromBase64String(data)
        Return encoder.GetString(Unprotect(b))

    End Function

#End Region

End Class

Download the code here

To use the class, just change the entropy value at the top (this is combined with the user's key to create a new key used to encrypt your data), call Protect() or ProtectString() and pass it the required data, and you're good to go! I added the ProtectString and UnprotectString because I was storing the values in the user's app.config file, and needed an easy string representation.

If you use the class or have any questions, please let me know!