单元测试和运行时中不同代码的行为

时间:2018-11-22 16:26:33

标签: android kotlin

相同的代码在运行时有效,而在测试中无效

有这样的代码

private fun generatePrivateKeyFromText(key: String): Key {
    val kf = KeyFactory.getInstance("RSA")
    val keySpecPKCS8 = PKCS8EncodedKeySpec(Base64.decodeBase64(key))
    return kf.generatePrivate(keySpecPKCS8)
}

当我运行或调试应用程序时,它可以正常运行,但在 testing

上,此代码在 generatePrivate 上失败
java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException : algid parse error, not a sequence

@Test
fun decrypt() {
    val encrypt = "MoRxCpLJNqxfXGVeU73zZFi+X2j2TLUTyIn1XRqCoEfeN8rNBR/YrEtumAz+8/0AaEsvx0+qTilfbw+edZd8Wfum4McWQ8oWXifvWLgoXybhxWUmCdi2fwA9Gw0parY6CSNYUDA2UuLrLLaDGMz/Jj4s4XmXKp5zuec1zXVdrPM="
    val prkey = "MIICXAIBAAKBgQCAaTCQOxAZPfTXfgel2MMPUCLAf32TxfsXu71/c3mVFGtDPB+7IpmxCmEvAv6nlq1ymX1zRR5gIzNt4DZoM0RhE58eoksUXcmFcRnMX5V4bnI8DitHLdB2EZzdvnPX0Umzs+tE7I1ouDIocNQRsEoQeDcNPfz5av2zMPsg0Xl/yQIDAQABAoGAV8UOX5cvQsGZZ+2J1q8ZbI8OodrCf83z+V3mgYXxVZe2VSd0XNmiiWMZ2CNI4k3YUhtdpvtYbsfAsFpvdbuNAXMW82Zwsd0oluPzzoLELGkFvaUJlh2YGmizrBnEwvF0usJYwjsjUbXw3o1xKX8ILk5FBfdr2+L65YIIZ0UhqoECQQD/B0P8iZhoOTx7myhwlFCuVeSadwaOMGy2CgXRLvTFAtstz8YVO+D+yPKsEpAvMlFgEnkTt7tl8DRxMpKnfmO5AkEAgOZudjUD72xWfSJpCEcX76WGSASWE+fLCZ8C/HumIZ+0trW5/bsmBrI/6SldEJcy4b2bHh2nOggC/6R5rEUkkQJAAg779IDj0wuLOnAxLl90G0QkOT72tZUce4evLlYTsbdpL4B619cI5OWYV906frcIQx9DDO6xu4vp0HQZDPMPOQJAOVbH8ntY2ctmmdmRwWXmpusJ1cV8gTROJGSArpHOcAycFd628sCqhLYMKgsFZBjuQG7YrsfgGLdxpgijO1eykQJBAOE8+BrQwFWyOcgnUShPHo8mDOBkeplGr9VZdnWktac2aBr1Biovy+pipUyjSCAaFgOsSU0FDcK0I5ulTOpgMRg="
    val decrypt = CryptoService.decrypt(encrypt, prkey)
    assertEquals("Pika-pika", decrypt)
}

fun decrypt(ciphertext: String, key: String): String {
    var decodedBytes: ByteArray? = null
    try {
        val c = Cipher.getInstance("RSA")
        c.init(Cipher.DECRYPT_MODE, generatePrivateKeyFromText(key))
        decodedBytes = c.doFinal(Base64.decodeBase64(ciphertext))

    } catch (e: Exception) {
        Log.e("Crypto", "RSA decryption error: $e")
    }
    return String(decodedBytes ?: ByteArray(0))
}

