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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
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) }
|
||||
|
||||
Reference in New Issue
Block a user