.net 生成和风天气api密钥

和风天气api密钥生成使用 ed25519(EdDSA) 签名算法签名 jwt。但是 .net 官方没有提供 ed25519 签名算法,所以要自行拼接 jwt 结构并使用第三方库进行签名

openssl 生成密钥对

macos 上可能要安装 openssl@3, brew install openssl@3
/opt/homebrew/opt/openssl@3/bin/openssl genpkey -algorithm ED25519 -out qweather-ed25519-private.pem  # 生成私钥
/opt/homebrew/opt/openssl@3/bin/openssl pkey -pubout -in qweather-ed25519-private.pem > qweather-ed25519-public.pem # 生成公钥

和风天气上创建项目

得到 kid 与 sub

创建待签名字符串

jwt 结构 headerBase64.payloadBase64.signatureBase64, 先生成待签名部分 headerBase64.payloadBase64

生成 headerBase64

header 比较简单,直接拼接 json 字符串

Dictionary<string, object> headerDict = new() { { "alg", "EdDSA" }, { "kid", kid }, };
string headerJson = JsonSerializer.Serialize(headerDict);
string headerBase64 = Base64UrlEncoder.Encode(headerJson);

生成 payloadBase64

payload 部分比较复杂,以后可能还要扩展,更改,所以使用 Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler 生成一个 jwt , 然后取出中间的 payload 部分


SecurityTokenDescriptor descriptor = new() {
  Subject = new ClaimsIdentity([new Claim("sub", sub), ]),
    Expires = DateTime.UtcNow.AddHours(20),
    IssuedAt = DateTime.UtcNow.AddSeconds(-60),
    NotBefore = null,
    SigningCredentials = null,
};

JsonWebTokenHandler handler = new();
handler.SetDefaultTimesOnTokenCreation = false; // 为了不自动生成 nbf, 和风建议不要生成
string ? token = handler.CreateToken(descriptor);
if (string.IsNullOrWhiteSpace(token)) {
  throw new InvalidOperationException("无法创建 jwt token");
}

string payloadBase64 = token.Split('.')[1];

生成待签名字符串

headerBase64.payloadBase64

生成签名 jwt

添加 NuGet 包 BouncyCastle.Cryptography

从 pem 文件中载入 ed25519 private key

# 从 pem 文件中载入二进制格式的 key 
byte[] keyData = await LoadPrivateKeyDataAsync(cancellationToken);
using MemoryStream stream = new(keyData);
using StreamReader reader = new(stream);
PemReader pemReader = new(reader);
object ? keyPair = pemReader.ReadObject();

return keyPair
switch {
  AsymmetricKeyParameter and Ed25519PrivateKeyParameters privateKey => privateKey,
    AsymmetricCipherKeyPair {
      Private: Ed25519PrivateKeyParameters privateKey,
    } => privateKey,
    _ =>
    throw new ArgumentException("无法加载私钥"),
};

签名

# 使用前面得到的 headerBase64 和 payloadBase64 拼接待签名字符串
string signingInput = $ "{headerBase64}.{payloadBase64}";

# 从 pem 文件中中加载私钥
Ed25519PrivateKeyParameters privateKey = await LoadPrivateKeyAsync(cancellationToken);

Ed25519Signer signer = new();
signer.Init(true, privateKey);
byte[] message = Encoding.UTF8.GetBytes(signingInput);
signer.BlockUpdate(message, 0, message.Length);
byte[] signatureBytes = signer.GenerateSignature();

string signatureBase64 = Base64UrlEncoder.Encode(signatureBytes);

# 得到最終結果, 在 jwt.io 上檢查, 和风后台也有检测 key 工具
return $ "{signingInput}.{signatureBase64}";
上一篇
下一篇