我正在尝试向电子支付处理 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% 确定发行人和受众的价值观应该是什么,或者为什么他们应该是什么,所以那里的任何指示也会有帮助。)
ECDSa.ImportFromPem
确实存在,但仅在 .NET 6 中存在。message
字符串有什么用?有关系吗?x
、y
和 d
参数的关键信息。PemReader
,但没有成功。CngKey
对象中。 (这是迄今为止我最好的线索。)Tldr,我有一个 ES512 PEM 密钥存储为字符串,我需要将其编码为 JWT Bearer 令牌中的秘密,但我陷入了 .NET Framework 4.5 并且对加密知之甚少。我尝试过的所有方法要么崩溃,要么导致 API 端点出现
401 unauthorized
错误响应。
.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-----