Web API使用DPAPI解密控制台加密密码时密钥找不到问题
为了在Web API中存储用户凭据,我在解决方案内创建了一个控制台应用程序,用于加密数据并写入Web API的appsettings.json。控制台程序代码如下:
var serviceCollection = new ServiceCollection(); serviceCollection.AddDataProtection(o => { o.ApplicationDiscriminator = CoreVariables.SALT; } ).SetApplicationName("test"); var services = serviceCollection.BuildServiceProvider(); var dataProtector = services.GetRequiredService<IDataProtectionProvider>(); var protector = dataProtector.CreateProtector(CoreVariables.DATA_PROTECTION_REASON); //Fill username and password, REMOVE AFTER ENCRYPTION, DONT PUSH IT TO REPOSITORY string username = ""; string password = ""; if (string.IsNullOrEmpty(username)) { throw new Exception("Username cannot be null or empty."); } if (string.IsNullOrEmpty(password)) { throw new Exception("Username cannot be null or empty."); } string protectedUsername = protector.Protect(username); string protectedPassword = protector.Protect(password); string appsettingsFilePath = Path.Combine(Directory.GetParent(Directory.GetCurrentDirectory()).Parent.Parent.Parent.FullName, "My.API", "appsettings.json"); if (!File.Exists(appsettingsFilePath)) { throw new Exception($"Cannot find file in path: {appsettingsFilePath}"); } var appsettingsContent = File.ReadAllText(appsettingsFilePath); var appsettingsJson = JsonConvert.DeserializeObject<Dictionary<string, object>>(appsettingsContent); appsettingsJson["User"] = protectedUsername; appsettingsJson["Secret"] = protectedPassword; appsettingsJson["SecretLastChanged"] = DateTime.Now.ToString(); var updatedJson = JsonConvert.SerializeObject(appsettingsJson, Formatting.Indented); File.WriteAllText(appsettingsFilePath, updatedJson);
部署应用后,遇到异常提示:The key was not found in the key ring。
Web API中的相关配置和解密代码如下:
Program.cs配置:
builder.Services.AddDataProtection(o => { o.ApplicationDiscriminator = CoreVariables.SALT; } ).SetApplicationName("test");
解密服务代码:
public MyService(IDataProtectionProvider provider) { _protector = provider.CreateProtector(CoreVariables.DATA_PROTECTION_REASON); } public async Task<string> Authentication(string userName, string password) { string authUsername = _protector.Unprotect(userName); string authPassword = _protector.Unprotect(password); }
请问该加密是否与本地计算机数据相关?部署到服务器后解密是否无法正常工作?
是的,你当前的加密实现完全和本地计算机绑定,部署到服务器后必然无法解密,原因及解决方案如下:
核心原因
ASP.NET Core Data Protection默认会将加密密钥存储在本地计算机的文件系统中:
- Windows系统:
%LOCALAPPDATA%\ASP.NET\DataProtection-Keys - Linux/macOS系统:
~/.aspnet/DataProtection-Keys
控制台程序在本地加密时生成的密钥仅保存在你本地电脑的上述路径,服务器环境中没有这个密钥,因此解密时会抛出「密钥在密钥环中找不到」的异常。你配置的ApplicationDiscriminator和SetApplicationName只是用于应用隔离,不会改变密钥的存储位置。
解决方案
要让本地加密的内容能在服务器正常解密,必须让双方共享同一个密钥环,常用方案有三种:
1. 共享密钥文件存储位置
将本地生成的DataProtection-Keys文件夹复制到服务器对应路径,或者配置控制台和Web API都使用同一个共享存储(如NAS、云存储目录)。配置示例(双方都需要添加):
serviceCollection.AddDataProtection(o => { o.ApplicationDiscriminator = CoreVariables.SALT; }) .SetApplicationName("test") .PersistKeysToFileSystem(new DirectoryInfo(@"\\shared-storage\dataprotection-keys")); // 替换为实际共享路径
2. 使用分布式密钥存储
将密钥存储到分布式服务(如Redis、SQL Server),让本地和服务器都从同一个数据源读取密钥。以Redis为例,先安装Microsoft.AspNetCore.DataProtection.StackExchangeRedis包,再添加配置:
serviceCollection.AddDataProtection(o => { o.ApplicationDiscriminator = CoreVariables.SALT; }) .SetApplicationName("test") .PersistKeysToStackExchangeRedis(ConnectionMultiplexer.Connect("your-redis-connection-string"));
3. 手动导出/导入密钥
在本地生成密钥后导出为XML文件,将文件部署到服务器,配置双方从该文件读取密钥:
- 本地导出密钥(控制台程序中添加):
var keyManager = services.GetRequiredService<IKeyManager>(); var keys = keyManager.GetAllKeys(); foreach (var key in keys) { File.WriteAllText(@"C:\temp\dataprotection-key.xml", key.ToXml()); } - 服务器配置导入密钥(双方都需要添加):
serviceCollection.AddDataProtection(o => { o.ApplicationDiscriminator = CoreVariables.SALT; }) .SetApplicationName("test") .PersistKeysToFileSystem(new DirectoryInfo(@"C:\server-dataprotection-keys")) // 服务器存放XML的路径 .ProtectKeysWithDpapi(); // Windows环境用DPAPI保护密钥文件;Linux/macOS可替换为其他加密方式
注意事项
- 严格遵守注释要求,不要将明文用户名密码提交到代码仓库。
- 密钥存储位置需设置严格的权限控制,避免未授权访问。
内容的提问来源于stack exchange,提问作者dezox

