Search code examples
androidkotlinencryptionrsajwk

Android Encrypt CC Using RSA JWK


I'm trying to create an RSAPublicKeySpec with the JWK modulus and exponent provided by CyberSource. The goal is to use it to encrypt a given credit card number. I'm stuck with this error "RSA Exponent is even"

Example 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 code:

// Here I pass a CC number like "2134345613458954" and n (modulus) e (exponent) from JWK above
@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) // error here
    val cipherText = oaepFromAlgo.doFinal(Base64.getEncoder().encode(cardNumber.toByteArray()))
    return Base64.getEncoder().encodeToString(cipherText) // pass this to Flex
}

Example of expected result:

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

Solution

  • Modulus and exponent in the JWK are Base64url encoded and therefore must be Base64url decoded:

    ...
    val modulusBytes = Base64.getUrlDecoder().decode(modulus)
    val exponentBytes = Base64.getUrlDecoder().decode(exponent)
    val modules = BigInteger(1, modulusBytes)
    val exponent = BigInteger(1, exponentBytes)
    ...
    

    However your key is classified as weak by BC and throws an exception: RSA modulus has a small prime factor.

    This vulnerability is identified by this BC logic.


    If the key is to be used anyway: BouncyCastle allows disabling the check with

    System.getProperties().put("org.bouncycastle.rsa.allow_unsafe_mod", "true")
    

    However, this does not work on my machine under Kotlin with the BC Provider installed by default. You have to uninstall the old BC Provider and install a newer version:

    Security.removeProvider("BC")
    Security.addProvider(BouncyCastleProvider())
    System.getProperties().put("org.bouncycastle.rsa.allow_unsafe_mod", "true")
    

    with the corresponding BC entry in the gradle file under dependencies, e.g.

    implementation 'org.bouncycastle:bcprov-jdk18on:1.71' 
    

    This way the check can be disabled on my machine so that the exception is not thrown.