|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionApplication 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 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)... BackgroundI 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 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
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 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 Here is an example of how to destroy a string in memory ( 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.
The following code sample is a method and a constructor from ''' <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
SecureTextBoxAs if the In order to accomplish this amazing feat, The The following code is from the ''' <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
ConclusionAs 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?). ReferencesI read many different articles on the Internet about
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||