You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用SECP256K1算法在Kotlin中签名JWT?

问题

我希望在Kotlin中使用椭圆曲线(secp256k1)签名JWT,编写了如下代码:

import com.nimbusds.jose.JOSEObjectType
import com.nimbusds.jose.JWSAlgorithm
import com.nimbusds.jose.JWSHeader
import com.nimbusds.jose.crypto.ECDSASigner
import com.nimbusds.jwt.JWTClaimsSet
import com.nimbusds.jwt.SignedJWT
import org.bouncycastle.jce.provider.BouncyCastleProvider
import java.security.KeyPairGenerator
import java.security.Security
import java.security.interfaces.ECPrivateKey
import java.security.spec.ECGenParameterSpec

fun main() {
    Security.addProvider(BouncyCastleProvider())
    val ecKPGen = KeyPairGenerator.getInstance("EC", "BC")
    ecKPGen.initialize(ECGenParameterSpec("secp256k1"))

    val header = JWSHeader.Builder(JWSAlgorithm.ES256K)
        .type(JOSEObjectType.JWT)
        .build()
    val payload = JWTClaimsSet.Builder()
        .issuer("me")
        .build()
    val signedJWT = SignedJWT(header, payload)

    val keyPair = ecKPGen.generateKeyPair()
    signedJWT.sign(ECDSASigner(keyPair.private as ECPrivateKey))  // 此处抛出异常
    println(signedJWT.serialize())
}

运行时抛出如下异常:

Exception in thread "main" com.nimbusds.jose.JOSEException: Curve not supported: org.bouncycastle.jce.spec.ECNamedCurveSpec@611889f4
    at com.nimbusds.jose.crypto.ECDSASigner.sign(ECDSASigner.java:287)
    at com.nimbusds.jose.JWSObject.sign(JWSObject.java:315)
    at McveKt.main(mcve.kt:27)
    at McveKt.main(mcve.kt)
Caused by: java.security.SignatureException: Curve not supported: org.bouncycastle.jce.spec.ECNamedCurveSpec@611889f4
    at jdk.crypto.ec/sun.security.ec.ECDSASignature.engineSign(ECDSASignature.java:485)
    at java.base/java.security.Signature$Delegate.engineSign(Signature.java:1423)
    at java.base/java.security.Signature.sign(Signature.java:712)
    at com.nimbusds.jose.crypto.ECDSASigner.sign(ECDSASigner.java:283)
    ... 3 more

我能正常验证PHP服务生成的secp256k1签名JWT,现在需要自行生成同类型JWT用于测试场景,请问该如何解决这个问题?

解决方案

问题核心是JDK默认签名实现不支持secp256k1曲线,而Nimbus JOSE+JWT默认会调用JDK的签名实现,导致异常。需要让密钥生成和签名全流程都依赖BouncyCastle,具体修改如下:

修正后的代码

import com.nimbusds.jose.JOSEObjectType
import com.nimbusds.jose.JWSAlgorithm
import com.nimbusds.jose.JWSHeader
import com.nimbusds.jose.crypto.ECDSASigner
import com.nimbusds.jwt.JWTClaimsSet
import com.nimbusds.jwt.SignedJWT
import org.bouncycastle.jce.provider.BouncyCastleProvider
import java.security.KeyPairGenerator
import java.security.Security
import java.security.Signature
import java.security.interfaces.ECPrivateKey
import java.security.spec.ECGenParameterSpec

fun main() {
    val bcProvider = BouncyCastleProvider()
    Security.addProvider(bcProvider)
    
    // 使用BouncyCastle生成secp256k1密钥对
    val ecKPGen = KeyPairGenerator.getInstance("EC", bcProvider)
    ecKPGen.initialize(ECGenParameterSpec("secp256k1"))
    val keyPair = ecKPGen.generateKeyPair()

    val header = JWSHeader.Builder(JWSAlgorithm.ES256K)
        .type(JOSEObjectType.JWT)
        .build()
    val payload = JWTClaimsSet.Builder()
        .issuer("me")
        .build()
    val signedJWT = SignedJWT(header, payload)

    // 创建BouncyCastle的ECDSA签名实例,指定用SHA256withECDSA算法
    val signature = Signature.getInstance("SHA256withECDSA", bcProvider)
    // 传入签名实例给ECDSASigner,强制使用BouncyCastle完成签名
    signedJWT.sign(ECDSASigner(keyPair.private as ECPrivateKey, signature))

    println(signedJWT.serialize())
}

关键修改说明

  1. 密钥生成环节:明确指定使用BouncyCastle Provider生成密钥对,确保密钥的曲线参数被正确识别。
  2. 签名环节:通过Signature.getInstance创建BouncyCastle的签名实例,再传给ECDSASigner的重载构造方法,避免Nimbus调用不支持secp256k1的JDK默认签名实现。

内容的提问来源于stack exchange,提问作者Robert

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 09:42:47