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

WPF应用Azure认证出现CryptographicException数据无效错误排查

问题原因与修复方案

核心错误:加密/解密的熵(Entropy)不匹配

你在StoreAccessToken方法中使用AccessToken本身作为熵(Encoding.UTF8.GetBytes(accessToken)),但在RetrieveAccessToken中却使用资源名作为熵(Encoding.Unicode.GetBytes(_resourceName)),两者完全不一致。ProtectedData.Protect和Unprotect必须使用相同的熵才能正常解密,这是抛出CryptographicException的直接原因。

修复步骤

1. 统一加密和解密的熵

熵(entropy)的作用是增加加密数据的安全性,它不需要保密,但必须在加密和解密时保持一致。建议使用固定的、与应用相关的字符串作为熵,修改后的SecureStorage类如下:

internal static class SecureStorage
{
    private const string _resourceName = "CDBManagerAccessToken";
    private const string _entropyKey = "YourAppSpecificEntropyKey"; // 替换为应用专属固定字符串
    private static readonly object _lockObject = new object();

    public static void StoreAccessToken(string accessToken, DateTimeOffset expirationTime)
    {
        // 统一用UTF8编码生成固定熵
        byte[] entropy = Encoding.UTF8.GetBytes(_entropyKey);
        // 序列化token和过期时间,统一加密存储
        var tokenData = new { Token = accessToken, Expiration = expirationTime };
        string serializedData = System.Text.Json.JsonSerializer.Serialize(tokenData);
        byte[] encryptedData = ProtectedData.Protect(Encoding.Unicode.GetBytes(serializedData), entropy, DataProtectionScope.CurrentUser);

        lock (_lockObject)
        {
            Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

            if (config.AppSettings.Settings[_resourceName] == null)
            {
                config.AppSettings.Settings.Add(_resourceName, Convert.ToBase64String(encryptedData));
            }
            else
            {
                config.AppSettings.Settings[_resourceName].Value = Convert.ToBase64String(encryptedData);
            }

            config.Save(ConfigurationSaveMode.Modified);
            ConfigurationManager.RefreshSection("appSettings"); // 刷新配置确保读取最新值
        }  
    }

    public static (string Token, DateTimeOffset Expiration) RetrieveAccessToken()
    {
        byte[] entropy = Encoding.UTF8.GetBytes(_entropyKey);

        lock(_lockObject)
        {
            Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            string encryptedData = config.AppSettings.Settings[_resourceName]?.Value;

            if (string.IsNullOrEmpty(encryptedData))
                return (null, DateTimeOffset.MinValue);

            byte[] encryptedBytes = Convert.FromBase64String(encryptedData);
            byte[] decryptedData = ProtectedData.Unprotect(encryptedBytes, entropy, DataProtectionScope.CurrentUser);
            string serializedData = Encoding.Unicode.GetString(decryptedData);
            
            var tokenData = System.Text.Json.JsonSerializer.Deserialize<(string Token, DateTimeOffset Expiration)>(serializedData);
            return tokenData;
        }   
    }
}

2. 修复AuthenticationManager的认证状态逻辑

原代码中IsUserAuthenticated仅读取token但未更新过期时间,导致状态判断失效。同时调整token存储逻辑,同步保存过期时间:

internal class AuthenticationManager
{
    private readonly string _clientId;
    private readonly string _tenantId;
    private readonly string _redirectUri;
    private string _authToken;
    private DateTimeOffset _tokenExpirationTime;
    private readonly TimeSpan _authenticationDuration;

    public AuthenticationManager()
    {
        _clientId = ConfigurationManager.AppSettings["ClientId"];
        _tenantId = ConfigurationManager.AppSettings["TenantId"];
        _redirectUri = ConfigurationManager.AppSettings["RedirectUri"];
        _authenticationDuration = TimeSpan.FromDays(1);
        // 初始化时读取已存储的token和过期时间
        var storedData = SecureStorage.RetrieveAccessToken();
        _authToken = storedData.Token;
        _tokenExpirationTime = storedData.Expiration;
    }

    public bool IsUserAuthenticated()
    {
        return !string.IsNullOrEmpty(_authToken) && DateTime.UtcNow < _tokenExpirationTime;
    }

    public async Task<string> AuthenticateUser()
    {
        try
        {
            if (!IsUserAuthenticated())
            {
                var pca = PublicClientApplicationBuilder.Create(_clientId)
                .WithAuthority($"https://login.microsoftonline.com/{_tenantId}")
                .WithRedirectUri(_redirectUri)
                .Build();

                var scopes = new[] { "User.Read" };

                var authResult = await pca.AcquireTokenInteractive(scopes).ExecuteAsync();

                _authToken = authResult.AccessToken;
                // 优先使用Azure AD返回的token过期时间,比手动加1天更准确
                _tokenExpirationTime = authResult.ExpiresOn;
                // 若需强制1天有效期,替换为:_tokenExpirationTime = DateTimeOffset.UtcNow.Add(_authenticationDuration);

                SecureStorage.StoreAccessToken(_authToken, _tokenExpirationTime);

                return authResult.AccessToken;
            }
            else
            {
                return _authToken;
            }
            
        }
        catch (Exception)
        {
            return null;
        }            
    }     
}

额外注意事项

  • 熵不能使用动态值(如AccessToken),必须固定,否则下次启动无法解密。
  • 优先使用Azure AD返回的ExpiresOn作为过期时间,避免手动设置的误差。
  • 修改配置后调用ConfigurationManager.RefreshSection,确保读取最新存储值。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 01:13:10