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