Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Securing Text Data in .NET

0.00/5 (No votes)
7 Dec 2006 1  
Discussion of securing text in an application. The example project contains a SecureString wrapper to make working with SecureString easier, and a textbox that directly manipulates a SecureString.

Introduction

Application security is important, but how secure is secure enough? If you are writing an online Advent calendar, security probably isn't that important. On the other hand, if you are writing software that triggers a nuclear device, security will probably be the primary feature (it probably only takes a couple of lines of code to actually trigger the device, but possibly hundreds of thousands of lines of code to secure it).

If you truly want secure software, the best advice is to not write it. Unwritten software is absolutely secure, of course, it's not very useful either. Security is always compromised in order to make software useful. Your job as a developer is to make sure that it takes an intruder more effort than it's worth to exploit those compromises.

This article attempts to explain a few of the security features in .NET for securing text in an application, both in memory and on disk. It discusses the security compromises that must be made to make an application useful, and how to minimize the security risks for sensitive text data.

The project included with this article contains a class called SimpleSecureString that wraps a SecureString and provides a number of useful functions to manipulate it. The project also includes a SecureTextBox that allows a user to enter sensitive text directly into a SimpleSecureString without ever putting the plain text in memory. Eventually, I am hoping to improve support for passwords, including automatically generating secure passwords and displaying them to the user as an image (to avoid plain text in memory), as well as checking to make sure a password is strong (based on configurable rules). But that will have to wait for another time and article.

If you are interested in more information about this topic, I have a couple of blog entries that you might be interested in (the posts include links to other articles on the Internet that I have found interesting)...

Background

I decided to write this article a few weeks ago when I was working on another article for CodeProject (Event-Based Asynchronous WebRequest). That article included code that set the WebRequest.Credentials without any form of data security. That project was adapted from a project I was working on at work that has a little bit of data security, but only enough to make me realize how little I knew about it :).

If you are like me, you know that data security is important, but are confused about how to actually implement it in a way that leaves your application both usable and secure. You've probably done some research and know that SecureString is a very useful class for implementing in-process data security, but have found that Microsoft didn't make it very easy to use and provided very little documentation on how to use it securely. I am certainly no expert on the subject, but I am willing to share my observations. If I get something wrong, please leave a comment so that others can learn from your experience.

SecureString

SecureString is basically an encrypted string. It was released with .NET 2.0 with, unfortunately, very limited support (the only classes in the framework that use it are Process, CspParameters, and X509Certificate). Hopefully, Microsoft will provide better support with future versions of .NET.

Of course, an encrypted string isn't very useful on its own (you can't compare it against a database value, pass it to your credit card vendor, etc.). To make matters worse, it isn't exactly straight-forward to create a SecureString either (you have to do it one character at a time).

For Each ch As Char In myText
    mySecureString.AppendChar(ch)
Next

If you think that's bad, wait until you try decrypting it!

Dim bstr As IntPtr

' converts the SecureString to a bstr (basic COM

' string).

bstr = Marshal.SecureStringToBSTR(mSecureString)
Try
    ' This creates an unencrypted copy of the 

    ' secure string

    Return Marshal.PtrToStringBSTR(bstr)
Finally
    ' zeros out the memory and then deallocates it

    Marshal.ZeroFreeBSTR(bstr)
End Try

The project included with this article includes a SecureString wrapper called SimpleSecureString that provides several very useful methods for working with SecureString. As you have probably guessed from the introduction, these methods expose security holes in SecureString in order to make it useful for your application. The goal is to limit the exposure of the risk. With that in mind, only use the decrypted text within a method scope, and destroy the string when you are done with it. When passing the text to other methods or storing it in memory, use the SecureString class wherever possible.

Here is an example of how to destroy a string in memory (DestroyString is a method available from SimpleSecureString).

Sub Main()
    Dim str As String
    Console.Write("Enter a value: ")
    ' we read the value from the command-line in order to avoid string interning

    str = Console.ReadLine()
    ' GCHandle is typically used to provide a pointer to the memory location

    ' when calling into unmanaged code. In this case, we are using it to prevent

    ' the garbage collector from copying the string.

    Dim gh As GCHandle = GCHandle.Alloc(str, GCHandleType.Pinned)
    Console.WriteLine("Input: " & str)
    DestroyString(str)
    gh.Free()
End Sub

''' <summary>

''' Zeros out the memory that is allocated for a string.

