Secure TextBox Updated

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!

11 Comments

  • Pasting is not handled properly in the SecureTextBox.

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

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

  • 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

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

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

    Wout

  • 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 = 0; i--) {
    _secureEntry.InsertAt(SelectionStart, password[i]);
    }
    }
    Text = new string(PasswordChar, _secureEntry.Length);
    SelectionStart = newSelectionStart;
    }

    _displayChar = false;
    break;

    Enjoy!

    Wout

  • 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:

    ///
    /// Show the hashed byte array back in a string for visual or string comparisons.
    ///
    ///
    public string GetHashString()
    {
    return BitConverter.ToString(GetHashBytes(SecureText));
    }

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

    /// hashed string from incomingString
    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);
    }
    }

  • 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;
    }

  • 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

    //...

  • Thanks for this great textbox!

Comments have been disabled for this content.