工作功能在片段

 private fun testCrypto() {
        val encrypt = "MoRxCpLJNqxfXGVeU73zZFi+X2j2TLUTyIn1XRqCoEfeN8rNBR/YrEtumAz+8/0AaEsvx0+qTilfbw+edZd8Wfum4McWQ8oWXifvWLgoXybhxWUmCdi2fwA9Gw0parY6CSNYUDA2UuLrLLaDGMz/Jj4s4XmXKp5zuec1zXVdrPM="
        val prkey = "MIICXAIBAAKBgQCAaTCQOxAZPfTXfgel2MMPUCLAf32TxfsXu71/c3mVFGtDPB+7IpmxCmEvAv6nlq1ymX1zRR5gIzNt4DZoM0RhE58eoksUXcmFcRnMX5V4bnI8DitHLdB2EZzdvnPX0Umzs+tE7I1ouDIocNQRsEoQeDcNPfz5av2zMPsg0Xl/yQIDAQABAoGAV8UOX5cvQsGZZ+2J1q8ZbI8OodrCf83z+V3mgYXxVZe2VSd0XNmiiWMZ2CNI4k3YUhtdpvtYbsfAsFpvdbuNAXMW82Zwsd0oluPzzoLELGkFvaUJlh2YGmizrBnEwvF0usJYwjsjUbXw3o1xKX8ILk5FBfdr2+L65YIIZ0UhqoECQQD/B0P8iZhoOTx7myhwlFCuVeSadwaOMGy2CgXRLvTFAtstz8YVO+D+yPKsEpAvMlFgEnkTt7tl8DRxMpKnfmO5AkEAgOZudjUD72xWfSJpCEcX76WGSASWE+fLCZ8C/HumIZ+0trW5/bsmBrI/6SldEJcy4b2bHh2nOggC/6R5rEUkkQJAAg779IDj0wuLOnAxLl90G0QkOT72tZUce4evLlYTsbdpL4B619cI5OWYV906frcIQx9DDO6xu4vp0HQZDPMPOQJAOVbH8ntY2ctmmdmRwWXmpusJ1cV8gTROJGSArpHOcAycFd628sCqhLYMKgsFZBjuQG7YrsfgGLdxpgijO1eykQJBAOE8+BrQwFWyOcgnUShPHo8mDOBkeplGr9VZdnWktac2aBr1Biovy+pipUyjSCAaFgOsSU0FDcK0I5ulTOpgMRg="
        val decrypt = CryptoService.decrypt(encrypt, prkey)
        println(decrypt) // "Pika-pika"
    }

我在 onViewCreated

上调用它

已更新:

我添加了BC提供者(感谢@JamesKPolk)

private fun generatePrivateKeyFromText(key: String): Key {
    Security.addProvider(BouncyCastleProvider())
    val kf = KeyFactory.getInstance(algorithm)
    val keySpecPKCS8 = PKCS8EncodedKeySpec(Base64.decodeBase64(key))
    return kf.generatePrivate(keySpecPKCS8)
}

但是在运行时仍然可以,并且在测试时不能

javax.crypto.BadPaddingException: Decryption error

所以不同的运行代码没有出现问题。

运行时和测试崩溃代码之间有什么区别?

1 个答案:

答案 0 :(得分:1)

问题在于,私钥不是不是 PKCS8EncodedKeySpec,而是PKCS#1中的RSAPrivateKey对象。但是,BC提供者仍将对此错误进行解码,而不会提出投诉。但是,其他提供者将理所当然地抱怨。我的猜测是,运行时使用的是较旧版本的Android,默认提供程序为BC,但您的测试使用的是较新版本,情况并非如此。

解决方法是使您的私钥成为正确的PKCS8EncodedKeySpec。或者,您可以显式请求“ BC”提供程序。为此,您需要在getInstance()调用中指定“ BC”:val keyFactory = KeyFactory.getInstance("RSA", "BC")

但是,请注意,似乎BC provider support is on its way out

要转换为PKCS#1格式的私钥,请在base64 blob周围包装“ BEGIN RSA PRIVATE KEY”样式的标头和页脚,或将base64 blob解码并将其放置在文件中,然后运行:

openssl pkcs8 -topk8 -in privkey.pem  -outform der -nocrypt | openssl base64 -A

openssl pkcs8 -topk8 -in privkey.der -inform der -nocrypt  | openssl base64 -A

第二个问题来自对默认值的依赖。而不是

val c = Cipher.getInstance("RSA")

默认为mode和padding,因此不可移植,请始终将完整的“ algorithm / mode / padding ”转换字符串指定为Cipher.getInstance()。在这种情况下,您似乎没有填充数据(不安全模式),您将需要

val c = Cipher.getInstance("RSA/ECB/NoPadding")

但是,您确实应该使用适当的随机填充,目前是OAEP填充。

摘要

运行时环境是Android,但我认为测试环境是Oracle Java(或openjdk)。在这些环境中,显然存在两个关键差异:

  1. Android使用KeyFactory的BC提供程序,它将处理以PKCS#1 RSAPrivateKey格式编码的私钥。 Oracle Java仅支持PKCS8编码的密钥。
  2. 在Android中,Cipher.getInstance("RSA")默认为Cipher.getInstance("RSA/ECB/NoPadding"),但是Oracle Java默认为Cipher.getInstance("RSA/ECB/PKCS1Padding")