C# DataProtector解密函数在Logon/TokenService场景部署异常排查
问题概述
代码从配置文件读取加密值并解密,用于Windows登录和令牌服务相关函数。Windows 11桌面调试时功能正常,但部署到Windows Server 2016后解密失败。直接硬编码解密后的配置值,服务器上可正常运行;调试时解密后的变量与硬编码值完全一致。日志报错:Decryption failed: The key {a79dd801-99f7-4ca9-a0a2-ad571f9ad08f} was not found in the key ring
相关代码
public class EncryptionService : IEncryptionService { private readonly IDataProtector _protector; private static readonly ILog _log = LogManager.GetLogger(typeof(EncryptionService)); // 构造函数通过依赖注入初始化IDataProtector public EncryptionService(IDataProtectionProvider provider) { // 唯一字符串确保不同用途的保护策略隔离 _protector = provider.CreateProtector("MyPurposeIs..."); } // 加密明文数据的方法 public string EncryptData(string plainText) { return _protector.Protect(plainText); } // 解密加密数据的方法 public string DecryptData(string encryptedData) { try { return _protector.Unprotect(encryptedData); } catch (Exception ex) { // 处理解密失败异常(如数据篡改或无效) return $"Decryption failed: {ex.Message}"; } } } public class Utilities : IUtilities { [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, out SafeAccessTokenHandle phToken); public IConfiguration _config; const int LOGON32_PROVIDER_DEFAULT = 0; const int LOGON32_LOGON_NEW_CREDENTIALS = 9; private static readonly ILog _log = LogManager.GetLogger(typeof(Utilities)); private readonly IEncryptionService _encryptionService; public Utilities(IConfiguration configuration, IEncryptionService encryptionService) { _config = configuration; _encryptionService = encryptionService; } // 从配置文件读取并解密后的域名、用户名、密码传入此函数 public string[]? ImpersonateAndGetFiles(string domain, string username, string password, string sourcePath, string fileSpec) { string[]? resultArray = null; try { SafeAccessTokenHandle? safeAccessTokenHandle; bool returnValue = LogonUser(username, domain, password, LOGON32_LOGON_NEW_CREDENTIALS, LOGON32_PROVIDER_DEFAULT, out safeAccessTokenHandle); if (false == returnValue) { int ret = Marshal.GetLastWin32Error(); _log.Error($"LogonUser failed with error code : {ret}"); return resultArray; } if (safeAccessTokenHandle is not null) { _log.Info("Logon Successful. Getting file list."); #pragma warning disable CA1416 // Validate platform compatibility WindowsIdentity.RunImpersonated(safeAccessTokenHandle, () => { resultArray = Directory.GetFiles(sourcePath, fileSpec); return Task.CompletedTask; }); #pragma warning restore CA1416 // Validate platform compatibility } } catch (Exception ex) { _log.Error($"Error: {ex}"); return resultArray; } return resultArray; } } // 调用函数的代码 string? domain = _config["domainCL"]; string? userName = _config["userNameCL"]; string? password = _config["passwordCL"]; string? unEncryptedDomain = null; string? unEncryptedUserName = null; string? unEncryptedPassword = null; unEncryptedDomain = _encryptionService.DecryptData(domain); unEncryptedUserName = _encryptionService.DecryptData(userName); unEncryptedPassword = _encryptionService.DecryptData(password); fileList = _utils.ImpersonateAndGetFiles(unEncryptedDomain, unEncryptedUserName, unEncryptedPassword, folder, "*.xml");
问题原因与解决方案
原因
.NET DataProtection默认密钥存储机制在Windows 11和Windows Server 2016上存在差异,加密操作在Windows 11完成后,生成的密钥仅保存在本地机器的密钥环中,Windows Server 2016无法访问该密钥,导致解密失败。
解决方案
- 统一密钥存储位置:配置DataProtection使用共享持久化存储(如文件系统),确保加密和解密共用同一套密钥。示例配置(Program.cs中):
builder.Services.AddDataProtection() .PersistKeysToFileSystem(new DirectoryInfo(@"\\shared-server\dp-keys")) .SetApplicationName("YourAppName");
- 导出导入密钥:从Windows 11导出DataProtection密钥,导入到Windows Server 2016:
- 在Win11上找到默认密钥存储路径
%LOCALAPPDATA%\ASP.NET\DataProtection-Keys,复制密钥文件; - 在Win2016服务器上,将密钥文件放到应用可访问的路径,配置DataProtection加载该路径的密钥。
- 在Win11上找到默认密钥存储路径
- 重新加密配置值:在Windows Server 2016上运行加密逻辑,重新生成配置文件中的加密值,确保使用服务器本地密钥环。
- 检查应用权限:确保服务器上应用程序池的运行身份拥有访问DataProtection密钥存储目录的权限。
内容的提问来源于stack exchange,提问作者WorkJ
相关产品推荐
相关产品推荐

