Android平台ES256签名JWT后在线验证失败问题求助
Android平台生成ES256签名JWT无法通过在线工具验证的问题
需求说明
- 密钥必须为P-256(secp256r1)椭圆曲线密钥,采用ECDSA-SHA256算法(对应JWT alg为ES256)
- JWT Header必须符合:
{ "alg": "ES256", "typ": "JWT" }
- Payload必须包含
id(UUID格式)和iat(UTC秒级时间戳),示例:
{ "id": "7986f03e-a4b8-4033-87b9-af8ae0468a0b", "iat": 1702390385 }
实现代码
1. 生成P-256密钥对(存储到Android KeyStore)
// Initialize KeyPairGenerator for ECDSA 256 val keyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, ANDROID_KEY_STORE) keyPairGenerator.initialize( KeyGenParameterSpec.Builder( keyStoreAlias, KeyProperties.PURPOSE_SIGN, ) .setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1")) .setDigests(KeyProperties.DIGEST_SHA256) .build() ) // Generate the key pair (it will be stored automatically in the Android key store) keyPairGenerator.generateKeyPair()
2. 编码Header和Payload
val encodedHeader = makeJsonAndEncodeToString( "alg" to "ES256", "typ" to "JWT", ) val encodedPayload = makeJsonAndEncodeToString( "id" to "27d09c71-bd67-4523-97bb-12e933e9acbb", "iat" to 1702462854, ) private fun makeJsonAndEncodeToString(vararg data: Pair<String, Any>): String { return Base64.encodeToString( JSONObject(data.toMap()).toString().toByteArray(), Base64.NO_PADDING or Base64.URL_SAFE or Base64.NO_WRAP, ) }
3. 生成签名并拼接JWT
val signatureInput = "$encodedHeader.$encodedPayload" val keyStore = loadKeyStore() // 自定义工具方法加载Android KeyStore // 获取私钥 val entry: KeyStore.Entry = keyStore.getEntry(keyStoreAlias, null) if (entry !is KeyStore.PrivateKeyEntry) { throw IllegalStateException("指定别名<$keyStoreAlias>下不存在私钥") } // 初始化签名并计算 val ecdsaSign = Signature.getInstance("SHA256withECDSA") ecdsaSign.initSign(entry.privateKey) ecdsaSign.update(signatureInput.toByteArray()) // 默认UTF-8编码 val signedContent = ecdsaSign.sign() val encodedSignature = Base64.encodeToString( signedContent, Base64.NO_PADDING or Base64.URL_SAFE or Base64.NO_WRAP, ) // 拼接完整JWT val token: String = "$encodedHeader.$encodedPayload.$encodedSignature"
4. 导出公钥为PEM格式
val keyStore = loadKeyStore() val publicKey = keyStore.getCertificate(keyStoreAlias)?.publicKey ?: return null val encoded = Base64.encodeToString(publicKey.encoded, Base64.DEFAULT) return "-----BEGIN PUBLIC KEY-----\n$encoded-----END PUBLIC KEY-----"
问题现象
运行代码后生成的JWT:
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjI3ZDA5YzcxLWJkNjctNDUyMy05N2JiLTEyZTkzM2U5YWNiYiIsImlhdCI6MTcwMjQ2Mjg1NH0.MEQCIGvk2N-3BDf13FAhAywXe7okYH4DygaViBWk6z6wnrvdAiBgJzHegGu8e9YSC9QiKqHvJxjyCRAX53tmmC_LaaFdRQ
使用jwt.io或token.dev等在线工具解码正常,但签名验证失败,已确认提供的公钥PEM格式正确。
已做的排查
- 用同一
Signature实例和公钥本地验证签名,结果为true - 确认Base64编码参数:Token的Header/Payload/Signature使用
NO_PADDING | URL_SAFE | NO_WRAP,公钥PEM使用DEFAULT - 尝试改用
KeyStore.getKey()获取私钥,问题依旧 - 验证公钥PEM格式正确,在线工具解析显示为ECDSA prime256v1、256位密钥
求问问题出在哪里?
内容的提问来源于stack exchange,提问作者Mackovich
相关产品推荐
相关产品推荐

