将SecureString放入PasswordBox

10

我有一个现成的SecureString,我想将它放入一个PasswordBox中,而不会暴露 .Password(密码)属性。这可以做到吗?例如:

tbPassword.SecurePassword = DecryptString(Properties.Settings.Default.proxyPassword);
在这种情况下,DecryptString 会生成一个 SecureString。但是,SecurePassword 是一个只读属性,因此我无法为其分配值。(了解更多)
2个回答

10

你不能这样做。

但是,你可以在那个位置放置占位文本(甚至可以用"placeholder",我们只是用它来显示几个点以出现在框中)。

在你放置占位符后,在程序的某个地方检查“当前密码”的时候,首先检查是否自输入占位符密码以来已经触发了PasswordChanged 事件。如果事件尚未触发,请使用旧存储的密码;如果事件已触发,请使用PasswordBoxSecurePassword属性中的当前密码。


当用户尝试使用密码框的Peek功能时,此操作会失败。 - Ian Boyd
如果您计划启用Peek功能,则无法使用此解决方法。 - Scott Chamberlain

2
很抱歉补充晚了,但这可能是对于那些也遇到相似问题的人一个好主意。(至少我在2021年查找时进入了这个页面)
看一下PasswordBox的源代码,我们可以看到它的属性是如何实现的。 Password属性setter将String复制到临时SecureString中,并将其转发到内部存储。只读的SecurePassword属性返回内部SecureString的副本,因此在其上调用.Clear() / .AppendChar(char)仅会更改此副本,如果没有在其上调用.MakeReadonly()。
[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);
      }
    }
  }
}

这可能有点取巧,但调用私有的SetSecurePassword方法可能是最接近绕过转换为明文以使用密码设置器的方法:(我们像在.Password设置器中一样制作临时副本,因为我们不负责管理提供的SecureString的生命周期,甚至可以是只读的)

// 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);

我希望这仍能帮助任何人。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接