在 Android 密钥库中安全地存储密钥

Posted

技术标签:

【中文标题】在 Android 密钥库中安全地存储密钥【英文标题】:Securely Storing Keys in Android Keystore 【发布时间】:2015-11-24 18:18:03 【问题描述】:

我正在制作一个与服务器通信的 android 应用程序。我在我的服务器上使用基于令牌的身份验证,并将信息从服务器传递给客户端,我使用的是非对称加密。

流程是这样的

    生成的公钥和私钥已经预先存在 公钥用于加密信息,然后从服务器传递到客户端 应用使用私钥解密信息

但是,我不知道如何将私钥安全地存储在密钥库中。如果我在运行时存储它,密钥将在代码中,如果我在 REST 连接期间发送私钥,那么加密没有意义,因为黑客可以找到这两个密钥。任何人都可以帮助我创建最佳解决方案吗?提前谢谢!

【问题讨论】:

如果服务器只需要发送信息而不接收信息,那么您可能需要考虑让客户端在安装后生成自己的私钥,将该密钥存储在 android keystore 中,然后发送其服务器的公钥。分发私钥几乎总是错误的做事方式,让每个客户使用相同的私钥更糟糕。 ***.com/a/67779409/6314955 看看这个 【参考方案1】:

您可以将您的私钥存储在共享首选项中,但使用生成的密钥进行加密,该密钥将存储在 Android KeyStore 中,这将为存储私钥提供更高的安全性。

请参阅下面的 Kotlin 示例。 首先,您需要生成密钥:

fun generateSecretKey(): SecretKey 
    val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
    val spec = KeyGenParameterSpec
            .Builder(secretKeyAlias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
            .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
            .build()

    keyGenerator.init(spec)
    return keyGenerator.generateKey()

它将自动存储在KeyStore 中,因为我们在获取KeyGenerator 的实例时将其作为提供者提及。

稍后,当您需要再次获取密钥时,您可以这样做:

fun getSecretKey(): SecretKey 
    val keyStore = KeyStore.getInstance("AndroidKeyStore").apply  load(null) 
    val secretKeyEntry = keyStore.getEntry(secretKeyAlias, null) as KeyStore.SecretKeyEntry
    return secretKeyEntry.secretKey

或者您可以随时使用getSecretKey() 方法,如果从KeyStore 获得的是null,则将最后一行更改为:

return secretKeyEntry.secretKey ?: generateSecretKey()

获得SecretKey后,您可以继续加密:

fun encrypt(data: String): ByteArray? 
    val cipher = Cipher.getInstance("AES/GCM/NoPadding")
    cipher.init(Cipher.ENCRYPT_MODE, getSecretKey())
    iv = cipher.iv
    return cipher.doFinal(data.toByteArray())

在这里,方法encrypt 将返回一个ByteArray,您可以将其存储在SharedPreferences 中。 注意:您还应该存储初始化向量 (IV)。在这里它被存储到iv 属性中。

要解密存储的数据,请使用以下方法:

fun decrypt(encrypted: ByteArray): String 
    val cipher = Cipher.getInstance("AES/GCM/NoPadding")
    val spec = GCMParameterSpec(128, iv)
    cipher.init(Cipher.DECRYPT_MODE, getSecretKey(), spec)
    val decoded = cipher.doFinal(encrypted)
    return String(decoded, Charsets.UTF_8)

在这里,您必须将存储初始化向量 (IV) 传递给 GCMParameterSpec

希望它对某人有所帮助。

【讨论】:

keyStore.getEntry(secretKeyAlias, null) 在某些设备的 Android 9 上为我解密时返回 null,您知道为什么会发生这种情况吗? ***.com/questions/57993508/… 我理解这个概念,但是要将私钥保存在共享首选项中,您必须在 android 代码中保存一次,并且由于某种原因,如果用户删除应用程序共享首选项将会丢失。在这种情况下,它们现在没有共享偏好中的私钥。那该怎么办呢?

以上是关于在 Android 密钥库中安全地存储密钥的主要内容,如果未能解决你的问题,请参考以下文章

使用PHP / MySQLI / Apache时,在哪里安全地存储证书/密钥?

Android密钥库系统KeyStore

在 Android 的密钥库中存储私钥

如何在 Android 中存储媒体 DRM 密钥以供离线使用

如何在android中存储硬编码的加密主密钥?

如何安全地存储加密密钥?