Skip to content

Commit

Permalink
[android] Use tutasdk RSA impl in Android
Browse files Browse the repository at this point in the history
This brings it in line with the earlier iOS change. It also means we can
test encryption results, since seeds are accepted.
  • Loading branch information
paw-hub authored and tutao-mac committed Aug 9, 2024
1 parent c608e44 commit c1a87eb
Show file tree
Hide file tree
Showing 2 changed files with 13 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

/**
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
Expand Down

0 comments on commit c1a87eb

Please sign in to comment.