Secure TextBox Updated

Published Sunday, October 29, 2006 4:53 PM

There has been quite some interest in my SecureTextBox control that I developed. Basically it offers textual entry via a standard textbox, but uses the SecureString as its internal storage. Typically aimed at entering passwords and similar secure information on a windows form.

Domink Zemp recently downloaded the control and has been using it with good success. He was also nice enough to forward some changes and improvements to the control to better handle the TAB, ENTER and ESCAPE keys. The new code has been posted on my site here. (http://www.theglavs.com/DownloadItem.aspx?FileID=46).

Incidentally, its been one of my more popular downloads. Its now clocked in at around 2500 downloads and steadily climbing.

It had a good plug from one of the Microsoft guys over in Hong Kong too. You can read about it here. It was used as collateral for a security presentation that was being delivered. Pretty cool.

Anyway, special thanks to Dominik who was nice enough to send through the changes to improve the control. Its always nice to hear from the community, how something is going, after you've let something loose in the community.

Update: I have recently updated this control based on "Wout's" bug fixes and changes and also added a new feature to confirm clearing the clipboard when you paste in some text. (Note: See comments below). Thanks Wout!

Comments

# S said on Friday, June 20, 2008 12:51 PM

Pasting is not handled properly in the SecureTextBox.

# S said on Friday, June 20, 2008 2:56 PM

Selecting text and pressing backspace also doesn't work correctly.

# Glav said on Saturday, June 21, 2008 2:24 AM

HI S,

Yep, I know about those issues but the control as it currently exists suits the purpose for which I originally used it. Feel free to grab the code and change it, I just dont have the time right now though.

Thanks for the comments.

# Wout said on Wednesday, August 13, 2008 9:30 PM

Bug fixes:

Bug: select text, press backspace twice. Exception pops up because code is not properly synching to the secure string. The fix is by simplifying the behavior and not mess with the TextBox:

                       case (int)Keys.Back:

                           if (this.SelectionLength == 0 && startPos > 0)

                           {

                               startPos--;

                               _secureEntry.RemoveAt(startPos);

                               // Wout: bug fix: don't mess with the TextBox behavior, it handles the backspace key just fine.

                               //this.Text = new string('*', _secureEntry.Length);

                               //this.SelectionStart = startPos;

                           }

                           else if (this.SelectionLength > 0)

                           {

                               for (int i = 0; i < this.SelectionLength; i++) {

                                   _secureEntry.RemoveAt(this.SelectionStart);

                               }

                           }

                           // Wout: bug fix: used to be false, but then the TextBox didn't process the backspace key.

                           _displayChar = true;

                           break;

Thanks for the good work!

Wout

# Glav said on Thursday, August 14, 2008 1:06 AM

Hey thanks Wout,

Such an easy bug to repro you'd think I would have caught that one. :-)

Thanks for sending through the fix too. Much appreciated.

# Wout said on Thursday, August 14, 2008 6:22 AM

You're welcome, I'd figure I'd just fix it and send you the fix instead of whining about it ;-).

Wout

# Wout said on Thursday, August 14, 2008 6:26 PM

Another donation to the community from Wout Ware! Here's a code snippet for the pasting behavior (add to method IsInputChar(char charCode):

                       case 22: // The paste button

                           // This kinda defeats the purpose of the secure textbox, but it's already

                           // on the clipboard anyway, so who cares.

                           string password = Clipboard.GetText();

                           if (password != null) {

                               int newSelectionStart = SelectionStart + password.Length;

                               if (SelectionLength == 0) {

                                   for (int i = password.Length - 1; i >= 0; i--) {

                                       _secureEntry.InsertAt(SelectionStart, password[i]);

                                   }

                               } else {

                                   // Remove old chars.

                                   for (int i = 0; i < this.SelectionLength; i++) {

                                       _secureEntry.RemoveAt(this.SelectionStart);

                                   }

                                   // Insert new chars.

                                   for (int i = password.Length - 1; i >= 0; i--) {

                                       _secureEntry.InsertAt(SelectionStart, password[i]);

                                   }

                               }

                               Text = new string(PasswordChar, _secureEntry.Length);

                               SelectionStart = newSelectionStart;

                           }

                           _displayChar = false;

                           break;

Enjoy!

Wout

# Joel said on Tuesday, September 2, 2008 4:49 PM

I'm benefiting from your work here so, thanks.  Good stuff.  

Nowever, I need the ability to take the values entered in your text box/SecureString and store them in a database for future comparison.  My understanding is the primary purpose of a SecureString is to limit the variable's lifespan; not to capitolize on encryption.  So, I have attached an example on how I took the value in the SecureString and encrypted it for storage as a byte array or as a string if you like:

       /// <summary>

       /// Show the hashed byte array back in a string for visual or string comparisons.

       /// </summary>

       /// <returns></returns>

       public string GetHashString()

       {

           return BitConverter.ToString(GetHashBytes(SecureText));

       }

       /// <summary>

       /// Convert a secure string to a hashed byte array based on SHA1 160-bit cryptology.

       /// </summary>

       /// <param name="incomingString">string to be hashed</param>

       /// <returns>hashed string from incomingString</returns>

       public byte[] GetHashBytes(SecureString secureString)

       {

           try

           {

               /// Strings cannot be hashed directly.  The string must be converted

               /// to a byte array before hashing.

               /// Create a pointer but do not store the available string anywhere.

               IntPtr ptr = Marshal.SecureStringToBSTR(secureString);

               // Define byte array to the length of the string to be hashed.

               byte[] bytes = new byte[Marshal.PtrToStringAuto(ptr).Length];

               // Do not place the Marshal.PtrToStringAuto(ptr) into a string variable.

               // That would defeat the purpose of having it in a SecureString

               // Convert one character at a time to a byte and store it in the bytes array

               int i = 0;

               foreach (char c in Marshal.PtrToStringAuto(ptr).ToCharArray())

               {

                   bytes[i] = Convert.ToByte(c); i++;

               }

               // Free the SecureString pointer now that the byte array is populated.

               Marshal.ZeroFreeBSTR(ptr);

               // Hash the bytes array then return the result as a string

               SHA1CryptoServiceProvider hasher = new SHA1CryptoServiceProvider();

               return hasher.ComputeHash(bytes);

           }

           catch (Exception ex)

           {

               throw new ApplicationException("Failed to hash string.  " + ex.Message, ex);

           }

       }

# Sly Gryphon said on Monday, June 1, 2009 12:00 AM

Marshal.PtrToStringAuto(ptr).Length is going to create a String, even if you reference it inline (if you look at PtrToStringAuto in Reflector or ildasm, you see it adds the chars to a StringBuilder first)... it is creating a string, so it has to go somewhere in memory, even if you don't hold the reference in a variable.

The code then calls PtrToStringAuto a second time creates another StringBuilder and String on the heap, and then an array of chars is created. Finally an array of bytes is also allocated in the managed heap, and also not cleared out, meaning you end up with six copies of the password bytes floating around in memory (2 StringBuilders, 2 Strings, 1 char array, 1 byte array). (Also, any clearing out should be in finally blocks.)

Also, chars are not the same as bytes ... a char is a UTF-16 code unit (i.e. 2 bytes), and trying to call ConvertTo something containing non-ASCII characters will throw an overflow exception.

You need to be careful to avoid getting the data into memory... either marshall it into a pinned (fixed) array, or wrap it in a Stream (that returns one character at a time) to pass to the hash function.

I think the stream solution is easier, but the following should work for the pinned array. Only two copies of the password exist outside the secure string, and both are cleared out.

 public static byte[] GetHashBytesSecure(SecureString secureString) {

   byte[] result = null;

   IntPtr secureStringPtr = IntPtr.Zero;

   SHA1CryptoServiceProvider hasher = new SHA1CryptoServiceProvider();

   try

   {

 // Get the secure string into a memory buffer

     secureStringPtr = Marshal.SecureStringToGlobalAllocAnsi(secureString);

     int stringSize = secureString.Length * sizeof(byte);

     // If you want to treat the secureString as Unicode, use the following instead

     //secureStringPtr = Marshal.SecureStringToGlobalAllocUnicode(secureString);

     //int stringSize = secureString.Length * sizeof(char);

     byte[] fixedByteArray = new byte[stringSize];

 // Pin the array, copy data in, use it, and then make sure it is clear before unpinning.

 unsafe {

     fixed( byte* ptr = fixedByteArray ) {

     try

 {

       Marshal.Copy(secureStringPtr, fixedByteArray, 0, stringSize);

           result = hasher.ComputeHash(fixedByteArray);

 }

 finally

 {

   // Ensure managed array is cleared

   Array.Clear(fixedByteArray, 0, stringSize);

 }

   }

     }

   }

   finally

   {

     // Ensure unmanaged memory is released.

     if(secureStringPtr != IntPtr.Zero)

 {

       Marshal.ZeroFreeGlobalAllocAnsi(secureStringPtr);

       //Marshal.ZeroFreeGlobalAllocUnicode(secureStringPtr);

     }

   }

   return result;

 }

# Cesar Ronchese said on Thursday, October 21, 2010 9:13 PM

I can't find the implementation of TAB and ENTER as mentioned above, and I really needed these keys to work.

Then, if anyone needs to customize to get it working, here it goes:

       protected override bool IsInputKey(Keys keyData)

       {

           //custom code: allow ENTER, TAB and  SHIFT+TAB

           if (keyData == Keys.Return) return false;

           if (keyData == Keys.Tab) return false;

           if ((keyData & Keys.Tab) == Keys.Tab && (keyData & Keys.Shift) == Keys.Shift) return false;

           //end of custom code

//...

# Steve said on Monday, December 19, 2011 2:54 PM

Thanks for this great textbox!

Leave a Comment

(required) 
(required) 
(optional)
(required) 

This Blog

Syndication