You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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:
    1. 在Win11上找到默认密钥存储路径%LOCALAPPDATA%\ASP.NET\DataProtection-Keys,复制密钥文件;
    2. 在Win2016服务器上,将密钥文件放到应用可访问的路径,配置DataProtection加载该路径的密钥。
  • 重新加密配置值:在Windows Server 2016上运行加密逻辑,重新生成配置文件中的加密值,确保使用服务器本地密钥环。
  • 检查应用权限:确保服务器上应用程序池的运行身份拥有访问DataProtection密钥存储目录的权限。

内容的提问来源于stack exchange,提问作者WorkJ

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 21:44:51