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

关于使用RSA私钥签名字符串及JWT签名的技术问询

签名问题答疑:字符串签名与JWT签名实现

一、SHA256withRSA/PSS算法签名字符串的正确方式

你的代码存在三个核心问题,导致签名结果和预期不符:

  • 私钥格式解析错误:你使用的是-----BEGIN RSA PRIVATE KEY-----开头的PKCS#1格式私钥,但代码中误用了对应PKCS#8格式的PKCS8EncodedKeySpec,解析逻辑不匹配。
  • 签名结果处理错误:直接将签名字节数组转为String会因编码问题产生乱码,正确做法是对字节数组做Base64编码(JWT场景需用Base64URL编码)。
  • RSA/PSS参数未显式指定:默认的SHA256withRSA/PSS使用的MGF1参数可能与预期不一致,需显式配置哈希算法匹配要求。

修正后的代码:

import java.security.*
import java.security.spec.RSAPrivateKeySpec
import java.util.Base64
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.util.io.pem.PemReader
import java.io.StringReader

fun signStr(strToSign: String): String {
    val PRIVATE_KEY = """
        -----BEGIN RSA PRIVATE KEY-----
        SOME_PRIVATE_KEY
        -----END RSA PRIVATE KEY-----
    """.trimIndent()

    // 加载BouncyCastle提供器,处理PKCS#1私钥和精细配置PSS参数
    Security.addProvider(BouncyCastleProvider())

    // 解析PKCS#1格式私钥
    val pemReader = PemReader(StringReader(PRIVATE_KEY))
    val pemObject = pemReader.readPemObject()
    val keyFactory = KeyFactory.getInstance("RSA", "BC")
    val privateKey = keyFactory.generatePrivate(RSAPrivateKeySpec(
        BigInteger(1, pemObject.content.copyOfRange(0, 128)),
        BigInteger(1, pemObject.content.copyOfRange(128, 256))
    ))

    // 配置SHA256withRSA/PSS参数,确保MGF1使用SHA256
    val pssParams = PSSParameterSpec(
        "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1
    )
    val signature = Signature.getInstance("SHA256withRSA/PSS", "BC")
    signature.setParameter(pssParams)
    signature.initSign(privateKey)

    // 显式指定UTF-8编码,避免环境编码差异
    signature.update(strToSign.toByteArray(Charsets.UTF_8))
    val signatureBytes = signature.sign()

    // 返回Base64编码的签名(JWT场景改用Base64.getUrlEncoder().withoutPadding())
    return Base64.getEncoder().encodeToString(signatureBytes)
}

说明:

  • 引入BouncyCastle是为了便捷处理PKCS#1私钥和PSS参数配置,也可手动将PKCS#1转成PKCS#8格式避免依赖,但依赖BC更省心。
  • PSS参数中的32是SHA256摘要长度,1是盐长度(通常与摘要长度一致,可按需调整)。

二、JWT令牌的签名逻辑验证

你的JWT签名框架逻辑正确,但细节存在错误:

  • header和payload必须是JSON字符串:JWT的header和payload本质是JSON对象,需先序列化为JSON字符串再做Base64URL编码,不能直接传入任意字符串。
  • 签名结果处理顺序错误:应先对签名字节数组做Base64URL编码,而非先转成乱码字符串再编码。
  • Base64URL编码需去掉填充:JWT要求使用无填充的Base64URL编码。

修正后的JWT签名代码(基于上述修正后的签名逻辑):

import com.fasterxml.jackson.databind.ObjectMapper
import java.util.Base64
import java.security.*
import java.security.spec.RSAPrivateKeySpec
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.util.io.pem.PemReader
import java.io.StringReader

// 定义JWT的header和payload数据类
data class JwtHeader(val alg: String = "PS256", val typ: String = "JWT")
data class JwtPayload(val sub: String, val exp: Long)

private val objectMapper = ObjectMapper()
private val base64UrlEncoder = Base64.getUrlEncoder().withoutPadding()

fun signJWT(payload: JwtPayload): String {
    // 序列化header和payload为JSON字符串
    val headerJson = objectMapper.writeValueAsString(JwtHeader())
    val payloadJson = objectMapper.writeValueAsString(payload)

    // 做Base64URL编码
    val headerBase64 = base64UrlEncoder.encodeToString(headerJson.toByteArray(Charsets.UTF_8))
    val payloadBase64 = base64UrlEncoder.encodeToString(payloadJson.toByteArray(Charsets.UTF_8))

    // 拼接待签名字符串
    val toSign = "$headerBase64.$payloadBase64"

    // 签名并做Base64URL编码
    val signatureBytes = signStrForJwt(toSign)
    val signatureBase64 = base64UrlEncoder.encodeToString(signatureBytes)

    return "$toSign.$signatureBase64"
}

// 为JWT场景调整签名方法,返回字节数组
fun signStrForJwt(strToSign: String): ByteArray {
    val PRIVATE_KEY = """
        -----BEGIN RSA PRIVATE KEY-----
        SOME_PRIVATE_KEY
        -----END RSA PRIVATE KEY-----
    """.trimIndent()

    Security.addProvider(BouncyCastleProvider())

    val pemReader = PemReader(StringReader(PRIVATE_KEY))
    val pemObject = pemReader.readPemObject()
    val keyFactory = KeyFactory.getInstance("RSA", "BC")
    val privateKey = keyFactory.generatePrivate(RSAPrivateKeySpec(
        BigInteger(1, pemObject.content.copyOfRange(0, 128)),
        BigInteger(1, pemObject.content.copyOfRange(128, 256))
    ))

    val pssParams = PSSParameterSpec(
        "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1
    )
    val signature = Signature.getInstance("SHA256withRSA/PSS", "BC")
    signature.setParameter(pssParams)
    signature.initSign(privateKey)

    signature.update(strToSign.toByteArray(Charsets.UTF_8))
    return signature.sign()
}

说明:

  • JWT中SHA256withRSA/PSS对应的算法标识是PS256,header里的alg必须设为该值。
  • 可使用Jackson、Gson等任意JSON库完成序列化。

三、推荐的Kotlin/Java库

1. 通用签名场景

  • BouncyCastle:Java生态最常用的密码学扩展库,支持几乎所有主流算法和密钥格式,处理RSA/PSS这类算法的参数配置灵活。
  • Apache Commons Codec:辅助处理Base64/Base64URL编码,简化编码解码逻辑。

2. JWT场景

  • JJWT:轻量级JWT专用库,支持所有标准签名算法(含PS256),封装了JWT的创建、解析、验证全流程,代码简洁易维护。
  • Nimbus JOSE + JWT:功能全面的JOSE/JWT库,支持JWK、加密JWT等复杂场景,适合企业级应用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 09:58:11