From c1a87eb90012a82cc78f319e8e8a3088a4876cba Mon Sep 17 00:00:00 2001 From: paw Date: Tue, 6 Aug 2024 17:02:08 +0200 Subject: [PATCH] [android] Use tutasdk RSA impl in Android This brings it in line with the earlier iOS change. It also means we can test encryption results, since seeds are accepted. --- .../de/tutao/tutanota/EncryptionCompatTest.kt | 5 +- .../tutanota/AndroidNativeCryptoFacade.kt | 101 ++---------------- 2 files changed, 13 insertions(+), 93 deletions(-) diff --git a/app-android/app/src/androidTest/java/de/tutao/tutanota/EncryptionCompatTest.kt b/app-android/app/src/androidTest/java/de/tutao/tutanota/EncryptionCompatTest.kt index 435451ead7ad..41766ffd8f8c 100644 --- a/app-android/app/src/androidTest/java/de/tutao/tutanota/EncryptionCompatTest.kt +++ b/app-android/app/src/androidTest/java/de/tutao/tutanota/EncryptionCompatTest.kt @@ -140,9 +140,8 @@ class CompatibilityTest { for (testData in testData.rsaEncryptionTests) { val publicKeyJSON = hexToPublicKey(testData.publicKey) val encryptedResult: ByteArray = crypto.rsaEncrypt(publicKeyJSON, hexToBytes(testData.input).wrap(), hexToBytes(testData.seed).wrap()).data - //String hexResult = bytesToHex(encryptedResultBytes); - //assertEquals(testData.getResult(), hexResult); - //cannot compare encrypted test data because default android implementation ignores randomizer + val hexResult = bytesToHex(encryptedResult) + assertEquals(testData.result, hexResult) val plainText = crypto.rsaDecrypt(hexToPrivateKey(testData.privateKey), encryptedResult.wrap()).data assertEquals(testData.input, bytesToHex(plainText)) val plainTextFromTestData = crypto.rsaDecrypt(hexToPrivateKey(testData.privateKey), hexToBytes(testData.result).wrap()).data diff --git a/app-android/app/src/main/java/de/tutao/tutanota/AndroidNativeCryptoFacade.kt b/app-android/app/src/main/java/de/tutao/tutanota/AndroidNativeCryptoFacade.kt index d76a1b176a30..d8cbafb3642b 100644 --- a/app-android/app/src/main/java/de/tutao/tutanota/AndroidNativeCryptoFacade.kt +++ b/app-android/app/src/main/java/de/tutao/tutanota/AndroidNativeCryptoFacade.kt @@ -13,14 +13,8 @@ import kotlinx.coroutines.withContext import org.apache.commons.io.IOUtils import org.apache.commons.io.input.BoundedInputStream import java.io.* -import java.math.BigInteger import java.security.* -import java.security.interfaces.RSAPrivateCrtKey -import java.security.interfaces.RSAPublicKey -import java.security.spec.InvalidKeySpecException import java.security.spec.MGF1ParameterSpec -import java.security.spec.RSAPrivateKeySpec -import java.security.spec.RSAPublicKeySpec import java.util.* import javax.crypto.* import javax.crypto.spec.IvParameterSpec @@ -38,7 +32,6 @@ class AndroidNativeCryptoFacade( const val AES_BLOCK_SIZE_BYTES = 16 val FIXED_IV = ByteArray(AES_BLOCK_SIZE_BYTES).apply { fill(0x88.toByte()) } const val RSA_KEY_LENGTH_IN_BITS = 2048 - const val RSA_ALGORITHM = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding" const val RSA_PUBLIC_EXPONENT = 65537 /** @@ -195,69 +188,34 @@ class AndroidNativeCryptoFacade( seed: DataWrapper, ): DataWrapper { try { - return this.rsaEncrypt( - javaPublicKey(publicKey), + return de.tutao.tutasdk.rsaEncryptWithPublicKeyComponents( data.data, - seed.data + seed.data, + publicKey.modulus, + publicKey.publicExponent.toUInt() ).wrap() - } catch (e: InvalidKeySpecException) { + } catch (e: de.tutao.tutasdk.RsaException) { // These types of errors can happen and that's okay, they should be handled gracefully. throw CryptoError(e) } } - /** - * Encrypts an aes key with RSA to a byte array. - */ - @Throws(CryptoError::class) - fun rsaEncrypt(publicKey: PublicKey, data: ByteArray, random: ByteArray): ByteArray { - randomizer.setSeed(random) - return rsaEncrypt(data, publicKey, randomizer) - } - - @Throws(CryptoError::class) - private fun rsaEncrypt(data: ByteArray, publicKey: PublicKey, randomizer: SecureRandom): ByteArray { - return try { - val cipher = Cipher.getInstance(RSA_ALGORITHM) - cipher.init(Cipher.ENCRYPT_MODE, publicKey, OAEP_PARAMETER_SPEC, randomizer) - cipher.doFinal(data) - } catch (e: BadPaddingException) { - throw CryptoError(e) - } catch (e: IllegalBlockSizeException) { - throw CryptoError(e) - } catch (e: InvalidKeyException) { - throw CryptoError(e) - } - } - @Throws(CryptoError::class) override suspend fun rsaDecrypt(privateKey: RsaPrivateKey, data: DataWrapper): DataWrapper { try { - return rsaDecrypt( - javaPrivateKey(privateKey), + return de.tutao.tutasdk.rsaDecryptWithPrivateKeyComponents( data.data, + privateKey.modulus, + privateKey.privateExponent, + privateKey.primeP, + privateKey.primeQ ).wrap() - } catch (e: InvalidKeySpecException) { + } catch (e: de.tutao.tutasdk.RsaException) { // These types of errors can happen and that's okay, they should be handled gracefully. throw CryptoError(e) } } - @Throws(CryptoError::class) - fun rsaDecrypt(privateKey: PrivateKey, encryptedKey: ByteArray): ByteArray { - return try { - val cipher = Cipher.getInstance(RSA_ALGORITHM) - cipher.init(Cipher.DECRYPT_MODE, privateKey, OAEP_PARAMETER_SPEC, randomizer) - cipher.doFinal(encryptedKey) - } catch (e: BadPaddingException) { - throw CryptoError(e) - } catch (e: InvalidKeyException) { - throw CryptoError(e) - } catch (e: IllegalBlockSizeException) { - throw CryptoError(e) - } - } - @Throws(IOException::class, CryptoError::class) override suspend fun aesEncryptFile(key: DataWrapper, fileUri: String, iv: DataWrapper): EncryptedFileInfo { val parsedFileUri = Uri.parse(fileUri) @@ -501,43 +459,6 @@ class AndroidNativeCryptoFacade( } } - @Throws(InvalidKeySpecException::class) - private fun javaPublicKey(key: RsaPublicKey): PublicKey { - val modulus = BigInteger(key.modulus.base64ToBytes()) - val keyFactory = KeyFactory.getInstance("RSA") - return keyFactory.generatePublic(RSAPublicKeySpec(modulus, BigInteger.valueOf(RSA_PUBLIC_EXPONENT.toLong()))) - } - - @Throws(InvalidKeySpecException::class) - private fun javaPrivateKey(key: RsaPrivateKey): PrivateKey { - val modulus = BigInteger(key.modulus.base64ToBytes()) - val privateExponent = BigInteger(key.privateExponent.base64ToBytes()) - val keyFactory = KeyFactory.getInstance("RSA") - return keyFactory.generatePrivate(RSAPrivateKeySpec(modulus, privateExponent)) - } - - private fun BigInteger.toBase64() = toByteArray().toBase64() - - private fun PrivateKey(javaKey: RSAPrivateCrtKey) = RsaPrivateKey( - version = 0, - // TODO: is this correct? - keyLength = RSA_KEY_LENGTH_IN_BITS, - modulus = javaKey.modulus.toBase64(), - privateExponent = javaKey.privateExponent.toBase64(), - primeP = javaKey.primeP.toBase64(), - primeQ = javaKey.primeQ.toBase64(), - primeExponentP = javaKey.primeExponentP.toBase64(), - primeExponentQ = javaKey.primeExponentQ.toBase64(), - crtCoefficient = javaKey.crtCoefficient.toBase64(), - ) - - private fun PublicKey(javaKey: RSAPublicKey) = RsaPublicKey( - version = 0, - keyLength = RSA_KEY_LENGTH_IN_BITS, - modulus = javaKey.modulus.toBase64(), - publicExponent = RSA_PUBLIC_EXPONENT, - ) - private fun hasMac(dataLength: Long): Boolean { return dataLength % 2 == 1L }