2021 年在 Android 中加密字符串的最佳和最安全的方法是啥?

Posted

技术标签:

【中文标题】2021 年在 Android 中加密字符串的最佳和最安全的方法是啥?【英文标题】:What is in 2021 the best and safest way to encrypt Strings in Android?2021 年在 Android 中加密字符串的最佳和最安全的方法是什么? 【发布时间】:2021-12-25 10:25:36 【问题描述】:

我惊讶地发现 Jatpack Security 提供了only support for File and SharedPreferences encryption。但是我需要能够加密和解密Strings,因为我想使用AccountManager 并存储刷新和访问令牌,并且按照官方文档中的建议,这种数据should be send encrypted to the AccountManager

在线搜索有很多关于如何在 android 上加密 Strings 的教程,但其中大多数似乎已经很老了,我害怕选择错误的教程可能会导致 Play 上出现这种警告商店控制台:

那么,2021 年在 Android 应用程序中加密Strings 的正确且安全的方法是什么? Jetpack Security 是否还能在一定程度上使用(也许是生成密钥?)以及为什么它不支持开箱即用的字符串加密,而只支持 Files 和 SharedPreferences

【问题讨论】:

将字符串存储在文件中。加密文件。 加密字符串真的是最佳实践吗? 如果不在文件中,您如何存储字符串? “我需要使用 AccountManager 并存储刷新和访问令牌”——AccountManager 似乎在 Android 应用开发圈子中并不是特别受欢迎。为什么你觉得你需要使用它? “为什么它不支持开箱即用的字符串加密”——“为什么开发人员 X 做出决定 Y?”形式的问题。对堆栈溢出不是很好。通常只有开发者 X 可以回答这个问题,开发者 X 也不太可能看到这个问题。 @rossum 就像我在问题中所说的那样,AccountManager:developer.android.com/reference/android/accounts/… 【参考方案1】:

在深入了解EncryptedSharedPreferencesEncryptedFile 的实现之后,我设法创建了一个CryptoHelper 类,它使用与Jetpack Security 中的两个类相同的方法,提供了一种加密、解密、签名并验证ByteArrays:

import android.content.Context
import androidx.security.crypto.MasterKeys
import com.google.crypto.tink.Aead
import com.google.crypto.tink.DeterministicAead
import com.google.crypto.tink.KeyTemplate
import com.google.crypto.tink.KeyTemplates
import com.google.crypto.tink.PublicKeySign
import com.google.crypto.tink.PublicKeyVerify
import com.google.crypto.tink.aead.AeadConfig
import com.google.crypto.tink.daead.DeterministicAeadConfig
import com.google.crypto.tink.integration.android.AndroidKeysetManager
import com.google.crypto.tink.signature.SignatureConfig
import java.io.IOException
import java.security.GeneralSecurityException

/**
 * Class used to encrypt, decrypt, sign ad verify data.
 *
 * <pre>
 * // Encrypt
 * val cypherText = cryptoHelper.encrypt(text.toByteArray())
 * // Decrypt
 * val plainText = cryptoHelper.decrypt(cypherText)
 * // Sign
 * val signature = cryptoHelper.sign(text.toByteArray())
 * // Verify
 * val verified = cryptoHelper.verify(signature, text.toByteArray())
 * </pre>
 */
