jwt在nodejs中如何实现RSA256签名验证

How does jwt implement RSA256 signature verification in nodejs

我试图了解 JWT 如何执行 RS256 签名验证的内部工作原理。签名算法的基本步骤如下:

  1. 对原始数据进行哈希处理
  2. 使用 RSA 私钥加密散列

并按照以下步骤进行验证:

  1. 使用 RSA public 密钥解密
  2. 将解密生成的哈希值与原始消息的 SHA256 哈希值相匹配。

尝试在其中一个 jwt 上进行测试时 eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.POstGetfAytaZS82wHcjoTyoqhMyxXiWdR7Nn7A29DNSl0EiXLdwJ6xC6AfgZWF1bOsS_TuYI3OG85AmiExREkrS6tDfTQ2B3WXlrr-wp5AokiRbz3_oB4OxG-W9KcEEbDRcZc0nH3L7LzYptiy1PtAylQGxHTWZXtGz4ht0bAecBgmpdgXMguEIcoqPJ1n3pIWk_dUZegpqx0Lka21H6XxUTxiy8OcaarA8zdnPUnV6AmNP3ecFawIFYdvJB_cm-GvpCSbr8G8y_Mllj8f4x9nBH8pQux89_6gUY618iYv7tuPWBFfEbLxtF2pZS6YC1aSfLQxeNe8djT9YjpvRZA

我发现从签名中得到的hash包含一些额外的字符。 例如。上述 jwt 十六进制编码的情况下,原始消息的 SHA256 为

8041fb8cba9e4f8cc1483790b05262841f27fdcb211bc039ddf8864374db5f53

但是上面jwt签名得到的hash解密后是 3031300d0609608648016503040201050004208041fb8cba9e4f8cc1483790b05262841f27fdcb211bc039ddf8864374db5f53

哈希前面有 3031300d060960864801650304020105000420 个额外字符。

这些字符代表什么,从消息和签名中获得的哈希值不应该相同吗?

rfc7518 3.3 定义 JWS 算法 RS256,384,512:

   This section defines the use of the RSASSA-PKCS1-v1_5 digital
   signature algorithm as defined in Section 8.2 of RFC 3447 [RFC3447]
   (commonly known as PKCS #1), using SHA-2 [SHS] hash functions.

rfc3447 8.2 定义 RSASS-PKCS1-v1_5

   RSASSA-PKCS1-v1_5 combines the RSASP1 and RSAVP1 primitives with the
   EMSA-PKCS1-v1_5 encoding method.   ....

其中 EMSA-PKCS1-v1_5 在 rfc3447 9.2 中定义为:

   1. Apply the hash function to the message M to produce a hash value
      H:

         H = Hash(M).

      If the hash function outputs "message too long," output "message
      too long" and stop.

   2. Encode the algorithm ID for the hash function and the hash value
      into an ASN.1 value of type DigestInfo (see Appendix A.2.4) with
      the Distinguished Encoding Rules (DER), where the type DigestInfo
      has the syntax

      DigestInfo ::= SEQUENCE {
          digestAlgorithm AlgorithmIdentifier,
          digest OCTET STRING
      }

      The first field identifies the hash function and the second
      contains the hash value.  Let T be the DER encoding of the
      DigestInfo value (see the notes below) and let tLen be the length
      in octets of T.

   3. If emLen < tLen + 11, output "intended encoded message length too
      short" and stop.

   4. Generate an octet string PS consisting of emLen - tLen - 3 octets
      with hexadecimal value 0xff.  The length of PS will be at least 8
      octets.

   5. Concatenate PS, the DER encoding T, and other padding to form the
      encoded message EM as

         EM = 0x00 || 0x01 || PS || 0x00 || T.

   6. Output EM. [added: which is then modexp'ed with d by RSASP1 to
   sign, or matched to the value modexp'ed with e by RSAVP1 to verify]

   Notes.

   1. For the six hash functions mentioned in Appendix B.1, the DER
      encoding T of the DigestInfo value is equal to the following:

      MD2:     (0x)30 20 30 0c 06 08 2a 86 48 86 f7 0d 02 02 05 00 04
                   10 || H.
      MD5:     (0x)30 20 30 0c 06 08 2a 86 48 86 f7 0d 02 05 05 00 04
                   10 || H.
      SHA-1:   (0x)30 21 30 09 06 05 2b 0e 03 02 1a 05 00 04 14 || H.
      SHA-256: (0x)30 31 30 0d 06 09 60 86 48 01 65 03 04 02 01 05 00
                   04 20 || H.
      SHA-384: (0x)30 41 30 0d 06 09 60 86 48 01 65 03 04 02 02 05 00
                   04 30 || H.
      SHA-512: (0x)30 51 30 0d 06 09 60 86 48 01 65 03 04 02 03 05 00
                      04 40 || H.

您发现的前缀与注释 1 中针对 SHA-256 散列的 DigestInfo 结构的第 2 步编码指定的前缀完全一致,正如预期的那样。

注意 rfc3447=PKCS1v2.1 已被 rfc8017=PKCS1v2.2 取代,但此区域唯一相关的更改是添加了 SHA512/224 和 SHA512/256 哈希,JWS 没有没用。

将签名和验证描述为 'encrypting' 和 'decrypting' 散列(实际上,散列的编码又名填充)被认为已过时。它最初是在几十年前使用的,并且仅用于 RSA 而不是其他签名算法,因为用于加密和解密的 modexp 操作与签名和验证之间的数学相似性,但发现将这些视为相同或可互换的结果在易受攻击和损坏的系统实现中。具体参见 rfc3447 5.2:

   The main mathematical operation in each primitive is
   exponentiation, as in the encryption and decryption primitives of
   Section 5.1.  RSASP1 and RSAVP1 are the same as RSADP and RSAEP
   except for the names of their input and output arguments; they are
   distinguished as they are intended for different purposes.

nodejs 使用这个过时的术语是因为它使用 OpenSSL,而 OpenSSL 通过其前身 SSLeay 可以追溯到 1990 年代初期,当时这个错误仍然很常见。 然而,这并不是真正的 programming/development 问题,更多的是 crypto.SX 和 security.SX 的主题;查看我在 https://security.stackexchange.com/questions/159282/can-openssl-decrypt-the-encrypted-signature-in-an-amazon-alexa-request#159289 收集的一些链接。