英文:
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("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和公钥传递给了在线JWT验证器https://jwt.io/,并得到了"已验证签名"的结果。
英文:
My IntelliJ is claiming that the "signWith"-line is deprecated.
So changing your code
final JwtBuilder jwtBuilder = Jwts.builder()
.setSubject("713f42c9-7df5-4271-8b53-112f30936c56")
.signWith(SignatureAlgorithm.ES512, privateKey)
.setHeaderParam("typ", "JWT");
to
final JwtBuilder jwtBuilder = Jwts.builder()
.setSubject("713f42c9-7df5-4271-8b53-112f30936c56")
.signWith(privateKey, SignatureAlgorithm.ES512)
.setHeaderParam("typ", "JWT");
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".
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论