无法使用椭圆曲线 (EC) 密码术签署 JWT

Cannot sign a JWT using Elliptic Curve (EC) cryptography

我已经使用椭圆曲线密码术生成了私钥:

openssl ecparam -genkey -name secp521r1 -noout | openssl pkcs8 -topk8 -nocrypt

我使用了以下 Java 代码来签署 JWT:

    String privateKeyPEM = "MIHtAgEAMBAGByqGSM49AgEGBSuBBAAjBIHVMIHSAgEBBEEmSOGpmkjzKM+uWhya"
        + "Cl6sbSsmROUol4HaDbORnOI6klbEjbCkPEyxKRnrrtrGFShhu7TPPlGDK39f+K3G"
        + "IZhbYKGBiQOBhgAEAJQiOIKV7YmIVI30Y3y1UZIvgZFRviHFWvSiTXEG4IqzHKpF"
        + "jOIYs0rzn1F2zrFHKpmMtZ0Kh5OzyfJsGeu1GZPzANYLZQ9m13Joi3fhGFUgHLNL"
        + "0hsz/HQP89aTa9Qr8QqEP7r/vCvrcoKn9cKPGwRxOFkRgG4FWGv76F/hv+1Cj2Z7";

    byte[] encoded = Base64.decodeBase64(privateKeyPEM);
    KeyFactory keyFactory = KeyFactory.getInstance("EC");
    PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
    ECPrivateKey privateKey = (ECPrivateKey) keyFactory.generatePrivate(keySpec);

    final JwtBuilder jwtBuilder = Jwts.builder()
        .setSubject("713f42c9-7df5-4271-8b53-112f30936c56")
        .signWith(SignatureAlgorithm.ES512, privateKey)
        .setHeaderParam("typ", "JWT");

    System.out.println(jwtBuilder.compact());

但是,生成的 JWT 始终具有无效签名:

eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJzdWIiOiI3MTNmNDJjOS03ZGY1LTQyNzEtOGI1My0xMTJmMzA5MzZjNTYifQ.MIGHAkFvCPq_BeXvATTN1duKjEf3K_Fja0ueoTuPQHC9kBc828wem7YO0vnlK6PVYXSkBk4gBaD0-OIMY_r-HS7-4-HaBwJCAMbj0k5YsBywMzme_adKTQq7YUsVvyZwGp8aVgX7vxsMhf-WNvQJSg7AG_zQiUaQ4jqtT9ZKzNoU4P5NZIGMDRCh

我不知道我的代码有什么问题。

发布的私钥是 PKCS#8 密钥。由此可以导出以下 public X.509 密钥:

-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAlCI4gpXtiYhUjfRjfLVRki+BkVG+
IcVa9KJNcQbgirMcqkWM4hizSvOfUXbOsUcqmYy1nQqHk7PJ8mwZ67UZk/MA1gtl
D2bXcmiLd+EYVSAcs0vSGzP8dA/z1pNr1CvxCoQ/uv+8K+tygqf1wo8bBHE4WRGA
bgVYa/voX+G/7UKPZns=
-----END PUBLIC KEY-----

如果 JWT 是使用发布的代码创建的,例如:

eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJzdWIiOiI3MTNmNDJjOS03ZGY1LTQyNzEtOGI1My0xMTJmMzA5MzZjNTYifQ.AE0sx6wHk2xBPkbam24n8NE39qkB0YX4j65DhrWyBKtaQXRMZjuzV78vFir3scfXVolFOf2gpo2K6x_hu0jPz-0IAIMbYQsglePQHQ9OZMSb2XAxKCVXccdvW27QeBov-VGUxxlL-CFNviaPaAGbNny_sc8cRjIF97pDD4KjOPBKkZzt

然后可以使用此 public 密钥验证没有问题,检查它,例如here,即发布的代码生成有效签名。


另一方面,问题中发布的 JWT:

eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJzdWIiOiI3MTNmNDJjOS03ZGY1LTQyNzEtOGI1My0xMTJmMzA5MzZjNTYifQ.MIGHAkFvCPq_BeXvATTN1duKjEf3K_Fja0ueoTuPQHC9kBc828wem7YO0vnlK6PVYXSkBk4gBaD0-OIMY_r-HS7-4-HaBwJCAMbj0k5YsBywMzme_adKTQq7YUsVvyZwGp8aVgX7vxsMhf-WNvQJSg7AG_zQiUaQ4jqtT9ZKzNoU4P5NZIGMDRCh

