Fixed
Status Update
Comments
jm...@google.com <jm...@google.com>
da...@google.com <da...@google.com>
ap...@google.com <ap...@google.com> #2
Thanks for filing the issue.
This is a particularly hard device to come by - do you happen to have access to the device? If so could you provide us with the output of: adb shell dumpsys media.camera > info.txt
Thanks!
This is a particularly hard device to come by - do you happen to have access to the device? If so could you provide us with the output of: adb shell dumpsys media.camera > info.txt
Thanks!
Description
Once a keyset is persisted, further encryption over multiple threads works as expected.
For our app, this means that any encryption performed directly after a fresh install is potentially unable to be decrypted. Subsequent app runs don't show this behaviour.
Perhaps I am using the API incorrectly (it isn't supposed to be thread safe in the manner shown in the example code given), if this is the case then there is at the least a bug/omission in the API documentation.
This is a minimal reproduction example (androidx.security:security-crypto:1.0.0-alpha02):
// Code starts
import android.os.AsyncTask
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import androidx.security.crypto.EncryptedFile
import androidx.security.crypto.MasterKeys
import java.io.File
import java.util.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val directory = getDir(Date().time.toString(), MODE_PRIVATE)
val files = (0..9).map { index -> File(directory, index.toString()) }
exerciseBug(files)
}
private fun exerciseBug(files: Iterable<File>) {
val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
val context = this
class FileEncryptor(private val file: File) : AsyncTask<Unit, Unit, Unit>() {
override fun doInBackground(vararg params: Unit?) {
val encryptedFile = EncryptedFile.Builder(
file,
context,
masterKeyAlias,
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()
encryptedFile.openFileOutput().use { outputStream ->
val buffer = ByteArray(16384)
outputStream.write(buffer, 0, buffer.size)
outputStream.flush()
}
}
}
// Encrypt all the files in parallel
val fileEncryptors = files.map { file -> FileEncryptor(file) }
fileEncryptors.forEach { fileEncryptor -> fileEncryptor.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) }
// Wait for them all to finish
fileEncryptors.forEach { fileEncryptor -> fileEncryptor.get() }
// Decrypt each file serially
files.forEachIndexed { index, file ->
val encryptedFile = EncryptedFile.Builder(
file,
context,
masterKeyAlias,
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()
try {
encryptedFile.openFileInput().use { inputStream ->
val buffer = ByteArray(16384)
inputStream.read(buffer) // BOOM!
}
} catch (e: Exception) {
Log.e("securitycryptoracebug", "failed to decrypt file #$index", e)
}
}
}
}
// Code ends
The above code produces exceptions similar to:
java.io.IOException: No matching key found for the ciphertext in the stream.
at com.google.crypto.tink.streamingaead.InputStreamDecrypter.read(InputStreamDecrypter.java:187)
at com.google.crypto.tink.streamingaead.InputStreamDecrypter.read(InputStreamDecrypter.java:130)
at androidx.security.crypto.EncryptedFile$EncryptedFileInputStream.read(EncryptedFile.java:296)
at com.markbuer.androidxsecuritycryptoracebug.MainActivity.exerciseBug(MainActivity.kt:59)