''' </summary>

''' <param name="str"></param>

''' <returns>True if the string is destroyed, otherwise 

''' false. Strings will not be destroyed if they are 

''' interned.</returns>

Public Shared Function DestroyString( _
        ByRef str As String) As Boolean

    ' send in the string as a reference otherwise we 

    ' end up with a copy of the string.


    ' If a string is interned, it should not be 

    ' changed. It could lead to harmful, very 

    ' difficult to track down side-effects.

    If String.IsInterned(str) IsNot Nothing Then
        Return False
    End If

    ' The length referred to by the length parameter

    ' in RtlZeroMemory is the number of bytes. In

    ' .Net, a char is 2 bytes, so we multiply the 

    ' length of the string by 2.

    ZeroMemory(str, str.Length * 2)

    Return True
End Function

Private Declare Sub ZeroMemory Lib "kernel32.dll" _
        Alias "RtlZeroMemory" _
        (ByVal Destination As String, _
         ByVal Length As Integer)

If you are interested in getting more information on this method, take a peek at the post Securing Text Data on my blog.

SecureString cannot be stored on disk (it is not serializable). If you need to store a SecureString on disk, you will need to decrypt it first. Of course, you don't want to store the decrypted value, so you must find a way to encrypt it so that it can be decrypted the next time your application starts up and where the encryption isn't easily broken. No worries, it's built right into SimpleSecureString, along with a constructor that will convert that back into a SimpleSecureString.

The following code sample is a method and a constructor from SimpleSecureString that can create and read a representation of the secure text that can be stored on disk and is safely encrypted. Make sure you read up on the DPAPI so that you use these methods correctly (it can only be decrypted for the user it was encrypted for). If you need to save it as a string, you can use the Convert.ToBase64String and Covert.FromBase64String methods.

''' <summary>

''' Gets an encrypted, binary representation of the 

''' current secure string that can be saved to disk and

''' restored using the proper constructor. Note that 

''' the encryption is based the user. It cannot be

''' decrypted for any other user.

''' </summary>

Public Function GetProtectedData() As Byte()
    Dim text As String = DecryptSecureString()

    ' GCHandle is typically used to provide a pointer 

    ' to the memory location when calling into 

    ' unmanaged code. In this case, we are using it to 

    ' prevent the garbage collector from copying the 

    ' string.

    Dim gh As GCHandle
    gh = GCHandle.Alloc(text, GCHandleType.Pinned)
    Dim data() As Byte = Unicode.GetBytes(text)
    Dim protectedData() As Byte

    ' Article on using the ProtectedData API

    ' http://blogs.msdn.com/shawnfa/archive/2004/05/05/126825.aspx

    protectedData = crypt.ProtectedData.Protect( _
                data, _
                sEntropy, _
                crypt.DataProtectionScope.CurrentUser)

    ' destroy the unprotected data array

    For i As Integer = 0 To data.Length - 1
        data(i) = 0
    Next

    ' we can't destroy interned strings

    If String.IsInterned(text) Is Nothing Then
        DestroyString(text)
    End If
    gh.Free()

    Return protectedData
End Function

''' <summary>

''' Creates a new instance of SecureString based on the

''' protected data. 

''' </summary>

''' <param name="protectedData">Must be created by 

''' SecureString.GetProtectedData().</param>

Public Sub New(ByVal protectedData() As Byte)
    mSecureString = New SecureString()
    Dim data() As Byte

    data = crypt.ProtectedData.Unprotect( _
                protectedData, _
                sEntropy, _
                crypt.DataProtectionScope.CurrentUser)

    Dim text As String
    text = Unicode.GetString(data)

    ' destroy the unprotected data array

    For i As Integer = 0 To data.Length - 1
        data(i) = 0
    Next

    ' pin the text so it doesn't get copied in memory

    Dim gh As GCHandle
    gh = GCHandle.Alloc(text, GCHandleType.Pinned)

    SetText(text)

    ' we can't destroy interned strings

    If String.IsInterned(text) Is Nothing Then
        DestroyString(text)
    End If
    gh.Free()
End Sub

SecureTextBox

As if the SimpleSecureString were not enough, the project included with this article also includes a SecureTextBox. This control inherits from TextBox, but provides direct manipulation of the SimpleSecureString. Don't bother trying to get the text from the SecureTextBox, it will only return asterisk.

