Skip to content
Go back

C#程序中安全的保存密码

Published:  at  01:40 PM

C#程序在Windows系统上安全的存储密码的两种方式:

下面详细介绍这两种方式。

Windows 凭据管理器

在C#程序中将用户密码保存到 Windows 凭据管理器需要用到一个nuget包CredentialManagementCredentialManagement 用于在 .NET 应用程序中安全地与 Windows 凭据管理器(Windows Credential Manager)进行交互。它封装了 Windows API(如 CredWriteCredReadCredDelete 等),让开发者能以面向对象的方式轻松保存、读取和删除凭据。

📦 NuGet 包名称:CredentialManagement
作者:Brandon Potter
GitHub:https://github.com/brandondahler/CredentialManagement
NuGet 链接:https://www.nuget.org/packages/CredentialManagement/

安装方式

在 Visual Studio 中使用 NuGet 包管理器控制台

Install-Package CredentialManagement

或通过 .NET CLI

dotnet add package CredentialManagement

✅ 支持 .NET Framework(4.0+)和 .NET Core/.NET 5+(Windows 平台)。


核心类与属性

Credential 类(主要操作类)

属性类型说明
Targetstring凭据的唯一标识(如 "MyApp.Database"
Usernamestring用户名(可选,某些场景可为空)
Passwordstring密码(敏感信息)
TypeCredentialTypeGenericDomainPassword
PersistancePersistanceType存储持久性(见下文)

持久性选项(PersistanceType):

说明
Session仅当前登录会话有效(重启后丢失)
LocalComputer保存到本地凭据存储(推荐)
Enterprise保存到域凭据存储(需加入域)

通常使用 PersistanceType.LocalComputer


基本用法示例

1. 保存凭据

using CredentialManagement;

var cred = new Credential
{
    Target = "MyApp.Database",      // 唯一标识,建议用应用名+用途
    Username = "dbuser",
    Password = "My$ecretP@ss123",
    Type = CredentialType.Generic,
    Persistance = PersistanceType.LocalComputer
};

bool saved = cred.Save();
if (saved)
    Console.WriteLine("凭据保存成功!");
else
    Console.WriteLine("保存失败(可能权限不足或目标已存在)");

✅ 凭据将出现在:控制面板 → 用户账户 → 凭据管理器 → Windows 凭据 → 普通凭据


2. 读取凭据

var cred = new Credential { Target = "MyApp.Database" };

if (cred.Load())
{
    Console.WriteLine($"用户名: {cred.Username}");
    Console.WriteLine($"密码: {cred.Password}"); // 自动解密
}
else
{
    Console.WriteLine("凭据不存在或当前用户无权访问。");
}

🔒 解密由 Windows 自动完成,只有加密时的同一用户才能成功读取。


3. 删除凭据

var cred = new Credential { Target = "MyApp.Database" };
bool deleted = cred.Delete();
Console.WriteLine(deleted ? "凭据已删除" : "删除失败(可能不存在)");

DPAPI

在 Windows 平台上,DPAPI(Data Protection API)是微软提供的一种操作系统级的数据加密机制,专门用于安全地存储敏感信息(如密码、密钥、令牌等)。它无需开发者管理加密密钥——密钥由操作系统自动管理,绑定到当前用户或本地机器,安全性高且使用简单。

在 .NET(包括 C#)中,可以通过 System.Security.Cryptography.ProtectedData 类来使用 DPAPI。

需要引用 System.Security.Cryptography.ProtectedData(.NET Framework 默认可用;.NET Core/.NET 5+ 需要安装 NuGet 包 System.Security.Cryptography.ProtectedData)。


DPAPI 的基本特征

⚠️ 注意:如果用户重装系统或更换账户,用 DPAPI 加密的数据将无法解密


C# 中使用 DPAPI 加密/解密数据

加密字符串

public static string EncryptString(string plainText, bool forCurrentUser = true)
{
    if (string.IsNullOrEmpty(plainText))
        return null;

    byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
    DataProtectionScope scope = forCurrentUser ? DataProtectionScope.CurrentUser : DataProtectionScope.LocalMachine;

    byte[] encryptedBytes = ProtectedData.Protect(plainBytes, optionalEntropy: null, scope);
    return Convert.ToBase64String(encryptedBytes);
}

解密字符串

public static string DecryptString(string encryptedBase64, bool forCurrentUser = true)
{
    if (string.IsNullOrEmpty(encryptedBase64))
        return null;

    try
    {
        byte[] encryptedBytes = Convert.FromBase64String(encryptedBase64);
        DataProtectionScope scope = forCurrentUser ? DataProtectionScope.CurrentUser : DataProtectionScope.LocalMachine;

        byte[] plainBytes = ProtectedData.Unprotect(encryptedBytes, optionalEntropy: null, scope);
        return Encoding.UTF8.GetString(plainBytes);
    }
    catch (CryptographicException ex)
    {
        // 解密失败:可能是用户变更、系统重装、或数据被篡改
        throw new InvalidOperationException("无法解密数据。可能当前用户与加密时不同,或数据已损坏。", ex);
    }
}

将加密后的密码存入注册表(安全做法)

// 保存加密后的密码到注册表
string encryptedPwd = EncryptString("user_password");
Registry.CurrentUser.CreateSubKey(@"Software\MyApp")?.SetValue("EncryptedPassword", encryptedPwd);

// 读取并解密
string stored = Registry.CurrentUser.OpenSubKey(@"Software\MyApp")?.GetValue("EncryptedPassword") as string;
if (!string.IsNullOrEmpty(stored))
{
    string password = DecryptString(stored);
    // 使用 password...
}

✅ 这样即使注册表被导出,攻击者也无法获取明文密码(除非以相同用户身份登录并运行解密程序)。


四、增强安全性:使用 Entropy(可选盐值)

DPAPI 支持传入一个 optionalEntropy(字节数组),相当于“应用特定的盐”,即使同一用户、同一数据,不同应用加密结果也不同。

private static readonly byte[] entropy = Encoding.UTF8.GetBytes("MyApp-Secret-Salt-2025");

public static string EncryptWithEntropy(string plainText)
{
    byte[] data = Encoding.UTF8.GetBytes(plainText);
    byte[] encrypted = ProtectedData.Protect(data, entropy, DataProtectionScope.CurrentUser);
    return Convert.ToBase64String(encrypted);
}

public static string DecryptWithEntropy(string encryptedBase64)
{
    byte[] data = Convert.FromBase64String(encryptedBase64);
    byte[] decrypted = ProtectedData.Unprotect(data, entropy, DataProtectionScope.CurrentUser);
    return Encoding.UTF8.GetString(decrypted);
}

🔒 建议使用固定 entropy(硬编码在程序中),但注意:如果程序被反编译,entropy 可能泄露。不过即使泄露,攻击者仍需用户登录上下文才能解密,安全性仍远高于明文存储。


总结:如何安全存储密码?

方案适用场景安全性备注
DPAPI + 配置文件/注册表应用内部敏感数据(如 API 密钥、连接字符串)高(用户绑定)简单易用,.NET 原生支持
Windows 凭据管理器用户账户密码、服务凭据最高用户可管理,系统集成好

最佳实践建议


Share this post on:

Next Post
C#中将Word文档转成PDF