在 Android 上使用 ES256 签署 JWT

问题描述 投票:0回答:1

我正在尝试生成 JWT 并使用私钥对其进行签名,在 Android 上,但我未能这样做,因为 jwt.io 或 token.dev 等在线解码器表示 他们无法验证签名 .


要求如下:

  • 密钥必须是 P-256 密钥,即带有 SHA-256 的 EC 算法 (ECDSA)。
  • 令牌的标头必须如下:
{
  "alg": "ES256",
  "typ": "JWT"
}
  • payload
    必须包含 id 和
    iat
    ,如下所示:
{
  "id": "7986f03e-a4b8-4033-87b9-af8ae0468a0b", // it's a UUID and this one is an example
  "iat": 1702390385 // timestamp in seconds from UTC
}

我已阅读以下有关 Android 的文档:


这是我想出的代码:

1) 生成 P-256 密钥对:

// 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)创建标头和有效负载,然后以 Base64 进行编码

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)对编码的标头和负载进行签名,然后以 Base64 进行编码

val signatureInput = "$encodedHeader.$encodedPayload"

val keyStore = loadKeyStore() // helper function to load the Android key store

// Retrieve Private Key
val entry: KeyStore.Entry = keyStore.getEntry(keyStoreAlias, null)
if (entry !is KeyStore.PrivateKeyEntry) {
    throw IllegalStateException("No such private key under the alias <$keyStoreAlias>")
}

// Setup signature & sign input
val ecdsaSign = Signature.getInstance("SHA256withECDSA")
ecdsaSign.initSign(entry.privateKey)
ecdsaSign.update(signatureInput.toByteArray()) // defaults to UTF-8

val signedContent = ecdsaSign.sign()
val encodedSignature = Base64.encodeToString(
    signedContent, 
    Base64.NO_PADDING or Base64.URL_SAFE or Base64.NO_WRAP,
)

// JWT complete
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-----"

使用这个逻辑,这是我得到的最终结果:

eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjI3ZDA5YzcxLWJkNjctNDUyMy05N2JiLTEyZTkzM2U5YWNiYiIsImlhdCI6MTcwMjQ2Mjg1NH0.MEQCIGvk2N-3BDf13FAhAywXe7okYH4DygaViBWk6z6wnrvdAiBgJzHegGu8e9YSC9QiKqHvJxjyCRAX53tmmC_LaaFdRQ

Jwt.io报告解码成功,但签名验证失败。我确保提供我的公钥的 PEM 表示形式。

我在这里做错了什么?


我做了什么调查和调试:

  1. 我尝试使用相同的
    Signature
    实例并使用公钥来验证签名。结果是
    true
  2. 由于我使用的是 Android Base64,所以在使用 Base64 编码时确保使用正确的标志。由于我们的 min SDK 设置为 24 (Android 7),Java.util 中的一个不可用:
  • 令牌内容以 Base64 编码,无需填充,对于 URL 来说是安全的,并且没有换行符(无换行),同时
  • 公钥的 PEM 表示形式使用默认标志进行编码。
  1. 我尝试使用
    getKey(...)
    上的
    KeyStore
    来检索
    PrivateKey
    ,这也可以工作,但在 jwt.io、token.dev 等上签名验证仍然失败。
  2. 我验证了 PEM 表示也是正确的。这是我的公钥:
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEa5M6OOsgL/+aR4jACkQxnjfRoJgw9fdsYR5ugxC6tBJIZA1oFRpjkc1TdQlbnE7BC6DEbaJPjH3jP0Lcnt2KWQ==
-----END PUBLIC KEY-----

我能够使用在线工具对其进行验证,例如:https://report-uri.com/home/pem_decoder。据报道:

Public Key Data
Key Algorithm: ECDSA prime256v1
Key Size: 256 bits
Raw Data
Array
(
    [bits] => 256
    [key] => -----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEa5M6OOsgL/+aR4jACkQxnjfRoJgw
9fdsYR5ugxC6tBJIZA1oFRpjkc1TdQlbnE7BC6DEbaJPjH3jP0Lcnt2KWQ==
-----END PUBLIC KEY-----
    [ec] => Array
        (
            [curve_name] => prime256v1
            [curve_oid] => 1.2.840.10045.3.1.7
            [x] => 6b933a38eb202fff9a4788c00a44319e37d1a09830f5f76c611e6e8310bab412
            [y] => 48640d68151a6391cd5375095b9c4ec10ba0c46da24f8c7de33f42dc9edd8a59
        )

    [type] => 3
)

感谢您的帮助。

android jwt digital-signature android-keystore ecdsa
1个回答
0
投票

您使用了错误的签名格式,即 ECDSA 签名的 ASN.1/DER 编码。对于 JWT,需要 IEEE P1363 格式 (r|s),请参阅 RFC 7518,第 3.4 节

尝试使用

SHA256withPlain-ECDSA
代替
SHA256withECDSA
(如有必要,使用 BouncyCastle)。

© www.soinside.com 2019 - 2024. All rights reserved.