@Suppress("unused")
class CryptoHelper(
    private val aead: Aead,
    private val deterministicAead: DeterministicAead,
    private val signer: PublicKeySign,
    private val verifier: PublicKeyVerify,
) 

    /**
     * Builder class to configure CryptoHelper
     */
    class Builder(
        // Required parameters
        private val context: Context,
    ) 
        // Optional parameters
        private var masterKeyAlias: String = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
        private var keysetPrefName = KEYSET_PREF_NAME
        private var keysetAlias = KEYSET_ALIAS
        private var aeadKeyTemplate: KeyTemplate
        private var deterministicAeadKeyTemplate: KeyTemplate
        private var signKeyTemplate: KeyTemplate

        init 
            AeadConfig.register()
            DeterministicAeadConfig.register()
            SignatureConfig.register()
            aeadKeyTemplate = KeyTemplates.get("AES256_GCM")
            deterministicAeadKeyTemplate = KeyTemplates.get("AES256_SIV")
            signKeyTemplate = KeyTemplates.get("ECDSA_P256")
        

        /**
         * @param masterKey The SharedPreferences file to store the keyset.
         * @return This Builder
         */
        fun setMasterKey(masterKey: String): Builder 
            this.masterKeyAlias = masterKey
            return this
        

        /**
         * @param keysetPrefName The SharedPreferences file to store the keyset.
         * @return This Builder
         */
        fun setKeysetPrefName(keysetPrefName: String): Builder 
            this.keysetPrefName = keysetPrefName
            return this
        

        /**
         * @param keysetAlias The alias in the SharedPreferences file to store the keyset.
         * @return This Builder
         */
        fun setKeysetAlias(keysetAlias: String): Builder 
            this.keysetAlias = keysetAlias
            return this
        

        /**
         * @param keyTemplate If the keyset for Aead encryption is not found or valid, generates a new one using keyTemplate.
         * @return This Builder
         */
        fun setAeadKeyTemplate(keyTemplate: KeyTemplate): Builder 
            this.aeadKeyTemplate = keyTemplate
            return this
        

        /**
         * @param keyTemplate If the keyset for deterministic Aead encryption is not found or valid, generates a new one using keyTemplate.
         * @return This Builder
         */
        fun setDeterministicAeadKeyTemplate(keyTemplate: KeyTemplate): Builder 
            this.deterministicAeadKeyTemplate = keyTemplate
            return this
        

        /**
         * @param keyTemplate If the keyset for signing/verifying is not found or valid, generates a new one using keyTemplate.
         * @return This Builder
         */
        fun setSignKeyTemplate(keyTemplate: KeyTemplate): Builder 
            this.signKeyTemplate = keyTemplate
            return this
        

        /**
         * @return An CryptoHelper with the specified parameters.
         */
        @Throws(GeneralSecurityException::class, IOException::class)
        fun build(): CryptoHelper 
            val aeadKeysetHandle = AndroidKeysetManager.Builder()
                .withKeyTemplate(aeadKeyTemplate)
                .withSharedPref(context, keysetAlias + "_aead__", keysetPrefName)
                .withMasterKeyUri(KEYSTORE_PATH_URI + masterKeyAlias)
                .build().keysetHandle
            val deterministicAeadKeysetHandle = AndroidKeysetManager.Builder()
                .withKeyTemplate(deterministicAeadKeyTemplate)
                .withSharedPref(context, keysetAlias + "_daead__", keysetPrefName)
                .withMasterKeyUri(KEYSTORE_PATH_URI + masterKeyAlias)
                .build().keysetHandle
            val signKeysetHandle = AndroidKeysetManager.Builder()
                .withKeyTemplate(signKeyTemplate)
                .withSharedPref(context, keysetAlias + "_sign__", keysetPrefName)
                .withMasterKeyUri(KEYSTORE_PATH_URI + masterKeyAlias)
                .build().keysetHandle
            val aead = aeadKeysetHandle.getPrimitive(Aead::class.java)
            val deterministicAead = deterministicAeadKeysetHandle.getPrimitive(DeterministicAead::class.java)
            val signer = signKeysetHandle.getPrimitive(PublicKeySign::class.java)
            val verifier = signKeysetHandle.publicKeysetHandle.getPrimitive(PublicKeyVerify::class.java)
            return CryptoHelper(aead, deterministicAead, signer, verifier)
        
    

    fun encrypt(plainText: ByteArray, associatedData: ByteArray = ByteArray(0)): ByteArray =
        aead.encrypt(plainText, associatedData)

    fun decrypt(ciphertext: ByteArray, associatedData: ByteArray = ByteArray(0)): ByteArray =
        aead.decrypt(ciphertext, associatedData)

    fun encryptDeterministically(plainText: ByteArray, associatedData: ByteArray = ByteArray(0)): ByteArray =
        deterministicAead.encryptDeterministically(plainText, associatedData)

    fun decryptDeterministically(ciphertext: ByteArray, associatedData: ByteArray = ByteArray(0)): ByteArray =
        deterministicAead.decryptDeterministically(ciphertext, associatedData)

    fun sign(data: ByteArray): ByteArray =
        signer.sign(data)

    fun verify(signature: ByteArray, data: ByteArray): Boolean =
        try 
            verifier.verify(signature, data)
            true
         catch (e: GeneralSecurityException) 
            false
        

    companion object 
        private const val KEYSTORE_PATH_URI = "android-keystore://"
        private const val KEYSET_PREF_NAME = "__crypto_helper_pref__"
        private const val KEYSET_ALIAS = "__crypto_helper_keyset"
    


不要忘记添加 com.google.crypto.tink:tink-android 作为实现依赖项,因为 Jetpack Security 不会将其公开为 api。

【讨论】:

以上是关于2021 年在 Android 中加密字符串的最佳和最安全的方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

[译] 最佳安全实践:在 Java 和 Android 中使用 AES 进行对称加密

在Android中使用AES加密的最佳做法是什么?

如何在 Android 中使用自己的密钥进行 AES-256 加密?

如何提供一种在 Android 库中使用自定义加密的方法?

密码加密和通过网络发送的最佳方法[重复]

HTTPS理论基础及其在Android中的最佳实践