无法使用椭圆曲线EC加密签署JWT

问题描述

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

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密钥。由此可以得出以下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

然后可以使用此公钥对此没有问题进行验证,例如进行检查here,即发布的代码产生有效的签名。


另一方面,JWT发表了一个问题:

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

确实无法验证。该JWT的签名已通过Base64url解码:

30818702416f08fabf05e5ef0134cdd5db8a8c47f72bf1636b4b9ea13b8f4070bd90173cdbcc1e9bb60ed2f9e52ba3d56174a4064e2005a0f4f8e20c63fafe1d2efee3e1da07024200c6e3d24e58b01cb033399efda74a4d0abb614b15bf26701a9f1a5605fbbf1b0c85ff9636f4094a0ec01bfcd0894690e23aad4fd64accda14e0fe4d64818c0d10a1

,因此 ASN.1编码herehere。但是,JWT使用编码为 r | s 的签名,例如,参见here。如果将签名转换为这种编码,则结果为:

6f08fabf05e5ef0134cdd5db8a8c47f72bf1636b4b9ea13b8f4070bd90173cdbcc1e9bb60ed2f9e52ba3d56174a4064e2005a0f4f8e20c63fafe1d2efee3e1da07c6e3d24e58b01cb033399efda74a4d0abb614b15bf26701a9f1a5605fbbf1b0c85ff9636f4094a0ec01bfcd0894690e23aad4fd64accda14e0fe4d64818c0d10a1

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

eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJzdWIiOiI3MTNmNDJjOS03ZGY1LTQyNzEtOGI1My0xMTJmMzA5MzZjNTYifQ.bwj6vwXl7wE0zdXbioxH9yvxY2tLnqE7j0BwvZAXPNvMHpu2DtL55Suj1WF0pAZOIAWg9PjiDGP6_h0u_uPh2gfG49JOWLAcsDM5nv2nSk0Ku2FLFb8mcBqfGlYF-78bDIX_ljb0CUoOwBv80IlGkOI6rU_WSszaFOD-TWSBjA0QoQ

JWT可以成功验证

由于发布的代码生成的是RFC兼容的JWT(具有 r | s 签名),因此问题中发布的JWT可能不是通过发布的代码生成的(由于 ASN)。 1 签名)。


更新:根据 jjwt bugtracker,存在一个错误(#125),该错误导致签名未使用ASN.1正确签名。该错误应该用 jjwt 0.7 修复,并且可以合理地解释您的问题,前提是您使用的是受影响的版本(该错误来自05.2016)。
我已经测试了您的代码使用 jjwt 0.9.1 (自2018年7月起)生成有效的签名,即表示有效。
当前版本为 jjwt 0.11.2 (自06.2020起) ,它也可以根据other answer使用。
因此,如果您使用的是受影响的版本,最好使用较新的/当前的 jjwt 版本。如果由于某些原因无法实现,您当然可以手动将签名从 ASN.1 转换为 r | 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公钥,并将JWT和公钥传递给Online JWT验证程序https://jwt.io/ 并得到结果“ 签名已验证”。

enter image description here