Fixed
Status Update
Comments
jm...@google.com <jm...@google.com>
da...@google.com <da...@google.com>
ap...@google.com <ap...@google.com> #2
Project: platform/frameworks/support
Branch: androidx-main
commit 5b9d0d058622ff58c87bd6d1f20d57d58d551de8
Author: Daniel Angell <danielangell@google.com>
Date: Mon Oct 17 13:38:29 2022
Eliminate race condition in EncryptedFile.Buidler.build()
Keys used with EncryptedFiles are assigned shared pref files
implicitely when they are first used to create an EncryptedFile.
To control access to this global filesystem state a new lock
is added around the operation that creates the shared prefs file.
Bug: 136590547
Test: Included new test
Change-Id: I723f161660ea33561aec18cff1fc32aa454c2850
M security/security-crypto/src/androidTest/java/androidx/security/crypto/EncryptedFileTest.java
M security/security-crypto/src/main/java/androidx/security/crypto/EncryptedFile.java
https://android-review.googlesource.com/2257884
Branch: androidx-main
commit 5b9d0d058622ff58c87bd6d1f20d57d58d551de8
Author: Daniel Angell <danielangell@google.com>
Date: Mon Oct 17 13:38:29 2022
Eliminate race condition in EncryptedFile.Buidler.build()
Keys used with EncryptedFiles are assigned shared pref files
implicitely when they are first used to create an EncryptedFile.
To control access to this global filesystem state a new lock
is added around the operation that creates the shared prefs file.
Bug: 136590547
Test: Included new test
Change-Id: I723f161660ea33561aec18cff1fc32aa454c2850
M security/security-crypto/src/androidTest/java/androidx/security/crypto/EncryptedFileTest.java
M security/security-crypto/src/main/java/androidx/security/crypto/EncryptedFile.java
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)