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

Android使用RSA JWK加密信用卡报错‘RSA Exponent is even’求助

问题:RSA公钥加密时触发“RSA Exponent is even”错误

尝试使用CyberSource提供的JWK中的模数(modulus)和指数(exponent)创建RSAPublicKeySpec,对指定信用卡号进行RSA加密,但始终遇到**"RSA Exponent is even"**错误。

示例JWK

{"kty": "RSA","use": "enc","kid": "05BgbFie7vX5vzSMKOoqEAAdfpdR4kas","n": "fC1G6rVue4w_jjJrKPZusGN9G-Y7mWuLJ0O2_GHd94LvR51-ok7ahuQUVMZLdigixnspaGo_WVLvTTZ5J28Cc1uSx0o_BsxpNaAQD8_aBZL3nnAiBLACxI1JHAVo7SXAJQmz-mqVFYTppg9QmpB2ATTmXjUOQy-Fqkw3EByfCANHhHSNs4-HASovsfcRMUmmvDfTd5qBb23KzDJeDVqTYWa3XjUorlZOCJuLyPgeDEK8oOC9C4W9dn32z8FJ4E6Dz28M_2O3g8FLQD2F-NezkQJsl8MEYo4rl1nr7_oIkMsYLCCoG8jwmryErb7km9JWWgqZ80trkjijFqDAbHfUEw","e": "AQAB"}

出错的Android Kotlin代码

// 传入的参数是信用卡号(比如"2134345613458954"),以及上述JWK中的n和e
@RequiresApi(Build.VERSION_CODES.O)
fun generateRSAEncryption(cardNumber: String, modulus: String, exponent: String): String {
    val modules = BigInteger(1, modulus.toByteArray())
    val exponent = BigInteger(1, exponent.toByteArray())
    val pubSpec = RSAPublicKeySpec(modules, exponent)
    val publicKey = KeyFactory.getInstance("RSA").generatePublic(pubSpec)
    val oaepFromAlgo = Cipher.getInstance("RSA/None/OAEPWithSHA256AndMGF1Padding", "BC")
    oaepFromAlgo.init(Cipher.ENCRYPT_MODE, publicKey) // 错误发生在此行
    val cipherText = oaepFromAlgo.doFinal(Base64.getEncoder().encode(cardNumber.toByteArray()))
    return Base64.getEncoder().encodeToString(cipherText) // 将结果传给Flex
}

预期加密结果示例

"LFDh8upgXaiUX0iNaCOcHgeaotRCNsDpdJf5SqACpiL38JVnpHW7bb/g3yM67uett1tUSPG9o1yexNaAneur4P2jbpSnU0kWWK7NpLIQWAvjmCVxGWceZdFPGvB+E2hQncvIImlYo+d/XIHZOUonVmDoj+pKouxmd60lpaMTrq7sJ8BrfWCDG1lJJ0M2S98CoDb19xK+XCn+cpd3KkTHsGJGHA6inT2stHxYJrF7dd3r1xuH0WW1gpRnRaXwl6BFZW9EzCCzaWZmifZYIPFXZIE44pU9xRCfjD1IUKXKLxw0l6cFAlaP0SHG2t9HDDMLjNQjvqRarFiPoAjtwfW7Zw=="

问题原因与修复方案

错误根源

  1. JWK的Base64解码错误:JWK中的n和e是URL安全的Base64编码(无填充),但代码中直接将字符串转成ByteArray再转BigInteger,相当于把Base64字符串的ASCII字节当成了原始模数/指数的字节,完全不符合RSA公钥的格式要求,导致指数被解析成了偶数(合法RSA指数必须是奇数)。
  2. 加密前多余的Base64编码:代码中对信用卡号先做了Base64编码再加密,这会导致加密内容和预期不符,应该直接使用卡号的原始字节数组。

修正后的代码

@RequiresApi(Build.VERSION_CODES.O)
fun generateRSAEncryption(cardNumber: String, modulus: String, exponent: String): String {
    // 1. 解码JWK中的URL安全Base64格式的n和e
    val modulusBytes = Base64.getUrlDecoder().decode(modulus)
    val exponentBytes = Base64.getUrlDecoder().decode(exponent)
    
    // 2. 从解码后的字节数组创建BigInteger
    val modulusBigInt = BigInteger(1, modulusBytes)
    val exponentBigInt = BigInteger(1, exponentBytes)
    
    // 3. 生成RSA公钥
    val pubSpec = RSAPublicKeySpec(modulusBigInt, exponentBigInt)
    val publicKey = KeyFactory.getInstance("RSA").generatePublic(pubSpec)
    
    // 4. 初始化加密算法(使用BC提供者)
    val oaepCipher = Cipher.getInstance("RSA/None/OAEPWithSHA256AndMGF1Padding", "BC")
    oaepCipher.init(Cipher.ENCRYPT_MODE, publicKey)
    
    // 5. 直接加密卡号的原始字节,再对加密结果做Base64编码
    val cipherText = oaepCipher.doFinal(cardNumber.toByteArray(Charsets.UTF_8))
    return Base64.getEncoder().encodeToString(cipherText)
}

关键修改点说明

  • 使用Base64.getUrlDecoder()解码JWK的n和e,这是符合JWK标准的解码方式。
  • 加密时直接传入卡号的UTF-8字节数组,而非先做Base64编码,确保加密内容正确。
  • 指定字符集为Charsets.UTF_8,避免因系统默认字符集差异导致的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 04:54:34