使用预生成的 ES512 私钥在 .NET Framework 4.5 中签署 JWT 时遇到困难

问题描述 投票:0回答:1

目标

我正在尝试向电子支付处理 API 发出简单的 HTTP Post 请求,并且他们的规范要求我向有效负载添加不记名令牌。即,

rest_request.AddHeader("Authorization", "Bearer " + myToken)
token
是一个像
"abcdefg.hijklmop.qrstuvwxyz"
一样的 JWT。

他们提供了一个ES512私有PEM密钥作为秘密编码到JWT中,但我很难理解我到底需要对我的密钥做什么(格式如下)。

-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----

环境

我正在使用 .NET Framework 4.5(这是一个旧系统),VB.NET 用于语言,API 提供商已为我提供了 密钥 ID私钥

API 提供商需要标头和声明中的

iss
aud
iat
exp
kid
jti
值。 (我不是 100% 确定发行人和受众的价值观应该是什么,或者为什么他们应该是什么,所以那里的任何指示也会有帮助。)

路障

  • 我已经能够使用 NuGet 库 JWT、BouncyCastle、jose-jwt 和通用 Microsoft.IdentityModel 生成令牌,但是 jwt.io 不会失败 告诉我我的 令牌无效
    • Microsoft 库不支持 ES512
    • JWT、BouncyCastle 和 jose-jwt 已经给了我完整的令牌,尽管调试器声称这些令牌“无效”。
  • 一个重大的遗憾是,尽管
    ECDSa.ImportFromPem
    确实存在,但仅在 .NET 6 中存在。
  • 我对所有这些加密混乱的理解基本上为零,所以像我 5 岁一样解释它。
  • 我发现的所有“类似”问题和表单帖子都没有考虑到预先存在的 PEM 密钥。

我尝试过的事情

  • 这是我发现的最相似的问题,但我仍然无法完成任何工作。
    message
    字符串有什么用?有关系吗?
  • 这看起来也很接近,但使用了公钥和一些我不明白的神奇数字。
  • 这看起来很有希望,但缺乏一些有关获取/使用
    x
    y
    d
    参数的关键信息。
  • 我尝试使用 BouncyCastle
    PemReader
    ,但没有成功。
  • jose-jwt 在 ES512 键上有 这部分,并且使用 CNG Keys 让我更近了。但是,我没有方法(或者不明白如何)存储 PEM 密钥以便将加载
    CngKey
    对象
    中。 (这是迄今为止我最好的线索。)

Tldr,我有一个 ES512 PEM 密钥存储为字符串,我需要将其编码为 JWT Bearer 令牌中的秘密,但我陷入了 .NET Framework 4.5 并且对加密知之甚少。我尝试过的所有方法要么崩溃,要么导致 API 端点出现

401 unauthorized
错误响应。

.net jwt .net-4.5 bearer-token
1个回答
0
投票

.NET Framework 不支持 PEM 编码密钥,因此必须使用第三方库进行密钥导入。 BouncyCastle 是一个可靠的库,可以用于此目的。

为了避免 BouncyCastle 数据类型和本机数据类型之间的转换,还应使用 BouncyCastle 类进行签名。

可能的 VB.NET 实现是(在 .NET Framework 4.5 和 Portable.BouncyCastle v1.9.0 上测试):

Imports Org.BouncyCastle.Crypto
Imports Org.BouncyCastle.Crypto.Parameters
Imports Org.BouncyCastle.Utilities.Encoders
Imports Org.BouncyCastle.OpenSsl
Imports Org.BouncyCastle.Security
Imports System.IO
Imports System.Text
Imports System

Public Module module1
    Public Sub Main()
        Dim headerB64url = toB64url("{""alg"":""ES512"",""typ"":""JWT"",""kid"":""dcaaddd0-ea67-45bd-bc6b-b35ec6c7fd63""}")
        Dim paylodB64url = toB64url("{""iss"":""me"",""aud"":""you"",""iat"":1731148967,""exp"":1762681367,""jti"":""fc19e8f0-e3bf-49c5-a738-df1461c59d36""}")
        Dim jwtUnsigned = headerB64url & "." & paylodB64url
        Dim privatePkcs8Pem = "-----BEGIN PRIVATE KEY-----
MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIBiyAa7aRHFDCh2qga
9sTUGINE5jHAFnmM8xWeT/uni5I4tNqhV5Xx0pDrmCV9mbroFtfEa0XVfKuMAxxf
Z6LM/yKhgYkDgYYABAGBzgdnP798FsLuWYTDDQA7c0r3BVk8NnRUSexpQUsRilPN
v3SchO0lRw9Ru86x1khnVDx+duq4BiDFcvlSAcyjLACJvjvoyTLJiA+TQFdmrear
jMiZNE25pT2yWP1NUndJxPcvVtfBW48kPOmvkY4WlqP5bAwCXwbsKrCgk6xbsp12
ew==
-----END PRIVATE KEY-----"
        Dim privateEcKey = CType(ImportPrivateFromPkcs8Pem(privatePkcs8Pem), ECKeyParameters)
        Dim signatureB64url = toB64url(EcSign("SHA-512withPLAIN-ECDSA", Encoding.UTF8.GetBytes(jwtUnsigned), privateEcKey))
        Dim jwtSigned = jwtUnsigned & "." & signatureB64url
        Console.WriteLine(jwtSigned)
    End Sub

    Private Function ImportPrivateFromPkcs8Pem(ByVal privatePkcs8Pem As String) As AsymmetricKeyParameter
        Dim pemReader As PemReader = New PemReader(New StringReader(privatePkcs8Pem))
        Return CType(pemReader.ReadObject(), AsymmetricKeyParameter)
    End Function

    Private Function EcSign(ByVal algo As String, ByVal msg As Byte(), ByVal key As ECKeyParameters) As Byte()
        Return EcSignVerify(algo, True, msg, key).GenerateSignature()
    End Function

    Private Function EcSignVerify(ByVal algo As String, ByVal isSigner As Boolean, ByVal msg As Byte(), ByVal key As ECKeyParameters) As ISigner
        Dim signerVerifier As ISigner = SignerUtilities.GetSigner(algo)
        signerVerifier.Init(isSigner, key)
        signerVerifier.BlockUpdate(msg, 0, msg.Length)
        Return signerVerifier
    End Function

    Private Function toB64url(ByVal data As Byte()) As String
        Return Encoding.UTF8.GetString(UrlBase64.Encode(data)).Replace(".", "")
    End Function

    Private Function toB64url(ByVal data As String) As String
        Return toB64url(Encoding.UTF8.GetBytes(data))
    End Function
End Module

示例中使用的标头和有效负载为(为了清晰起见,换行和缩进):

{
  "alg": "ES512",
  "typ": "JWT",
  "kid": "dcaaddd0-ea67-45bd-bc6b-b35ec6c7fd63"
}

{
  "iss": "me",
  "aud": "you",
  "iat": 1731148967,
  "exp": 1762681367,
  "jti": "fc19e8f0-e3bf-49c5-a738-df1461c59d36"
}

如果您使用代码,请不要忘记将示例实现中的值替换为声明的值以及密钥。

如果您不清楚所使用的权利要求的含义,您可以在 RFC7519 第 2 节中找到说明。 4.1。这些值本身由您的用例决定,如有疑问,您必须查阅 API 文档。

请注意,JOSE 对 ES512 和 ECDSA 使用非确定性变体,即每次都会生成不同的签名(即使标头、有效负载和密钥相同)。

获得的签名 JWT 可以通过 jwt.io 进行验证,例如此处,使用以下公钥:

-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ
PDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47
6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM
Al8G7CqwoJOsW7Kddns=
-----END PUBLIC KEY-----
© www.soinside.com 2019 - 2024. All rights reserved.