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
bstr = Marshal.SecureStringToBSTR(mSecureString)
Try
Return Marshal.PtrToStringBSTR(bstr)
Finally
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: ")
str = Console.ReadLine()
Dim gh As GCHandle = GCHandle.Alloc(str, GCHandleType.Pinned)
Console.WriteLine("Input: " & str)
DestroyString(str)
gh.Free()
End Sub
Public Shared Function DestroyString( _
ByRef str As String) As Boolean
If String.IsInterned(str) IsNot Nothing Then
Return False
End If
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.
Public Function GetProtectedData() As Byte()
Dim text As String = DecryptSecureString()
Dim gh As GCHandle
gh = GCHandle.Alloc(text, GCHandleType.Pinned)
Dim data() As Byte = Unicode.GetBytes(text)
Dim protectedData() As Byte
protectedData = crypt.ProtectedData.Protect( _
data, _
sEntropy, _
crypt.DataProtectionScope.CurrentUser)
For i As Integer = 0 To data.Length - 1
data(i) = 0
Next
If String.IsInterned(text) Is Nothing Then
DestroyString(text)
End If
gh.Free()
Return protectedData
End Function
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)
For i As Integer = 0 To data.Length - 1
data(i) = 0
Next
Dim gh As GCHandle
gh = GCHandle.Alloc(text, GCHandleType.Pinned)
SetText(text)
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.
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
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
mSecureString.Replace( _
"", _
SelectionStart, _
SelectionLength)
ElseIf Me.SelectionStart > 0 Then
mSecureString.RemoveAt( _
SelectionStart - 1)
caretPos -= 1
Else
Return
End If
Case Keys.Delete
If SelectionStart _
>= mSecureString.Length Then
Return
ElseIf SelectionLength > 0 Then
mSecureString.Replace( _
"", _
SelectionStart, _
SelectionLength)
Else
mSecureString.RemoveAt( _
Me.SelectionStart)
End If
Case Else
e.Handled = False
e.SuppressKeyPress = False
Return
End Select
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
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
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.