首页 > 解决方案 > 无法使用椭圆曲线 (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

我无法弄清楚我的代码有什么问题。

标签: javacryptographyelliptic-curve

解决方案


发布的私钥是 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编码。


推荐阅读