Add Google Tink dependency, replacing androidx.security.crypto (#4405)
* Add Google Tink dependency, replacing `androidx.security.crypto` * Replace the `EncryptedFile` implementation too * Extract constants, add some more docs
This commit is contained in:
committed by
GitHub
parent
27299607f9
commit
e2afa9a691
@@ -81,6 +81,7 @@ google_firebase_bom = "com.google.firebase:firebase-bom:33.10.0"
|
|||||||
firebase_appdistribution_gradle = { module = "com.google.firebase:firebase-appdistribution-gradle", version.ref = "firebaseAppDistribution" }
|
firebase_appdistribution_gradle = { module = "com.google.firebase:firebase-appdistribution-gradle", version.ref = "firebaseAppDistribution" }
|
||||||
autonomousapps_dependencyanalysis_plugin = { module = "com.autonomousapps:dependency-analysis-gradle-plugin", version.ref = "dependencyAnalysis" }
|
autonomousapps_dependencyanalysis_plugin = { module = "com.autonomousapps:dependency-analysis-gradle-plugin", version.ref = "dependencyAnalysis" }
|
||||||
ksp_plugin = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
|
ksp_plugin = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
|
||||||
|
google_tink = "com.google.crypto.tink:tink-android:1.16.0"
|
||||||
|
|
||||||
# AndroidX
|
# AndroidX
|
||||||
androidx_core = { module = "androidx.core:core", version.ref = "core" }
|
androidx_core = { module = "androidx.core:core", version.ref = "core" }
|
||||||
@@ -100,7 +101,6 @@ androidx_browser = "androidx.browser:browser:1.8.0"
|
|||||||
androidx_lifecycle_runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" }
|
androidx_lifecycle_runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" }
|
||||||
androidx_lifecycle_process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "lifecycle" }
|
androidx_lifecycle_process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "lifecycle" }
|
||||||
androidx_splash = "androidx.core:core-splashscreen:1.0.1"
|
androidx_splash = "androidx.core:core-splashscreen:1.0.1"
|
||||||
androidx_security_crypto = "androidx.security:security-crypto:1.1.0-alpha06"
|
|
||||||
androidx_media3_exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3" }
|
androidx_media3_exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3" }
|
||||||
androidx_media3_ui = { module = "androidx.media3:media3-ui", version.ref = "media3" }
|
androidx_media3_ui = { module = "androidx.media3:media3-ui", version.ref = "media3" }
|
||||||
androidx_biometric = "androidx.biometric:biometric-ktx:1.2.0-alpha05"
|
androidx_biometric = "androidx.biometric:biometric-ktx:1.2.0-alpha05"
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ dependencies {
|
|||||||
implementation(libs.androidx.activity.activity)
|
implementation(libs.androidx.activity.activity)
|
||||||
implementation(libs.androidx.recyclerview)
|
implementation(libs.androidx.recyclerview)
|
||||||
implementation(libs.androidx.exifinterface)
|
implementation(libs.androidx.exifinterface)
|
||||||
implementation(libs.androidx.security.crypto)
|
|
||||||
api(libs.androidx.browser)
|
api(libs.androidx.browser)
|
||||||
|
|
||||||
testImplementation(projects.tests.testutils)
|
testImplementation(projects.tests.testutils)
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.libraries.androidutils.file
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.security.crypto.EncryptedFile
|
|
||||||
import androidx.security.crypto.MasterKey
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
class EncryptedFileFactory(
|
|
||||||
private val context: Context,
|
|
||||||
) {
|
|
||||||
fun create(file: File): EncryptedFile {
|
|
||||||
// We need to use the same key for all the encrypted files.
|
|
||||||
val masterKey = MasterKey.Builder(context)
|
|
||||||
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
|
||||||
.build()
|
|
||||||
return EncryptedFile.Builder(
|
|
||||||
context,
|
|
||||||
file,
|
|
||||||
masterKey,
|
|
||||||
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
|
|
||||||
).build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -23,7 +23,7 @@ dependencies {
|
|||||||
implementation(libs.sqldelight.driver.android)
|
implementation(libs.sqldelight.driver.android)
|
||||||
implementation(libs.sqlcipher)
|
implementation(libs.sqlcipher)
|
||||||
implementation(libs.sqlite)
|
implementation(libs.sqlite)
|
||||||
implementation(libs.androidx.security.crypto)
|
implementation(libs.google.tink)
|
||||||
|
|
||||||
implementation(projects.libraries.androidutils)
|
implementation(projects.libraries.androidutils)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.encrypteddb.crypto
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.google.crypto.tink.KeyTemplates
|
||||||
|
import com.google.crypto.tink.RegistryConfiguration
|
||||||
|
import com.google.crypto.tink.StreamingAead
|
||||||
|
import com.google.crypto.tink.integration.android.AndroidKeysetManager
|
||||||
|
import com.google.crypto.tink.streamingaead.StreamingAeadConfig
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is used to write and read encrypted data to/from a file.
|
||||||
|
*
|
||||||
|
* It's a simplified version of the same class in [androidx.security.crypto](https://developer.android.com/reference/androidx/security/crypto/package-summary).
|
||||||
|
*
|
||||||
|
* It uses hardcoded constants that are used in that library, for backwards compatibility reasons.
|
||||||
|
*/
|
||||||
|
internal class EncryptedFile(
|
||||||
|
private val context: Context,
|
||||||
|
private val file: File
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* The file content is encrypted using StreamingAead with AES-GCM, with the file name as associated data.
|
||||||
|
*/
|
||||||
|
private const val KEYSET_ENCRYPTION_SCHEME = "AES256_GCM_HKDF_4KB"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The keyset is stored in a shared preference file with this name.
|
||||||
|
*/
|
||||||
|
private const val KEYSET_PREF_FILE_NAME = "__androidx_security_crypto_encrypted_file_pref__"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The keyset is stored in a shared preference with this key, inside the specified file.
|
||||||
|
*/
|
||||||
|
private const val KEYSET_ALIAS = "__androidx_security_crypto_encrypted_file_keyset__"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The URI referencing the master key in the Android Keystore used to encrypt/decrypt the keyset.
|
||||||
|
*/
|
||||||
|
private const val MASTER_KEY_URI = "android-keystore://_androidx_security_master_key_"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val androidKeysetManager by lazy {
|
||||||
|
val keysetManagerBuilder = AndroidKeysetManager.Builder()
|
||||||
|
.withKeyTemplate(KeyTemplates.get(KEYSET_ENCRYPTION_SCHEME))
|
||||||
|
.withSharedPref(context, KEYSET_ALIAS, KEYSET_PREF_FILE_NAME)
|
||||||
|
.withMasterKeyUri(MASTER_KEY_URI)
|
||||||
|
|
||||||
|
keysetManagerBuilder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val streamingAead: StreamingAead by lazy {
|
||||||
|
val streamingAeadKeysetHandle = androidKeysetManager.keysetHandle
|
||||||
|
streamingAeadKeysetHandle.getPrimitive(RegistryConfiguration.get(), StreamingAead::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
StreamingAeadConfig.register()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun openFileOutput(): FileOutputStream {
|
||||||
|
val fos = FileOutputStream(file)
|
||||||
|
val stream = streamingAead.newEncryptingStream(fos, file.name.toByteArray())
|
||||||
|
return EncryptedFileOutputStream(fos.fd, stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun openFileInput(): FileInputStream {
|
||||||
|
val fis = FileInputStream(file)
|
||||||
|
val stream = streamingAead.newDecryptingStream(fis, file.name.toByteArray())
|
||||||
|
return EncryptedFileInputStream(fis.fd, stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.encrypteddb.crypto
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import java.io.FileDescriptor
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is used to read encrypted data from a file.
|
||||||
|
*
|
||||||
|
* It comes directly from [androidx.security.crypto](https://developer.android.com/reference/androidx/security/crypto/package-summary).
|
||||||
|
*/
|
||||||
|
internal class EncryptedFileInputStream(
|
||||||
|
fileDescriptor: FileDescriptor,
|
||||||
|
private val inputStream: InputStream,
|
||||||
|
) : FileInputStream(fileDescriptor) {
|
||||||
|
private val lock = Any()
|
||||||
|
|
||||||
|
override fun read(): Int = inputStream.read()
|
||||||
|
|
||||||
|
override fun read(b: ByteArray?): Int = inputStream.read(b)
|
||||||
|
|
||||||
|
override fun read(b: ByteArray?, off: Int, len: Int): Int = inputStream.read(b, off, len)
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
||||||
|
override fun readAllBytes(): ByteArray? = inputStream.readAllBytes()
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
||||||
|
override fun readNBytes(b: ByteArray?, off: Int, len: Int): Int = inputStream.readNBytes(b, off, len)
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
||||||
|
override fun readNBytes(len: Int): ByteArray? = inputStream.readNBytes(len)
|
||||||
|
|
||||||
|
override fun skip(n: Long): Long = inputStream.skip(n)
|
||||||
|
|
||||||
|
override fun available(): Int = inputStream.available()
|
||||||
|
|
||||||
|
override fun mark(readlimit: Int) = synchronized(lock) { inputStream.mark(readlimit) }
|
||||||
|
|
||||||
|
override fun markSupported(): Boolean = inputStream.markSupported()
|
||||||
|
|
||||||
|
override fun reset() = synchronized(lock) { inputStream.reset() }
|
||||||
|
|
||||||
|
override fun close() = inputStream.close()
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.encrypteddb.crypto
|
||||||
|
|
||||||
|
import java.io.FileDescriptor
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is used to write encrypted data to a file.
|
||||||
|
*
|
||||||
|
* It comes directly from [androidx.security.crypto](https://developer.android.com/reference/androidx/security/crypto/package-summary).
|
||||||
|
*/
|
||||||
|
internal class EncryptedFileOutputStream(
|
||||||
|
fileDescriptor: FileDescriptor,
|
||||||
|
private val outputStream: OutputStream
|
||||||
|
) : FileOutputStream(fileDescriptor) {
|
||||||
|
override fun write(b: ByteArray?) = outputStream.write(b)
|
||||||
|
|
||||||
|
override fun write(b: ByteArray?, off: Int, len: Int) = outputStream.write(b, off, len)
|
||||||
|
|
||||||
|
override fun write(b: Int) = outputStream.write(b)
|
||||||
|
|
||||||
|
override fun flush() = outputStream.flush()
|
||||||
|
|
||||||
|
override fun close() = outputStream.close()
|
||||||
|
}
|
||||||
@@ -8,8 +8,7 @@
|
|||||||
package io.element.encrypteddb.passphrase
|
package io.element.encrypteddb.passphrase
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.security.crypto.EncryptedFile
|
import io.element.encrypteddb.crypto.EncryptedFile
|
||||||
import io.element.android.libraries.androidutils.file.EncryptedFileFactory
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
|
|
||||||
@@ -25,7 +24,7 @@ class RandomSecretPassphraseProvider(
|
|||||||
private val secretSize: Int = 256,
|
private val secretSize: Int = 256,
|
||||||
) : PassphraseProvider {
|
) : PassphraseProvider {
|
||||||
override fun getPassphrase(): ByteArray {
|
override fun getPassphrase(): ByteArray {
|
||||||
val encryptedFile = EncryptedFileFactory(context).create(file)
|
val encryptedFile = EncryptedFile(context, file)
|
||||||
return if (!file.exists()) {
|
return if (!file.exists()) {
|
||||||
val secret = generateSecret()
|
val secret = generateSecret()
|
||||||
encryptedFile.openFileOutput().use { it.write(secret) }
|
encryptedFile.openFileOutput().use { it.write(secret) }
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ dependencies {
|
|||||||
implementation(libs.dagger)
|
implementation(libs.dagger)
|
||||||
implementation(libs.androidx.corektx)
|
implementation(libs.androidx.corektx)
|
||||||
implementation(libs.androidx.datastore.preferences)
|
implementation(libs.androidx.datastore.preferences)
|
||||||
implementation(libs.androidx.security.crypto)
|
|
||||||
implementation(platform(libs.network.retrofit.bom))
|
implementation(platform(libs.network.retrofit.bom))
|
||||||
implementation(libs.network.retrofit)
|
implementation(libs.network.retrofit)
|
||||||
implementation(libs.serialization.json)
|
implementation(libs.serialization.json)
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ dependencies {
|
|||||||
implementation(libs.sqldelight.driver.android)
|
implementation(libs.sqldelight.driver.android)
|
||||||
implementation(libs.sqlcipher)
|
implementation(libs.sqlcipher)
|
||||||
implementation(libs.sqlite)
|
implementation(libs.sqlite)
|
||||||
implementation(libs.androidx.security.crypto)
|
|
||||||
implementation(projects.libraries.di)
|
implementation(projects.libraries.di)
|
||||||
implementation(libs.sqldelight.coroutines)
|
implementation(libs.sqldelight.coroutines)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user