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

huangapple go评论79阅读模式
英文:

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

我无法确定代码中的问题所在。

英文:

I have generated a private key using Elliptic Curve cryptography:

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

I have used the following Java code to sign a 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());

However, the resulting JWT always have an invalid signature:

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

I can't figure out what's wrong with my code.

答案1

得分: 3

以下是翻译好的部分:

posted private key is a PKCS#8 key. From this the following public X.509 key can be derived:

发贴的私钥是PKCS#8密钥。通过这个私钥,可以得出以下公共X.509密钥:

If a JWT is created with the posted code, e.g.:

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

then this can be verified without problems with this public key, check it e.g. here, i.e. the posted code produces a valid signature.

那么可以使用这个公钥在没有问题的情况下进行验证,例如在这里1,即发布的代码产生了一个有效的签名。

On the other hand, the JWT posted in the question:

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

can indeed not be verified. The signature of this JWT is Base64url decoded:

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

and thus ASN.1 encoded, s. here and here. However, JWTs use a signature encoded as r|s, see e.g. here. If the signature is converted to this encoding, the result is:

因此,它是以ASN.1编码的,参见这里这里。然而,JWT使用_r|s_编码的签名,详见这里。如果将签名转换为此编码,结果是:

If this is Base64url encoded and used in the posted JWT (instead of the old signature), that is:

如果对此进行Base64url编码,并在发布的JWT中使用(而不是旧的签名),即:

the JWT can be successfully validated.

则JWT可以被成功验证。

Since the posted code generates an RFC compliant JWT (with r|s signature), the JWT posted in the question was probably not generated with the posted code (because of the ASN.1 signature).

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

