JWTs:从 RFC7515 复制签名示例

JWTs: Reproducing the signing example from RFC7515

为了我自己对验证 JWT 签名如何工作的理解,我尝试重新实现 RFC7515 的附录 A.2 中给出的示例,该 RFC 定义了 JSON 网络签名(或 JWS)。 https://datatracker.ietf.org/doc/html/rfc7515#appendix-A.2

我正在使用 python 3.9 和 pycryptodome 库。我已经成功地重现了他们为 JWS 签名输入值派生的八位字节序列,而且我很确定我也已经成功地将他们的 RSA 密钥描述转换为有效的 Crypto.PublicKey.RSA 对象。但是,我的签名值与他们所拥有的值不匹配,我不知道我在哪里犯了错误。如果有人能在这里帮助我,我将不胜感激!

我的代码如下

import base64

from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
from Crypto.Signature import pkcs1_15

header = '{"alg":"RS256"}'
body = '{"iss":"joe","exp":1300819380,"http://example.com/is_root":true}'
enc_header = base64.urlsafe_b64encode(header.encode("utf-8"))
enc_body = base64.urlsafe_b64encode(body.encode("utf-8"))
enc = enc_header + b"." + enc_body
enc = enc[:-2]  # Drop b"=="
print("JWS Signing Input value:", [int(x) for x in enc])  # Matches the IETF example

# The following were extracted from their RSA key by applying for each value:
#  value -> int.from_bytes(bytes=base64.urlsafe_b64decode(value + b"=" * (-len(value) % 4)), byteorder='big')
n = 20446702916744654562596343388758805860065209639960173505037453331270270518732245089773723012043203236097095623402044690115755377345254696448759605707788965848889501746836211206270643833663949992536246985362693736387185145424787922241585721992924045675229348655595626434390043002821512765630397723028023792577935108185822753692574221566930937805031155820097146819964920270008811327036286786392793593121762425048860211859763441770446703722015857250621107855398693133264081150697423188751482418465308470313958250757758547155699749157985955379381294962058862159085915015369381046959790476428631998204940879604226680285601
e = 65537
d = 2358310989939619510179986262349936882924652023566213765118606431955566700506538911356936879137503597382515919515633242482643314423192704128296593672966061810149316320617894021822784026407461403384065351821972350784300967610143459484324068427674639688405917977442472804943075439192026107319532117557545079086537982987982522396626690057355718157403493216553255260857777965627529169195827622139772389760130571754834678679842181142252489617665030109445573978012707793010592737640499220015083392425914877847840457278246402760955883376999951199827706285383471150643561410605789710883438795588594095047409018233862167884701
p = 157377055902447438395586165028960291914931973278777532798470200156035267537359239071829408411909323208574959800537247728959718236884809685233284537349207654661530801859889389455120932077199406250387226339056140578989122526711937239401762061949364440402067108084155200696015505170135950332209194782224750221639
q = 129921752567406358990993347540064445018230073402482260994179328573323861908379211274626956543471664997237185298964648133324343327052852264060322088122401124781249085873464824282666514908127141915943024862618996371026577302203267804867959037802770797169483022132210859867700312376409633383772189122488119155159
ietf_key = RSA.construct(
    rsa_components=(n, e, d, p, q)
)

signer = pkcs1_15.new(ietf_key)
h = SHA256.new(enc)
signature = signer.sign(h)
pkcs1_15.new(ietf_key).verify(h, signature)
print([int(x) for x in signature])  # Does not match the IETF example
print(base64.urlsafe_b64encode(signature))

你从 RFC 7515 得到结果,A.2.1. Encoding 如果你使用 body:

body = '{"iss":"joe",\r\n "exp":1300819380,\r\n "http://example.com/is_root":true}'

与您使用的值不同的是每个逗号后有一个 0x0d0a20 字节序列 (\r\n<space>)。 A.1.1. Encoding.

中对此进行了描述