如何使用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()) }
关键修改说明
- 密钥生成环节:明确指定使用BouncyCastle Provider生成密钥对,确保密钥的曲线参数被正确识别。
- 签名环节:通过
Signature.getInstance创建BouncyCastle的签名实例,再传给ECDSASigner的重载构造方法,避免Nimbus调用不支持secp256k1的JDK默认签名实现。
内容的提问来源于stack exchange,提问作者Robert
相关产品推荐
相关产品推荐

