使用C#从Windows凭据存储中获取凭据

50

我只想查询凭据存储区(或在Windows 8中称为Vault),并获取登录数据。在这种情况下,MSDN真的没有什么帮助,而且我也不想使用任何C++的P/Invoke方法。

我知道类似的问题在这里已经问过几次,但那些解决方案都不适用于我的情况。我不使用Metro应用程序编程,所以像PasswordVault之类的东西(看起来)不可用。我只创建一个简单的C# WPF桌面应用程序。

理想情况下,它应该在几个Windows版本中工作,但首选Windows 8。

更具体地说,我想从Outlook的CRM插件中查询存储的数据,以便自动让我的应用程序登录到CRM服务器,而无需用户请求他/她的凭据。也就是说,如果这是可能的...

那么我如何访问Windows凭据存储区?

4个回答

65

我一直在使用一个名为 CredentialManagement 的 NuGet 库。

使用方法非常简单。我稍微包装了一下,但可能不是必要的:

public static class CredentialUtil
{
    public static UserPass GetCredential(string target)
    {
        var cm = new Credential {Target = target};
        if (!cm.Load())
        {
            return null;
        }

        // UserPass is just a class with two string properties for user and pass
        return new UserPass(cm.Username, cm.Password);
    }

    public static bool SetCredentials(
         string target, string username, string password, PersistanceType persistenceType)
    {
       return new Credential {Target = target,
                              Username = username,
                              Password = password,
                              PersistanceType =  persistenceType}.Save();
    }

    public static bool RemoveCredentials(string target)
    {
        return new Credential { Target = target }.Delete();
    }
}

示例用法:

CredentialUtil.SetCredentials("FOO", "john", "1234", PersistanceType.LocalComputer);
var userpass = CredentialUtil.GetCredential("FOO");
Console.WriteLine($"User: {userpass.Username} Password: {userpass.Password}");
CredentialUtil.RemoveCredentials("FOO");
Debug.Assert(CredentialUtil.GetCredential("FOO") == null);

如果您有兴趣自己实现它,请浏览源代码: http://credentialmanagement.codeplex.com/SourceControl/latest 诀窍在于没有C# API进入凭据管理器。这个库很好地包装了其他.dll入口点。 :-)

我不理解的是PasswordVault类被标记为[DualApiPartition()]属性,这意味着它也可以在桌面应用程序(WPF)中使用。这是他们类页面上的一个错别字吗?https://msdn.microsoft.com/en-us/library/windows/apps/windows.security.credentials.passwordvault.aspx - Ebsan
4
这个答案有两个考虑因素,并不一定是库的缺陷,而是Windows凭据管理器的缺陷。你可以基本上加载和解密机器上任何凭据的用户名和密码,其他应用程序也可以这样做。这在任何意义上都不安全或安全。另外,正如其他用户所提到的,还存在明文字符串的使用。然而,仅凭第一个观点就应该让您对整个系统非常怀疑。 - user562566
1
我发现这个页面对于理解Credential类中不同事物的含义非常有帮助:https://msdn.microsoft.com/en-us/library/windows/desktop/aa374788(v=vs.85).aspx - Matt Thomas
6
可怕!我只需执行CredentialManager.GetCredentials("outlook.office365.com"); 就可以获取我的明文用户名/密码! - Kevin Doyon
1
我知道这是一个非常老的答案,但有没有人能够使用这个库以这种格式 \\192.168.0.1 存储 Windows凭据,而不是 通用凭据 - user1676874
它在服务器上是无用的,因为只有创建它的帐户才能读取它。因此,您无法在 Web 应用程序中使用它。 - Himalaya Garg

4

这个方法适用于 Windows Server 2012。我没有 Windows 8 系统可以测试。

在 .NET 桌面应用程序中使用 Windows 8 WinRT API

简而言之:

  1. 卸载项目文件
  2. 编辑它
  3. PropertyGroup 部分添加 <TargetPlatformVersion>8.0</TargetPlatformVersion>
  4. 添加 Windows.Security 的引用(你将会看到一个 Windows 库列表)
  5. 添加位于 C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETCore\v4.5System.Runtime.WindowsRuntime.dll

你可以从 这里 使用这个方法:

private string resourceName = "My App";
private string defaultUserName;

private void Login()
{
    var loginCredential = GetCredentialFromLocker();

    if (loginCredential != null)
    {
        // There is a credential stored in the locker.
        // Populate the Password property of the credential
        // for automatic login.
        loginCredential.RetrievePassword();
    }
    else
    {
        // There is no credential stored in the locker.
        // Display UI to get user credentials.
        loginCredential = GetLoginCredentialUI();
    }

    // Log the user in.
    ServerLogin(loginCredential.UserName, loginCredential.Password);
}


private Windows.Security.Credentials.PasswordCredential GetCredentialFromLocker()
{
    Windows.Security.Credentials.PasswordCredential credential = null;

    var vault = new Windows.Security.Credentials.PasswordVault();
    var credentialList = vault.FindAllByResource(resourceName);
    if (credentialList.Count > 0)
    {
        if (credentialList.Count == 1)
        {
            credential = credentialList[0];
        }
        else
        {
            // When there are multiple usernames,
            // retrieve the default username. If one doesn’t
            // exist, then display UI to have the user select
            // a default username.

            defaultUserName = GetDefaultUserNameUI();

            credential = vault.Retrieve(resourceName, defaultUserName);
        }
    }
    return credential;
}

2

Randy的答案使用System.String来存储密码,这是不安全的。您应该使用System.Security.SecureString

如果您只是阅读.NET Framework 2.0中的凭据管理,那么您会更好。


2
你是正确的,通常我使用SecureString。但是你提供的MSDN页面并没有回答这个问题。它只回答了如何处理凭据/凭据集和密码提示,而不是如何处理Windows内置的密码保险库。 - Kirschi

0

使用CredentialManagement(查看答案{{link1:使用C#从Windows凭据存储中检索凭据}})。

也可以使用PowerShell:

CredMan.ps1 https://gallery.technet.microsoft.com/scriptcenter/PowerShell-Credentials-d44c3cde

我无法列出所有存储的凭据。

using CredentialManagement;
using System.Diagnostics;

namespace UnitTestProject1
{
    [TestClass]
    public class CredentialTests
    {

        [TestMethod]
        public void Set_Credentials_for_older_domain_whe_migration_to_new_domain()
        {
            var accesos = new List<string> {
            "intranet",
            "intranet.xxxxx.net",
            "intranet.zzzzzzzz.com",
            "intranetescritorio.zzzzzzzz.net",
            "more...",
            };

            accesos.ForEach(acceso => SaveCredential(acceso));
        }

        private static Credential SaveCredential(string CredentialName)
        {
            var UserName = @"OLDERDOMAIN\user";
            var Password = "pass";

            var cm = new Credential { Target = CredentialName, Type = CredentialType.DomainPassword };
            if (cm.Exists())
            {
                cm.Load();
                Console.WriteLine("Credential " + cm.Target + ". Data: " + cm.Username + " " + cm.Password);

                //if (cm.Type == CredentialType.Generic)  cm.Delete();

                return cm;
            }

            cm = new Credential
            {
                Target = CredentialName,
                Type = CredentialType.DomainPassword,
                PersistanceType = PersistanceType.Enterprise,
                Username = UserName,
                Password = Password
            };
            cm.Save();
            return cm;
        }
    }

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