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:
Jorge Martin Espinosa
2025-03-17 10:04:21 +01:00
committed by GitHub
parent 27299607f9
commit e2afa9a691
10 changed files with 171 additions and 38 deletions

View File

@@ -23,7 +23,7 @@ dependencies {
implementation(libs.sqldelight.driver.android)
implementation(libs.sqlcipher)
implementation(libs.sqlite)
implementation(libs.androidx.security.crypto)
implementation(libs.google.tink)
implementation(projects.libraries.androidutils)
}

View File

@@ -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)
}
}

View File

@@ -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()
}

View File

@@ -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()
}

View File

@@ -8,8 +8,7 @@
package io.element.encrypteddb.passphrase
import android.content.Context
import androidx.security.crypto.EncryptedFile
import io.element.android.libraries.androidutils.file.EncryptedFileFactory
import io.element.encrypteddb.crypto.EncryptedFile
import java.io.File
import java.security.SecureRandom
@@ -25,7 +24,7 @@ class RandomSecretPassphraseProvider(
private val secretSize: Int = 256,
) : PassphraseProvider {
override fun getPassphrase(): ByteArray {
val encryptedFile = EncryptedFileFactory(context).create(file)
val encryptedFile = EncryptedFile(context, file)
return if (!file.exists()) {
val secret = generateSecret()
encryptedFile.openFileOutput().use { it.write(secret) }