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

C# .NET 6中Fireblocks API客户端报'invalid exp value'求修复

解决Fireblocks API "invalid exp value" 错误的C# .NET 6实现修正

问题根源是JWT标准要求iat(签发时间)和exp(过期时间)必须是Unix时间戳(从1970-01-01 UTC到目标时间的秒数),你的代码直接传入了DateTime对象,导致JWT中的exp值格式不符合Fireblocks API的校验要求。以下是修正后的完整代码:

using System.Security.Cryptography;
using System.Text;
using Newtonsoft.Json.Linq;

public class FireblocksHttpClient
{
    private readonly string baseUrl = "https://api.fireblocks.io";
    private readonly string apiKey;
    private readonly RSA privateKey;
    private readonly HttpClient client;

    public FireblocksHttpClient(string apiKey, string apiSecret)
    {
        this.apiKey = apiKey;
        client = new HttpClient();

        // 清理私钥格式并导入
        byte[] keyBytes = Convert.FromBase64String(apiSecret
            .Replace("-----BEGIN PRIVATE KEY-----", "")
            .Replace("-----END PRIVATE KEY-----", "")
            .Replace("\n", ""));

        var rsa = RSA.Create();
        rsa.ImportPkcs8PrivateKey(keyBytes, out _);
        privateKey = rsa;
    }

    public async Task<JObject> Get(string path)
    {
        var req = new HttpRequestMessage(HttpMethod.Get, $"{baseUrl}{path}");
        req.Headers.Add("X-API-Key", apiKey);
        req.Headers.Add("Authorization", $"Bearer {SignJwt(path)}");

        using var resp = await client.SendAsync(req);
        resp.EnsureSuccessStatusCode(); // 可选:主动抛出HTTP错误便于调试
        var body = await resp.Content.ReadAsStringAsync();
        return JObject.Parse(body);
    }

    public async Task<JObject> Post(string path, JObject data)
    {
        var req = new HttpRequestMessage(HttpMethod.Post, $"{baseUrl}{path}");
        req.Headers.Add("X-API-Key", apiKey);
        var dataStr = data.ToString();
        req.Headers.Add("Authorization", $"Bearer {SignJwt(path, dataStr)}");
        req.Content = new StringContent(dataStr, Encoding.UTF8, "application/json");

        using var resp = await client.SendAsync(req);
        resp.EnsureSuccessStatusCode(); // 可选:主动抛出HTTP错误便于调试
        var body = await resp.Content.ReadAsStringAsync();
        return JObject.Parse(body);
    }

    private string SignJwt(string path) => SignJwt(path, string.Empty);

    private string SignJwt(string path, string dataJSONString)
    {
        using var sha256 = SHA256.Create();
        byte[] dataHash = sha256.ComputeHash(Encoding.UTF8.GetBytes(dataJSONString));
        string bodyHash = BitConverter.ToString(dataHash).Replace("-", "").ToLower();

        var now = DateTime.UtcNow;
        // 转换为Unix时间戳(秒),符合JWT标准要求
        var unixNow = ((DateTimeOffset)now).ToUnixTimeSeconds();
        var expiresUnix = unixNow + 55; // 55秒后过期

        var jwtClaims = new
        {
            sub = apiKey,
            iat = unixNow,
            exp = expiresUnix,
            nonce = Guid.NewGuid().ToString(),
            uri = path,
            bodyHash
        };

        var jwtHeader = new Dictionary<string, object>
        {
            { "alg", "RS256" },
            { "typ", "JWT" }
        };

        // 直接使用已初始化的privateKey,无需重复导出导入RSA参数
        var jwt = Jose.JWT.Encode(jwtClaims, privateKey, Jose.JwsAlgorithm.RS256, extraHeaders: jwtHeader);
        return jwt;
    }
}

关键修改点:

  1. Unix时间戳转换:将iat和exp从DateTime对象改为Unix时间戳(秒数),使用DateTimeOffset.ToUnixTimeSeconds()实现,完全符合JWT标准格式。
  2. 简化RSA密钥操作:移除了不必要的RSA参数导出和重新导入步骤,直接使用构造函数中初始化的privateKey实例,减少冗余代码并提升性能。
  3. 调试增强:添加resp.EnsureSuccessStatusCode()主动抛出HTTP错误,方便快速定位请求异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 10:53:12