在 WPF 应用程序中加密凭据

2023-12-14

在 WPF 应用程序中,我想提供典型的“记住我”选项来记住凭据并在下次启动应用程序时自动使用它们。

使用单向哈希显然不是一个选择,虽然我可以存储凭据隔离存储 or 在注册表中,加密凭证时需要处理一个问题。

如果我使用对称密钥加密算法,我需要将密钥存储在某处。例如,如果密钥硬编码在内存中,那么我想分解 .NET 程序集并找到它会很容易。

在 .NET 中加密凭据并保证其安全、使加密密钥完全遥不可及的最佳方法是什么?


以下是我的博客文章的摘要:如何在 Windows 上存储密码?

您可以使用数据保护 API 及其 .NET 实现(受保护的数据) 对密码进行加密。这是一个例子:

public static string Protect(string str)
{
    byte[] entropy = Encoding.ASCII.GetBytes(Assembly.GetExecutingAssembly().FullName);
    byte[] data = Encoding.ASCII.GetBytes(str);
    string protectedData = Convert.ToBase64String(ProtectedData.Protect(data, entropy, DataProtectionScope.CurrentUser));
    return protectedData;
}

public static string Unprotect(string str)
{
    byte[] protectedData = Convert.FromBase64String(str);
    byte[] entropy = Encoding.ASCII.GetBytes(Assembly.GetExecutingAssembly().FullName);
    string data = Encoding.ASCII.GetString(ProtectedData.Unprotect(protectedData, entropy, DataProtectionScope.CurrentUser));
    return data;
}

或者您可以使用 Windows 凭据管理器(这是我更喜欢的方式,因为它允许用户备份/恢复/编辑其凭据,即使您的应用程序没有此类功能)。我创建了一个 NuGet 包Meziantou.Framework.Win32.CredentialManager。如何使用它:

CredentialManager.WriteCredential("ApplicationName", "username", "Pa$$w0rd", CredentialPersistence.Session);

var cred = CredentialManager.ReadCredential("ApplicationName");
Assert.AreEqual("username", cred.UserName);
Assert.AreEqual("Pa$$w0rd", cred.Password);

CredentialManager.DeleteCredential("ApplicationName");

使用本机 API 包装器的原始答案(更新的版本是可以在 GitHub 上找到):

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using System.Text;
using System.ComponentModel;

public static class CredentialManager
{
    public static Credential ReadCredential(string applicationName)
    {
        IntPtr nCredPtr;
        bool read = CredRead(applicationName, CredentialType.Generic, 0, out nCredPtr);
        if (read)
        {
            using (CriticalCredentialHandle critCred = new CriticalCredentialHandle(nCredPtr))
            {
                CREDENTIAL cred = critCred.GetCredential();
                return ReadCredential(cred);
            }
        }

        return null;
    }

    private static Credential ReadCredential(CREDENTIAL credential)
    {
        string applicationName = Marshal.PtrToStringUni(credential.TargetName);
        string userName = Marshal.PtrToStringUni(credential.UserName);
        string secret = null;
        if (credential.CredentialBlob != IntPtr.Zero)
        {
            secret = Marshal.PtrToStringUni(credential.CredentialBlob, (int)credential.CredentialBlobSize / 2);
        }

        return new Credential(credential.Type, applicationName, userName, secret);
    }

    public static int WriteCredential(string applicationName, string userName, string secret)
    {
        byte[] byteArray = Encoding.Unicode.GetBytes(secret);
        if (byteArray.Length > 512)
            throw new ArgumentOutOfRangeException("secret", "The secret message has exceeded 512 bytes.");

        CREDENTIAL credential = new CREDENTIAL();
        credential.AttributeCount = 0;
        credential.Attributes = IntPtr.Zero;
        credential.Comment = IntPtr.Zero;
        credential.TargetAlias = IntPtr.Zero;
        credential.Type = CredentialType.Generic;
        credential.Persist = (UInt32)CredentialPersistence.Session;
        credential.CredentialBlobSize = (UInt32)Encoding.Unicode.GetBytes(secret).Length;
        credential.TargetName = Marshal.StringToCoTaskMemUni(applicationName);
        credential.CredentialBlob = Marshal.StringToCoTaskMemUni(secret);
        credential.UserName = Marshal.StringToCoTaskMemUni(userName ?? Environment.UserName);

        bool written = CredWrite(ref credential, 0);
        int lastError = Marshal.GetLastWin32Error();

        Marshal.FreeCoTaskMem(credential.TargetName);
        Marshal.FreeCoTaskMem(credential.CredentialBlob);
        Marshal.FreeCoTaskMem(credential.UserName);

        if (written)
            return 0;

        throw new Exception(string.Format("CredWrite failed with the error code {0}.", lastError));
    }

