문제

I have an existing SecureString that I would like to put into a PasswordBox without revealing the .Password. Can this be done? For example:

tbPassword.SecurePassword = DecryptString(Properties.Settings.Default.proxyPassword);

In this case DecryptString produces a SecureString. However, SecurePassword is a read-only property so I can't assign a value to it.

도움이 되었습니까?

해결책

You can't.

However, what you can do is put placeholder text in it's place (it can even be "placeholder", we are only using it to make a few dots to show up in the box).

After you put the placeholder in, when you go to retrieve the "current password" somewhere in your program first check if the PasswordChanged event has fired since you entered the placeholder password. If the event has not fired use the old stored password, if the event has fired use the current password from the SecurePassword property of PasswordBox.

다른 팁

Sorry for the late addition, but it might be an idea for people who also walk into this. (At least I ended up on this page in 2021 looking for it)

Looking at the source of PasswordBox, we can see how its properties are implemented. The Password property setter just copies the String into a temporary SecureString and forwards it to its internal storage. The readonly SecurePassword property returns a copy of the internal SecureString, so calling .Clear() / .AppendChar(char) on it will only change this copy, if .MakeReadonly() has not been called on it.

[TemplatePart(Name = "PART_ContentHost", Type = typeof (FrameworkElement))]
public sealed class PasswordBox : Control, ITextBoxViewHost
{
  public SecureString SecurePassword => this.TextContainer.GetPasswordCopy();
  [DefaultValue("")]
  [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  public unsafe string Password
  {
    [SecurityCritical] get { /* left out to reduce space */ }
    [SecurityCritical] set
    {
      if (value == null)
        value = string.Empty;
      // We want to replicate this, but copy a SecureString instead of creating one from a String
      using (SecureString secureString = new SecureString())
      {
        for (int index = 0; index < value.Length; ++index)
          secureString.AppendChar(value[index]);
        this.SetSecurePassword(secureString);
      }
    }
  }
}

It may be a bit hacky, but calling the private SetSecurePassword may be closest to bypassing conversion to clear text in order to use the Password setter : (we make a temporary copy just like in the .Password setter as we are not responsible for managing lifetime of the provided SecureString, which could even be readonly)

// option 1: streight reflection 
var setPasswordMethod = typeof(PasswordBox).GetMethod("SetSecurePassword", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] {typeof(SecureString)}, null);
using (var copy = mySecurePassword.Copy())
    setPasswordMethod.Invoke(PasswordControl, new[] {copy});

// option 2: compiled delegate so reflection will only kick in once
Action<PasswordBox, SecureString> setSecurePassword = null; // this would be a cache lookup instead of a local variable.
if (setSecurePassword == null)
{
    var passwordBox = Expression.Parameter(typeof(PasswordBox), "passwordBox");
    var password = Expression.Parameter(typeof(SecureString), "securePassword");
    //// if we want to include code for making the temporary copy in the delegate, use this instead to create its body
    //var passwordCopy = Expression.Variable(typeof(SecureString));
    //var makePasswordCopy = Expression.Call(password, nameof(SecureString.Copy), Type.EmptyTypes);
    //var body = Expression.Block(new[] {passwordCopy},
    //    Expression.Assign(passwordCopy, makePasswordCopy),
    //    Expression.TryFinally(
    //        Expression.Call(passwordBox, "SetSecurePassword", Type.EmptyTypes, passwordCopy),
    //        Expression.Call(Expression.Convert(passwordCopy, typeof(IDisposable)),
    //            nameof(IDisposable.Dispose), Type.EmptyTypes)));
    var body = Expression.Call(passwordBox, "SetSecurePassword", Type.EmptyTypes, password);
    setSecurePassword = Expression.Lambda<Action<PasswordBox, SecureString>>(body, passwordBox, password).Compile();
}
using (var copy = mySecurePassword.Copy()) // if we would make the copy inside the delegate, we won't need to do it here.
    setSecurePassword(PasswordControl, copy);

I hope this still helps anyone.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top