java - 无法使用椭圆曲线 (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
然后可以用这个公钥验证没有问题,例如在这里检查它,即发布的代码产生一个有效的签名。
另一方面,JWT 在问题中发布:
eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJzdWIiOiI3MTNmNDJjOS03ZGY1LTQyNzEtOGI1My0xMTJmMzA5MzZjNTYifQ.MIGHAkFvCPq_BeXvATTN1duKjEf3K_Fja0ueoTuPQHC9kBc828wem7YO0vnlK6PVYXSkBk4gBaD0-OIMY_r-HS7-4-HaBwJCAMbj0k5YsBywMzme_adKTQq7YUsVvyZwGp8aVgX7vxsMhf-WNvQJSg7AG_zQiUaQ4jqtT9ZKzNoU4P5NZIGMDRCh
确实无法验证。这个 JWT 的签名是 Base64url 解码的:
30818702416f08fabf05e5ef0134cdd5db8a8c47f72bf1636b4b9ea13b8f4070bd90173cdbcc1e9bb60ed2f9e52ba3d56174a4064e2005a0f4f8e20c63fafe1d2efee3e1da07024200c6e3d24e58b01cb033399efda74a4d0abb614b15bf26701a9f1a5605fbbf1b0c85ff9636f4094a0ec01bfcd0894690e23aad4fd64accda14e0fe4d64818c0d10a1
因此ASN.1 编码, s。这里和这里。但是,JWT 使用编码为r|s的签名,请参见此处。如果将签名转换为此编码,则结果为:
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 错误地签名。如果您使用的是受影响的版本(该错误来自 05.2016),则应该使用jjwt 0.7修复此错误,并且可能是对您的问题的合理解释。
我已经用jjwt 0.9.1(从 07.2018 开始)测试了你的代码,它生成了一个有效的签名,这意味着它可以工作。
当前版本是jjwt 0.11.2(从 06.2020 开始),它也可以根据另一个答案工作。
因此,如果您正在使用受影响的版本,最好使用更新的/当前的jjwt版本。如果由于某种原因无法做到这一点,您当然可以手动将签名从ASN.1转换为r|s编码。
推荐阅读
- node.js - 在 @nuxtjs/firebase 中实现 promptUserForPassword firebase
- python - 为什么我在运行此代码时收到错误代码?
- reactjs - 将元素放在 Nav 组件上方的正确方法?将导航元素推到一边。反应引导
- mysql - 可以在mysql模块filebeat上设置多个慢日志和错误日志吗?
- python - 在 EMR 集群上运行的 jupyterhub notebook 中本地保存文件
- python - 签名 签名 buyTokenByTokenWithPermission ETH BSC 内存 _signature bytes _signature
- c++ - 如何将错误调试为“未解决的外部”?
- python - 在这个简短的 py 代码中,什么触发了“+: 'int' 和 'list' 不支持的操作数类型”错误?我该如何解决
- python - 点击提交后如何清除烧瓶表格?
- ios - 如何知道设备可以使用 captureTextFromCamera API?