Update: 根据_jjwt_的错误跟踪器,存在一个错误(#125),导致使用ASN.1错误地对签名进行了签名。这个错误应该在_jjwt 0.7_版本中得到修复,并且可以解释您的问题,前提是您使用的是受影响的版本(该错误来自于2016年5月)。我已经测试了您的代码使用_jjwt 0.9.1_版本(来自于2018年7月),它生成了一个有效的签名,这意味着它有效。当前版本是_jjwt 0.11.2_(来自于2020年6月),根据其他答案,也能正常工作。因此,如果您使用的是受影响的版本,最好使用更新的/当前的_jjwt_版本。如果出于某种原因无法实现这一点,当然可以手动将签名从_ASN.1_编码转换为_r|s_编码。

英文:

The posted private key is a PKCS#8 key. From this the following public X.509 key can be derived:

<!-- language: lang-none -->
-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAlCI4gpXtiYhUjfRjfLVRki+BkVG+
IcVa9KJNcQbgirMcqkWM4hizSvOfUXbOsUcqmYy1nQqHk7PJ8mwZ67UZk/MA1gtl
D2bXcmiLd+EYVSAcs0vSGzP8dA/z1pNr1CvxCoQ/uv+8K+tygqf1wo8bBHE4WRGA
bgVYa/voX+G/7UKPZns=
-----END PUBLIC KEY-----

If a JWT is created with the posted code, e.g.:

<!-- language: lang-none -->
eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJzdWIiOiI3MTNmNDJjOS03ZGY1LTQyNzEtOGI1My0xMTJmMzA5MzZjNTYifQ.AE0sx6wHk2xBPkbam24n8NE39qkB0YX4j65DhrWyBKtaQXRMZjuzV78vFir3scfXVolFOf2gpo2K6x_hu0jPz-0IAIMbYQsglePQHQ9OZMSb2XAxKCVXccdvW27QeBov-VGUxxlL-CFNviaPaAGbNny_sc8cRjIF97pDD4KjOPBKkZzt

then this can be verified without problems with this public key, check it e.g. here, i.e. the posted code produces a valid signature.


On the other hand, the JWT posted in the question:

<!-- language: lang-none -->
eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJzdWIiOiI3MTNmNDJjOS03ZGY1LTQyNzEtOGI1My0xMTJmMzA5MzZjNTYifQ.MIGHAkFvCPq_BeXvATTN1duKjEf3K_Fja0ueoTuPQHC9kBc828wem7YO0vnlK6PVYXSkBk4gBaD0-OIMY_r-HS7-4-HaBwJCAMbj0k5YsBywMzme_adKTQq7YUsVvyZwGp8aVgX7vxsMhf-WNvQJSg7AG_zQiUaQ4jqtT9ZKzNoU4P5NZIGMDRCh

can indeed not be verified. The signature of this JWT is Base64url decoded:

<!-- language: lang-none -->
30818702416f08fabf05e5ef0134cdd5db8a8c47f72bf1636b4b9ea13b8f4070bd90173cdbcc1e9bb60ed2f9e52ba3d56174a4064e2005a0f4f8e20c63fafe1d2efee3e1da07024200c6e3d24e58b01cb033399efda74a4d0abb614b15bf26701a9f1a5605fbbf1b0c85ff9636f4094a0ec01bfcd0894690e23aad4fd64accda14e0fe4d64818c0d10a1

and thus ASN.1 encoded, s. here and here. However, JWTs use a signature encoded as r|s, see e.g. here. If the signature is converted to this encoding, the result is:

<!-- language: lang-none -->
6f08fabf05e5ef0134cdd5db8a8c47f72bf1636b4b9ea13b8f4070bd90173cdbcc1e9bb60ed2f9e52ba3d56174a4064e2005a0f4f8e20c63fafe1d2efee3e1da07c6e3d24e58b01cb033399efda74a4d0abb614b15bf26701a9f1a5605fbbf1b0c85ff9636f4094a0ec01bfcd0894690e23aad4fd64accda14e0fe4d64818c0d10a1

If this is Base64url encoded and used in the posted JWT (instead of the old signature), that is:

<!-- language: lang-none -->
eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJzdWIiOiI3MTNmNDJjOS03ZGY1LTQyNzEtOGI1My0xMTJmMzA5MzZjNTYifQ.bwj6vwXl7wE0zdXbioxH9yvxY2tLnqE7j0BwvZAXPNvMHpu2DtL55Suj1WF0pAZOIAWg9PjiDGP6_h0u_uPh2gfG49JOWLAcsDM5nv2nSk0Ku2FLFb8mcBqfGlYF-78bDIX_ljb0CUoOwBv80IlGkOI6rU_WSszaFOD-TWSBjA0QoQ

the JWT can be successfully validated.

Since the posted code generates an RFC compliant JWT (with r|s signature), the JWT posted in the question was probably not generated with the posted code (because of the ASN.1 signature).


Update: According to the jjwt bugtracker there is a bug (#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).<br>I have tested your code with jjwt 0.9.1 (from 07.2018), which generates a valid signature, meaning it works.<br>The current version is jjwt 0.11.2 (from 06.2020), which also works according to the other answer.<br>So if you are working with an affected version, it would be best to use a newer / the current jjwt version. If this is not possible for some reason you can of course convert the signature manually from ASN.1 to r|s encoding.

答案2

得分: 1

我的IntelliJ声称"signWith"行已被弃用。

因此,将您的代码更改为

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

变为

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

将生成以下JWT:

eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJzdWIiOiI3MTNmNDJjOS03ZGY1LTQyNzEtOGI1My0xMTJmMzA5MzZjNTYifQ.ALFk_BGerAstughF4ssl5eGQmx0mu5jvWb13QB228hAD5g8dwM-NvBsyevCuYUXLBJKzIUdPL-LVwQoPkwIbYrKhACzKwUwRN_v3IX2GIPW2ctTcRGPwA7gUaDWrOtwqcHALSfk20QZXT2TQfOnXX8tv0vhXLK_SnnHH5o1b96sa_HSR

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

英文:

My IntelliJ is claiming that the "signWith"-line is deprecated.

So changing your code

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

to

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

is giving this JWT:

eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJzdWIiOiI3MTNmNDJjOS03ZGY1LTQyNzEtOGI1My0xMTJmMzA5MzZjNTYifQ.ALFk_BGerAstughF4ssl5eGQmx0mu5jvWb13QB228hAD5g8dwM-NvBsyevCuYUXLBJKzIUdPL-LVwQoPkwIbYrKhACzKwUwRN_v3IX2GIPW2ctTcRGPwA7gUaDWrOtwqcHALSfk20QZXT2TQfOnXX8tv0vhXLK_SnnHH5o1b96sa_HSR

As you only provided the ec private key I used OpenSSL to generate the EC Public key and passed the JWT and public key to the Online JWT verifier https://jwt.io/
and got the result "Signature verified".

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

huangapple
  • 本文由 发表于 2020年9月22日 16:11:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/64005572.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定