Click here to Skip to main content
15,896,063 members
Articles / Desktop Programming / Windows Forms

Securing Text Data in .NET

Rate me:
Please Sign up or sign in to vote.
4.15/5 (7 votes)
7 Dec 20066 min read 59.9K   661   36  
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.
Imports System.Security
Imports System.Runtime.InteropServices
Imports crypt = System.Security.Cryptography
Imports System.Text.Encoding

<Serializable()> _
Public Class SimpleSecureString
    Implements IDisposable

#Region "Initialization and destruction"

    ''' <summary>
    ''' Creates a new instance of SecureString.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub New()
        mSecureString = New SecureString()
    End Sub

    ''' <summary>
    ''' Creates a new instance of SecureString based on the
    ''' SecureString passed in. 
    ''' </summary>
    ''' <param name="secureStr"></param>
    Public Sub New(ByVal secureStr As SecureString)
        ' Making changes to the original SecureString can 
        ' lead to difficult-to-track bugs in calling code, 
        ' so make a copy.
        mSecureString = secureStr.Copy()
    End Sub

    ''' <summary>
    ''' Creates a new instance of SecureString based on the
    ''' text passed in.
    ''' </summary>
    ''' <param name="text"></param>
    ''' <remarks></remarks>
    Public Sub New(ByVal text As String)
        mSecureString = New SecureString()
        SetText(text)
    End Sub

    ''' <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

    Protected Overrides Sub Finalize()
        Dispose(False)
    End Sub

    Public Overloads Sub Dispose() Implements IDisposable.Dispose
        Dispose(True)
    End Sub

    Protected Overloads Sub Dispose(ByVal disposing As Boolean)
        ' Using the IDisposable pattern.

        ' if we come from the dispose method, suppress the
        ' finalize method so this instance is only disposed
        ' of once.
        If disposing Then
            GC.SuppressFinalize(Me)
        End If

        If mSecureString IsNot Nothing Then
            mSecureString.Dispose()
            mSecureString = Nothing
        End If
    End Sub

#End Region

#Region "Fields and properties"

    Private mSecureString As SecureString

    ''' <summary>
    ''' This field is used as part of the DataProtection.
    ''' It will help prevent other applications from 
    ''' accessing this applications data.
    ''' </summary>
    ''' <remarks></remarks>
    Private Shared sEntropy() As Byte = Unicode.GetBytes("put whatever you want here")

    ''' <summary>
    ''' Gets the length of the current secure string.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property Length() As Integer
        Get
            Return mSecureString.Length
        End Get
    End Property

#End Region