In order to accomplish this amazing feat, SecureTextBox handles the KeyDown and KeyPress events. The KeyDown event handler handles the Backspace and Delete keys (KeyPress is only raised for printable characters). KeyPress handles all of the printable characters.

The KeyPress and KeyDown handlers swallow the Key event to prevent the system from doing anything with them. It then modifies the SimpleSecureString object based on the key stroke. It handles all of the cases I could think of, including selecting multiple characters in the TextBox and typing over them while placing the caret in the correct position to allow the user to continue typing (trickier than it sounds).

The following code is from the SecureTextBox class which is in the project included with this article.

''' <summary>

''' Updates the TextBox with a string that represents

''' the secure string.

''' </summary>

Private Sub SetText()
  MyBase.Text = New String("*"c, mSecureString.Length)
End Sub

Private Sub mSecureString_SecureStringChanged( _
        ByVal sender As Object, _
        ByVal e As System.EventArgs) _
        Handles mSecureString.SecureStringChanged
  
  SetText()
  OnSecureStringChanged(e)
End Sub

Private Sub TextBox_KeyDown( _
    ByVal sender As Object, _
    ByVal e As KeyEventArgs) _
    Handles MyBase.KeyDown

  ' This method handles the Delete and Backspace keys.

  ' These keys are not sent to the KeyPress event.


  Dim ch As Char = Convert.ToChar(e.KeyValue)

  e.Handled = True
  e.SuppressKeyPress = True

  Dim caretPos As Integer = SelectionStart

  Select Case e.KeyCode
      Case Keys.Back
          If Me.SelectionLength > 0 Then
              ' one or more characters are selected


              mSecureString.Replace( _
                  "", _
                  SelectionStart, _
                  SelectionLength)

          ElseIf Me.SelectionStart > 0 Then
              ' no characters are selected and we 

              ' are not at the beginning of the text


              mSecureString.RemoveAt( _
                  SelectionStart - 1)

              caretPos -= 1
          Else
              ' at the beginning of the text with 

              ' nothing selected 

              Return ' don't change the SelectionStart

          End If
      Case Keys.Delete
          If SelectionStart _
              >= mSecureString.Length Then

              ' at the end of the string

              Return ' don't change the SelectionStart


          ElseIf SelectionLength > 0 Then
              ' one or more characters are selected


              mSecureString.Replace( _
                  "", _
                  SelectionStart, _
                  SelectionLength)

          Else
              ' no characters are selected


              mSecureString.RemoveAt( _
                  Me.SelectionStart)

          End If
      Case Else
          ' allow all other keys to be processed

          e.Handled = False
          e.SuppressKeyPress = False
          Return ' don't change the SelectionStart

  End Select

  ' we have to reset the SelectionStart because the 

  ' text is reset when the secure string changes

  Me.SelectionStart = caretPos
  Me.SelectionLength = 0
End Sub

Private Sub TextBox_KeyPress( _
    ByVal sender As Object, _
    ByVal e As KeyPressEventArgs) _
    Handles Me.KeyPress

  Dim ch As Char = e.KeyChar

  ' The KeyPress event is only raised for printable 

  ' chars. Control chars are handled in the KeyDown

  ' event handler.


  e.Handled = True

  Dim caretPos As Integer = SelectionStart

  If SelectionStart >= mSecureString.Length Then
      mSecureString.AppendChar(ch)
  ElseIf Me.SelectionLength > 0 Then

      mSecureString.Replace( _
          ch, _
          SelectionStart, _
          SelectionLength)

  Else
      mSecureString.InsertAt(caretPos, ch)
  End If

  ' we have to reset the SelectionStart because we 

  ' reset the text when the secure string changes

  SelectionStart = caretPos + 1
  SelectionLength = 0
End Sub

Conclusion

As a word of caution, I would recommend not rushing off and implementing data security until you fully understand the risks associated with it. There are valid arguments against the need to implement in-process data security (SecureString is overrated).

The primary concept that I am hoping you will take away from this article is that securing data is important and should be considered in your application. However, you also need to be willing to make compromises in your security in order to make the data useable. The compromises you make should be based on the risk involved in losing control of that data (will a kid get a piece of chocolate early, or will thousands die in a nuclear explosion?).

References

I read many different articles on the Internet about SecureString and related issues, while writing this article. Below are a few of the articles that I thought were interesting enough to bookmark. You will probably see where some of my influences come from.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here