1
0
Fork 0
seedvault/app/src/main/java/com/stevesoltys/backup/crypto/KeyManager.kt
Torsten Grote 2ce625ac87
Huge refactoring of backup transport
* to get rid of global state
* to have a testable architecture
* to allow for authenticated encryption
* to have a backup format version
* to potentially allow for other storage plugins
2019-09-02 09:47:49 -03:00

72 lines
2.5 KiB
Kotlin

package com.stevesoltys.backup.crypto
import android.os.Build.VERSION.SDK_INT
import android.security.keystore.KeyProperties.*
import android.security.keystore.KeyProtection
import java.security.KeyStore
import java.security.KeyStore.SecretKeyEntry
import javax.crypto.SecretKey
import javax.crypto.spec.SecretKeySpec
internal const val KEY_SIZE = 256
private const val KEY_SIZE_BYTES = KEY_SIZE / 8
private const val KEY_ALIAS = "com.stevesoltys.backup"
private const val ANDROID_KEY_STORE = "AndroidKeyStore"
interface KeyManager {
/**
* Store a new backup key derived from the given [seed].
*
* The seed needs to be larger or equal to [KEY_SIZE_BYTES].
*/
fun storeBackupKey(seed: ByteArray)
/**
* @return true if a backup key already exists in the [KeyStore].
*/
fun hasBackupKey(): Boolean
/**
* Returns the backup key, so it can be used for encryption or decryption.
*
* Note that any attempt to export the key will return null or an empty [ByteArray],
* because the key can not leave the [KeyStore]'s hardware security module.
*/
fun getBackupKey(): SecretKey
}
class KeyManagerImpl : KeyManager {
private val keyStore by lazy {
KeyStore.getInstance(ANDROID_KEY_STORE).apply {
load(null)
}
}
override fun storeBackupKey(seed: ByteArray) {
if (seed.size < KEY_SIZE_BYTES) throw IllegalArgumentException()
// TODO check if using first 256 of 512 bytes produced by PBKDF2WithHmacSHA512 is safe!
val secretKeySpec = SecretKeySpec(seed, 0, KEY_SIZE_BYTES, "AES")
val ksEntry = SecretKeyEntry(secretKeySpec)
keyStore.setEntry(KEY_ALIAS, ksEntry, getKeyProtection())
}
override fun hasBackupKey() = keyStore.containsAlias(KEY_ALIAS) &&
keyStore.entryInstanceOf(KEY_ALIAS, SecretKeyEntry::class.java)
override fun getBackupKey(): SecretKey {
val ksEntry = keyStore.getEntry(KEY_ALIAS, null) as SecretKeyEntry
return ksEntry.secretKey
}
private fun getKeyProtection(): KeyProtection {
val builder = KeyProtection.Builder(PURPOSE_ENCRYPT or PURPOSE_DECRYPT)
.setBlockModes(BLOCK_MODE_GCM)
.setEncryptionPaddings(ENCRYPTION_PADDING_NONE)
.setRandomizedEncryptionRequired(true)
// unlocking is required only for decryption, so when restoring from backup
if (SDK_INT >= 28) builder.setUnlockedDeviceRequired(true)
return builder.build()
}
}