C#程序在Windows系统上安全的存储密码的两种方式:
- DPAPI(Data Protection API),微软提供的一种操作系统级的数据加密机制
- Windows 凭据管理器(Windows Credential Manager)
下面详细介绍这两种方式。
Windows 凭据管理器
在C#程序中将用户密码保存到 Windows 凭据管理器需要用到一个nuget包CredentialManagement。CredentialManagement 用于在 .NET 应用程序中安全地与 Windows 凭据管理器(Windows Credential Manager)进行交互。它封装了 Windows API(如 CredWrite、CredRead、CredDelete 等),让开发者能以面向对象的方式轻松保存、读取和删除凭据。
📦 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 类(主要操作类)
| 属性 | 类型 | 说明 |
|---|---|---|
Target | string | 凭据的唯一标识(如 "MyApp.Database") |
Username | string | 用户名(可选,某些场景可为空) |
Password | string | 密码(敏感信息) |
Type | CredentialType | Generic 或 DomainPassword |
Persistance | PersistanceType | 存储持久性(见下文) |
持久性选项(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 的基本特征
- 用户范围保护(CurrentUser):只有当前登录用户能解密。
- 机器范围保护(LocalMachine):本机上任何用户都能解密(不推荐用于密码)。
- 加密密钥由 Windows 自动管理(基于用户登录凭据或机器密钥)。
- 无需存储或传输密钥,避免密钥泄露风险。
⚠️ 注意:如果用户重装系统或更换账户,用 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 凭据管理器 | 用户账户密码、服务凭据 | 最高 | 用户可管理,系统集成好 |
✅ 最佳实践建议:
- 普通应用配置 → 用 DPAPI 加密后存入用户配置文件或注册表。
- 用户登录凭据 → 用 Windows 凭据管理器。