Android AES 大文件加密/解密失败且内存不足

问题描述 投票:0回答:1

我尝试使用 AES 加密 Android 中的大文件,但因内存不足错误而失败。

这是我正在使用的代码:

import android.content.Context
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import timber.log.Timber
import java.io.IOException
import javax.crypto.Cipher
import javax.crypto.CipherOutputStream
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.SecretKeySpec

suspend fun encryptFile(
    context: Context,
    file: DocumentFile,
    secretByte: ByteArray,
    iv: ByteArray
) {
    try {
        val bufferSize = 1024 * 1024
        val secretKeySpec = SecretKeySpec(secretByte, "AES")

        val encryptedFileUri = createEncryptedFileUri(file)
            ?: throw IOException("Failed to create URI for encrypted file")
        
        context.contentResolver.openOutputStream(encryptedFileUri)?.use { outputStream ->


            context.contentResolver.openInputStream(file.uri)?.use { inputStream ->
                val buffer = ByteArray(bufferSize)
                var bytesRead: Int

                val cipher = Cipher.getInstance("AES/GCM/NoPadding")
                val gcmSpec = GCMParameterSpec(128, iv)
                cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, gcmSpec)

                CipherOutputStream(outputStream, cipher).use { cipherOutputStream ->
                    while (inputStream.read(buffer).also { bytesRead = it } != -1) {
                        cipherOutputStream.write(buffer, 0, bytesRead)
                    }
                    cipherOutputStream.flush()
                }

            } ?: throw IOException("Failed to open input stream for file: ${file.uri}")
        } ?: throw IOException("Failed to open output stream for file: $encryptedFileUri")

        if (!file.delete()) {
            Timber.e("Failed to delete original file: ${file.uri}")
        }
    } catch (e: Exception) {
        e.printStackTrace()
        Timber.e("Error during file encryption: ${e.message}")
    }
}

private fun createEncryptedFileUri(file: DocumentFile): Uri? {
    val parentDirectory = file.parentFile ?: throw IllegalArgumentException("No parent directory")
    return parentDirectory.createFile("*/*", file.name + ".aesEncr")?.uri
}

我遇到的错误:

java.lang.OutOfMemoryError: Failed to allocate a 266338320 byte allocation with 25165824 free bytes and 121MB until OOM, target footprint 166187728, growth limit 268435456
    at com.android.org.conscrypt.OpenSSLAeadCipher.expand(OpenSSLAeadCipher.java:127)
    at com.android.org.conscrypt.OpenSSLAeadCipher.updateInternal(OpenSSLAeadCipher.java:300)
    at com.android.org.conscrypt.OpenSSLCipher.engineUpdate(OpenSSLCipher.java:332)
    at javax.crypto.Cipher.update(Cipher.java:1741)
    at javax.crypto.CipherOutputStream.write(CipherOutputStream.java:158)                                                                                                   
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
    at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:115)
    at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:103)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:584)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:793)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:697)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:684)
Suppressed: java.lang.OutOfMemoryError: Failed to allocate a 132120608 byte allocation with 25165824 free bytes and 121MB until OOM, target footprint 166188144, growth limit 268435456
    at com.android.org.conscrypt.OpenSSLCipher.engineDoFinal(OpenSSLCipher.java:359)
    at javax.crypto.Cipher.doFinal(Cipher.java:1957)
android encryption memory-management out-of-memory aes
1个回答
0
投票

我可以使用下面的代码解决这个问题。
不要使用 CipherOutputStream,而是使用 缓冲区作为独立字节来加密

 fun encryptFile(
        context: Context,
        file: DocumentFile,
        secretByte: ByteArray,
        iv: ByteArray
    ) {
        try {
            val bufferSize = 1024 * 1024
            val encryptedFileUri = createEncryptedFileUri(file)
                ?: throw IOException("Failed to create URI for encrypted file")
    
            context.contentResolver.openInputStream(file.uri).use { inputStream ->
                context.contentResolver.openOutputStream(encryptedFileUri).use { outputStream ->
                    val buffer = ByteArray(bufferSize)
                    var bytesRead: Int
                    while (inputStream!!.read(buffer).also { bytesRead = it } != -1) {
                        val resultByte = encryptByte(buffer, secretByte, iv = iv)
                        outputStream?.write(resultByte, 0, resultByte.size)
                    }
                }
            }
    
            if (!file.delete()) {
                Timber.e("Failed to delete original file: ${file.uri}")
            }
    
        } catch (e: Exception) {
            e.printStackTrace()
            Timber.e("Error during file encryption: ${e.message}")
        }
    }
    
    
    fun encryptByte(byte: ByteArray, key: ByteArray, iv: ByteArray): ByteArray {
        val cipher = Cipher.getInstance("AES/GCM/NoPadding")
        val gcmSpec = GCMParameterSpec(128, iv)
        val secretKey = SecretKeySpec(key, "AES")
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, gcmSpec)
        return cipher.doFinal(byte)
    }
© www.soinside.com 2019 - 2024. All rights reserved.