#Region "Methods"

    ''' <summary>
    ''' Set the secure string to the specified text value.
    ''' </summary>
    ''' <param name="text">The text to set the secure 
    ''' string to.</param>
    ''' <remarks></remarks>
    Public Sub SetText(ByVal text As String)
        mSecureString.Clear()
        For Each ch As Char In text
            mSecureString.AppendChar(ch)
        Next
    End Sub

    ''' <summary>
    ''' Append a character to the end of the current secure 
    ''' string.
    ''' </summary>
    ''' <param name="ch">A character to append to the secure 
    ''' string</param>
    ''' <remarks></remarks>
    Public Sub AppendChar(ByVal ch As Char)
        mSecureString.AppendChar(ch)
        OnSecureStringChanged(EventArgs.Empty)
    End Sub

    ''' <summary>
    ''' Inserts a character into the current secure string
    ''' at the specified index.
    ''' </summary>
    ''' <param name="index">The index position where 
    ''' parameter ch should be inserted.</param>
    ''' <param name="ch">A character to insert into the 
    ''' secure string</param>
    ''' <remarks></remarks>
    Public Sub InsertAt(ByVal index As Integer, ByVal ch As Char)
        mSecureString.InsertAt(index, ch)
        OnSecureStringChanged(EventArgs.Empty)
    End Sub

    ''' <summary>
    ''' Replaces the existing character at the specified 
    ''' index position with another character.
    ''' </summary>
    ''' <param name="index">The index position of an 
    ''' existing character in this secure string</param>
    ''' <param name="ch">A character that replaces the 
    ''' existing character.</param>
    ''' <remarks></remarks>
    Public Sub SetAt(ByVal index As Integer, ByVal ch As Char)
        mSecureString.SetAt(index, ch)
        OnSecureStringChanged(EventArgs.Empty)
    End Sub

    ''' <summary>
    ''' Removes the character at the specified index 
    ''' position from this secure string.
    ''' </summary>
    ''' <param name="index">The index position of a 
    ''' character in this secure string.</param>
    ''' <remarks></remarks>
    Public Sub RemoveAt(ByVal index As Integer)
        mSecureString.RemoveAt(index)
        OnSecureStringChanged(EventArgs.Empty)
    End Sub

    ''' <summary>
    ''' Replaces the existing characters at the specified
    ''' index position through the specified length with
    ''' the specified text.
    ''' </summary>
    ''' <param name="text">The text that replaces the 
    ''' existing characters.</param>
    ''' <param name="index">The starting index position of  
    ''' the characters in this secure string to replace.
    ''' </param>
    ''' <param name="length">The number of characters to 
    ''' replace.</param>
    Public Sub Replace( _
            ByVal text As String, _
            ByVal index As Integer, _
            ByVal length As Integer)

        Dim i As Integer = 0

        ' remove the chars that we are replacing
        For i = 0 To length - 1
            mSecureString.RemoveAt(index)
        Next

        ' add the new string to the text in the correct 
        ' place
        i = index
        For Each ch As Char In text
            mSecureString.InsertAt(i, ch)
            i += 1
        Next

        OnSecureStringChanged(EventArgs.Empty)
    End Sub

    ''' <summary>
    ''' Deletes the value of the current secure string.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub Clear()
        mSecureString.Clear()
        OnSecureStringChanged(EventArgs.Empty)
    End Sub

    ''' <summary>
    ''' Indicates whether this secure string is marked 
    ''' read-only.
    ''' </summary>
    ''' <returns>true if this secure string is marked 
    ''' read-only; otherwise, false.</returns>
    ''' <remarks></remarks>
    Public Function IsReadonly() As Boolean
        Return mSecureString.IsReadOnly()
    End Function

    ''' <summary>
    ''' Makes the text value of this secure string 
    ''' read-only.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub MakeReadonly()
        mSecureString.MakeReadOnly()
    End Sub

    ''' <summary>
    ''' Returns a SecureString.
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function ToSecureString() As SecureString
        Return mSecureString.Copy()
    End Function

    ''' <summary>
    ''' Gets the unencrypted text for the current secure
    ''' string.
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function DecryptSecureString() As String
        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
    End Function

    ''' <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>
    ''' <returns></returns>
    ''' <remarks></remarks>
    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>
    ''' 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>
    ''' <remarks></remarks>
    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)

#End Region

#Region "Events"

    ''' <summary>
    ''' Raised whenever the current secure string changes.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Public Event SecureStringChanged(ByVal sender As Object, ByVal e As EventArgs)
    Protected Overridable Sub OnSecureStringChanged(ByVal e As EventArgs)
        RaiseEvent SecureStringChanged(Me, e)
    End Sub

#End Region

End Class

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

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


Written By
Web Developer
United States United States
I'm a software engineer in Spokane, WA. I have been developing with .Net since 2002. My main area of focus has been designing and implementing a UI framework for an ERP system. Before I got into .Net, I developed for several years in a variety of languages and platforms including mostly ASP, though I've also developed applications for both Palm and Pocket PC devices.

I received my degree in Computing and Software System from the University of Washington in 1999. I have also completed a certificate course in Object-Oriented Analysis and Design Using UML, also from the University of Washington, in 2005.

Comments and Discussions