    public static IReadOnlyList<Credential> EnumerateCrendentials()
    {
        List<Credential> result = new List<Credential>();

        int count;
        IntPtr pCredentials;
        bool ret = CredEnumerate(null, 0, out count, out pCredentials);
        if (ret)
        {
            for (int n = 0; n < count; n++)
            {
                IntPtr credential = Marshal.ReadIntPtr(pCredentials, n * Marshal.SizeOf(typeof(IntPtr)));
                result.Add(ReadCredential((CREDENTIAL)Marshal.PtrToStructure(credential, typeof(CREDENTIAL))));
            }
        }
        else
        {
            int lastError = Marshal.GetLastWin32Error();
            throw new Win32Exception(lastError);
        }

        return result;
    }

    [DllImport("Advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)]
    static extern bool CredRead(string target, CredentialType type, int reservedFlag, out IntPtr credentialPtr);

    [DllImport("Advapi32.dll", EntryPoint = "CredWriteW", CharSet = CharSet.Unicode, SetLastError = true)]
    static extern bool CredWrite([In] ref CREDENTIAL userCredential, [In] UInt32 flags);

    [DllImport("advapi32", SetLastError = true, CharSet = CharSet.Unicode)]
    static extern bool CredEnumerate(string filter, int flag, out int count, out IntPtr pCredentials);

    [DllImport("Advapi32.dll", EntryPoint = "CredFree", SetLastError = true)]
    static extern bool CredFree([In] IntPtr cred);



    private enum CredentialPersistence : uint
    {
        Session = 1,
        LocalMachine,
        Enterprise
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    private struct CREDENTIAL
    {
        public UInt32 Flags;
        public CredentialType Type;
        public IntPtr TargetName;
        public IntPtr Comment;
        public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
        public UInt32 CredentialBlobSize;
        public IntPtr CredentialBlob;
        public UInt32 Persist;
        public UInt32 AttributeCount;
        public IntPtr Attributes;
        public IntPtr TargetAlias;
        public IntPtr UserName;
    }

    sealed class CriticalCredentialHandle : CriticalHandleZeroOrMinusOneIsInvalid
    {
        public CriticalCredentialHandle(IntPtr preexistingHandle)
        {
            SetHandle(preexistingHandle);
        }

        public CREDENTIAL GetCredential()
        {
            if (!IsInvalid)
            {
                CREDENTIAL credential = (CREDENTIAL)Marshal.PtrToStructure(handle, typeof(CREDENTIAL));
                return credential;
            }

            throw new InvalidOperationException("Invalid CriticalHandle!");
        }

        protected override bool ReleaseHandle()
        {
            if (!IsInvalid)
            {
                CredFree(handle);
                SetHandleAsInvalid();
                return true;
            }

            return false;
        }
    }
}

public enum CredentialType
{
    Generic = 1,
    DomainPassword,
    DomainCertificate,
    DomainVisiblePassword,
    GenericCertificate,
    DomainExtended,
    Maximum,
    MaximumEx = Maximum + 1000,
}

public class Credential
{
    private readonly string _applicationName;
    private readonly string _userName;
    private readonly string _password;
    private readonly CredentialType _credentialType;

    public CredentialType CredentialType
    {
        get { return _credentialType; }
    }

    public string ApplicationName
    {
        get { return _applicationName; }
    }

    public string UserName
    {
        get { return _userName; }
    }

    public string Password
    {
        get { return _password; }
    }

    public Credential(CredentialType credentialType, string applicationName, string userName, string password)
    {
        _applicationName = applicationName;
        _userName = userName;
        _password = password;
        _credentialType = credentialType;
    }

    public override string ToString()
    {
        return string.Format("CredentialType: {0}, ApplicationName: {1}, UserName: {2}, Password: {3}", CredentialType, ApplicationName, UserName, Password);
    }
}

Usage:

WriteCredential("ApplicationName", "Meziantou", "Passw0rd");
Console.WriteLine(ReadCredential("Demo"));
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 WPF 应用程序中加密凭据 的相关文章

随机推荐

  • 使用 Selenium WebDriver 进行 PrimeFaces 文件上传测试

    我已经成功测试了 fileUploadSimplehttp www primefaces org showcase ui fileUploadSimple jsf使用 webElement sendKeys 方法 它不适用于自动上传 有没有
  • 使用 JSON 对象作为负载向 REST API 发出 POST 请求

    我正在尝试使用具有 JSON 负载的 POST 请求从 REST API 获取 JSON 响应 应在发送前转换为 URL 编码文本 我已经按照一些教程来实现该过程 但收到状态代码 400 的错误 我可能没有对给定的 JSON 字符串进行编码
  • 如何在 iframe 上设置“X-Frame-Options”?

    如果我创建一个iframe像这样 var dialog div align center div dialog 如何使用 JavaScript 修复以下错误 拒绝展示 https www google com ua gws rd ssl 在
  • 执行 chrome.extension.getBackgroundPage() 时抛出错误

    我正在开发我的第一个扩展 并尝试创建一个简单的扩展来在页面上注入可拖动的 div 这很好用 但我想保留 div 在后台页面上的位置 我也在尝试本地存储 但想了解为什么这不起作用 我不需要按钮 因此没有创建 popup html 文件 我相信
  • XPath 查找节点是否存在

    使用 XPath 查询如何查找节点 标签 是否存在 例如 如果我需要确保网站页面具有正确的基本结构 例如 html body and html head title
  • 联邦学习训练期间模型性能没有提高

    我已关注这个emnist教程创建图像分类实验 7 个类别 目的是使用 TFF 框架在 3 个数据孤岛上训练分类器 在训练开始之前 我使用以下命令将模型转换为 tf keras 模型tff learning assign weights to
  • openapi java 生成器将 LocalDate 序列化为数组而不是完整日期

    我在 spring 项目中使用 OpenAPI java 生成器 1 和 library resttemplate dateLibrary java8 从规范生成客户端 对于规范中的属性 targetDate type string for
  • 如何在 launch.json 的 Visual Studio Code 中反转 ${relativeFile} 中的反斜杠?

    我正在尝试配置 Windows Visual Studio Codelaunch json推出jest测试当前文件 获取我使用的路径 relativeFile 变量给出一个带有反斜杠的字符串 如下所示 src services some s
  • 为什么unique_ptr重载reset(pointer p = point())和reset(nullptr_t)?

    根据http en cppreference com w cpp memory unique ptr reset void reset pointer ptr pointer template lt class U gt void rese
  • exec() 和变量范围[重复]

    这个问题在这里已经有答案了 我确信这个问题已经被问过并得到回答 但我找不到具体的 我刚刚开始学习Python 但我不明白变量作用域问题 我已将问题简化为以下内容 Case 1 def lev1 exec aaa 123 print lev1
  • dart-server/angulardart 和 CORS 的问题

    我在我的服务器上使用 dart 并使用 angulardart 作为我的客户端 我可以通过我的服务器上的 http get 请求数据 工作正常 但我无法让 POST 工作 服务器 服务器使用 开始 https github com lviv
  • Grails 中按关联计数排序

    我有很多 Topic 对象 每个 Topic 有很多帖子 Post 如何根据帖子数对所有主题对象进行排序 您可以使用 size 函数在一个 HQL 查询中完成此操作 这样您就可以在一个查询中获取 Topic 实例 SELECT topic
  • 设置带有 a-frame 的加载动画

    我正在使用框架加载全景照片 示例代码如下 加载照片时会显示白屏 并持续几秒钟 这会造成糟糕的用户体验 我想在加载照片时添加加载动画 但找不到任何有用的指南 有人可以帮忙吗
  • 如何在没有 APD 的情况下覆盖现有的 PHP 函数?

    我想覆盖 PHP 中已经声明的几个函数 我正在使用的应用程序充满了遗留代码 包括对eval 因此仅替换代码中调用函数的标识符可能还不够 是否可以更改已声明的函数 而不使用高级 PHP 调试器在 PHP5 中 是的 通过使用 runkit f
  • angularjs表单输入建议

    我对 AngularJS 中的表单有疑问 经典 html 和 php 示例
  • 等待加载 angularjs 指令模板

    我想做的是推迟加载指令的 Angular js 模板 直到我真正需要它为止 我什至可能根本不需要它 有没有一种方法可以让我只在需要时加载指令模板 服务可以做到这一点吗 我的应用程序已经加载了很多指令模板 我想避免加载太多的东西 除非我需要它
  • 静态强制两个对象是从相同的(Int)“种子”创建的

    在我正在开发的库中 我有一个类似于以下内容的 API data Collection a Collection Seed etc type Seed Int newCollection Seed gt IO Collection a new
  • 在二进制文件中搜索字符串的代码

    前几天我问过这个问题 如何在二进制文件中查找 ANSI 字符串 我得到了一个非常好的answer 后来变成了一个更难的问题 输入迭代器可以用在需要前向迭代器的地方吗 现在确实不是我能理解的水平 我仍在学习 C 并且正在寻找一种在二进制文件中
  • socket.io 硬编码与动态连接 url

    为什么这里的大多数教程 示例和问题都使用本地 IP 或localhost 或任何其他硬编码路径 如下所示 var socket io connect http 127 0 0 1 3700 而不是简单地 var socket io conn
  • 在 WPF 应用程序中加密凭据

    在 WPF 应用程序中 我想提供典型的 记住我 选项来记住凭据并在下次启动应用程序时自动使用它们 使用单向哈希显然不是一个选择 虽然我可以存储凭据隔离存储 or 在注册表中 加密凭证时需要处理一个问题 如果我使用对称密钥加密算法 我需要将密