确实无法验证。这个JWT的签名是Base64url解码的:

30818702416f08fabf05e5ef0134cdd5db8a8c47f72bf1636b4b9ea13b8f4070bd90173cdbcc1e9bb60ed2f9e52ba3d56174a4064e2005a0f4f8e20c63fafe1d2efee3e1da07024200c6e3d24e58b01cb033399efda74a4d0abb614b15bf26701a9f1a5605fbbf1b0c85ff9636f4094a0ec01bfcd0894690e23aad4fd64accda14e0fe4d64818c0d10a1

因此 ASN.1 编码, s. here and here. However, JWTs use a signature encoded as r|s, see e.g. here。如果将签名转换为这种编码,则结果为:

6f08fabf05e5ef0134cdd5db8a8c47f72bf1636b4b9ea13b8f4070bd90173cdbcc1e9bb60ed2f9e52ba3d56174a4064e2005a0f4f8e20c63fafe1d2efee3e1da07c6e3d24e58b01cb033399efda74a4d0abb614b15bf26701a9f1a5605fbbf1b0c85ff9636f4094a0ec01bfcd0894690e23aad4fd64accda14e0fe4d64818c0d10a1

如果这是 Base64url 编码并用于发布的 JWT(而不是旧签名),即:

eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJzdWIiOiI3MTNmNDJjOS03ZGY1LTQyNzEtOGI1My0xMTJmMzA5MzZjNTYifQ.bwj6vwXl7wE0zdXbioxH9yvxY2tLnqE7j0BwvZAXPNvMHpu2DtL55Suj1WF0pAZOIAWg9PjiDGP6_h0u_uPh2gfG49JOWLAcsDM5nv2nSk0Ku2FLFb8mcBqfGlYF-78bDIX_ljb0CUoOwBv80IlGkOI6rU_WSszaFOD-TWSBjA0QoQ

JWT 可以成功验证

由于发布的代码生成符合 RFC 的 JWT(带有 r|s 签名),问题中发布的 JWT 可能不是使用发布的代码生成的(因为ASN.1 签名)。


更新: 根据 jjwt bugtracker 有一个错误(#125) that causes the signature to be signed incorrectly with ASN.1. This bug should be fixed with jjwt 0.7 and would be a plausible explanation for your issue, provided you are working with an affected version (the bug is from 05.2016).
I have tested your code with jjwt 0.9.1 (from 07.2018), which generates a valid signature, meaning it works.
The current version is jjwt 0.11.2 (from 06.2020), which also works according to the
所以如果您正在使用受影响的版本,最好使用较新/当前的 jjwt 版本。如果由于某种原因无法做到这一点,您当然可以从 ASN.1r|s 编码。

我的 IntelliJ 声称“signWith”行已弃用。

所以改变你的代码

final JwtBuilder jwtBuilder = Jwts.builder()
  .setSubject("713f42c9-7df5-4271-8b53-112f30936c56")
  .signWith(SignatureAlgorithm.ES512, privateKey)
  .setHeaderParam("typ", "JWT");

final JwtBuilder jwtBuilder = Jwts.builder()
  .setSubject("713f42c9-7df5-4271-8b53-112f30936c56")
  .signWith(privateKey, SignatureAlgorithm.ES512)
  .setHeaderParam("typ", "JWT");

正在给这个 JWT:

eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJzdWIiOiI3MTNmNDJjOS03ZGY1LTQyNzEtOGI1My0xMTJmMzA5MzZjNTYifQ.ALFk_BGerAstughF4ssl5eGQmx0mu5jvWb13QB228hAD5g8dwM-NvBsyevCuYUXLBJKzIUdPL-LVwQoPkwIbYrKhACzKwUwRN_v3IX2GIPW2ctTcRGPwA7gUaDWrOtwqcHALSfk20QZXT2TQfOnXX8tv0vhXLK_SnnHH5o1b96sa_HSR

由于您只提供了 ec 私钥,我使用 OpenSSL 生成 EC Public 密钥并将 JWT 和 public 密钥传递给在线 JWT 验证器 https://jwt.io/ 并得到结果“签名已验证”。