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.