【问题标题】:Cannot save password to credential manager when encrypted加密时无法将密码保存到凭据管理器
【发布时间】:2026-01-26 00:55:01
【问题描述】:

我正在尝试将加密密码保存到 Windows 凭据管理器,以下代码可以正常读取,并且我可以生成正确加密的字符串,但是密码的加密形式永远不会保留。

为了完整起见,我需要对密码进行加密,因为客户端应用程序需要这样做(Office 2010)。如果我通过 Office 2010 保存密码,我可以正确读取它。

凭证结构

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct Credential
{
  public UInt32 flags;
  public UInt32 type;
  public string targetName;
  public string comment;
  public System.Runtime.InteropServices.ComTypes.FILETIME lastWritten; 
  public UInt32 credentialBlobSize;
  public IntPtr credentialBlob;
  public UInt32 persist;
  public UInt32 attributeCount;
  public IntPtr credAttribute;
  public string targetAlias;
  public string userName;
}

阅读:

IntPtr credPtr;
if (!Win32.CredRead(target, settings.Type, 0, out credPtr))
{
  Trace.TraceError("Could not find a credential with the given target name");
  return;
}

var passwordBytes = new byte[blobSize];
Marshal.Copy(blob, passwordBytes, 0, (int)blobSize);

var decrypted = ProtectedData.Unprotect(passwordBytes, null, DataProtectionScope.CurrentUser);
return Encoding.Unicode.GetString(decrypted);

写:

var bytes = Encoding.Unicode.GetBytes(password);
var encypted = ProtectedData.Protect(bytes, null, DataProtectionScope.CurrentUser);

//construct and set all the other properties on Credential...

credential.credentialBlobSize = (uint)bytes.Length;
credential.credentialBlob = GetPtrToArray(bytes);

if (!Win32.CredWrite(ref credential, 0))
  throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());

GetPtrToArray

 private static IntPtr GetPtrToArray(byte[] bytes)
 {
   var handle = Marshal.AllocHGlobal(bytes.Length);
   Marshal.Copy(bytes, 0, handle, bytes.Length);
   return handle;
 }

我尝试过的事情是:

  1. credentialBlob 更改为 byte[],这将在 CredentialRead 期间因 PtrToStructure 中的编组错误而失败
  2. credentialBlob 更改为字符串,并在解密之前使用Unicode.GetBytes(),这会产生一个2 字符的字符串,假定为IntPtr 的内容

我认为问题在于共享 byte[] 的内存,即生成与CredWrite() 一起使用的IntPtr 的方法。之后尝试读取凭据时,blobSizeblob 均为 0(即指向 blob 的空指针)。

为了完整起见,这是从 .net 4.6.1 代码库运行的,我可以毫无问题地存储未加密的字符串(使用 Marshal.StringToCoTaskMemUni(password))。

你能帮忙吗?

【问题讨论】:

  • type 字段中有什么内容? Iirc 你可以写一个纯文本密码或读取加密字节,这取决于 CRED_TYPE_???? .

标签: c# pinvoke credential-manager


【解决方案1】:

原来我忘记了 Credential 是一个结构,以下几行在一个单独的函数中(上面已简化),因此这些字段永远不会保留它们的值。

void SetPasswordProperties(Win32.Credential credential)
{
  credential.credentialBlobSize = (uint)bytes.Length;
  credential.credentialBlob = GetPtrToArray(bytes);
}

使用ref 参数解决了这个问题,如下所示:

void SetPasswordProperties(ref Win32.Credential credential)

【讨论】: