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=="
问题原因与修复方案
错误根源
- JWK的Base64解码错误:JWK中的
n和e是URL安全的Base64编码(无填充),但代码中直接将字符串转成ByteArray再转BigInteger,相当于把Base64字符串的ASCII字节当成了原始模数/指数的字节,完全不符合RSA公钥的格式要求,导致指数被解析成了偶数(合法RSA指数必须是奇数)。 - 加密前多余的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
相关产品推荐
相关产品推荐

