在Android中使用Jetpack Security进行字符串加密

问题描述

当前,我们将字符串加密为:

import android.util.Base64;
import java.security.Key;
import java.util.Arrays;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class Cipher {

    private static final String TEXT_ENCODING_TYPE = "UTF-8";
    private static final String ALGO = "AES";
    private static final String TYPE = ALGO + "/CBC/PKCS5Padding";
    private static final String KEY = "MY_STATIC_KEY";
    private static final String IV = "MY_STATIC_VECTOR";
    private static final String IV_PADDING = "                ";

    public static String encrypt(String data) {
        try {
            if (!data.isEmpty()) {
                javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(TYPE);
                cipher.init(javax.crypto.Cipher.ENCRYPT_MODE,getKey(),getIV());
                return Base64.encodeToString(cipher.doFinal((IV_PADDING + data).getBytes()),Base64.NO_WRAP).trim();
            } else {
                return data;
            }
        } catch (Exception e) {
            return data;
        }
    }
            return new String(cipher.doFinal(data)).trim();
        } else {
            return encryptedData;
        }
    } catch (Exception e) {
        LogUtils.log(e,Cipher.class);
        return encryptedData;
    }
}

private static Key getKey() throws Exception {
    return new SecretKeySpec(KEY.getBytes(TEXT_ENCODING_TYPE),ALGO);
}

private static IvParameterSpec getIV() throws Exception {
    return new IvParameterSpec(IV.getBytes(TEXT_ENCODING_TYPE));
}

private static IvParameterSpec getIV(byte[] iv) {
    return new IvParameterSpec(iv);
}
}

但是我们已经收到来自Google Play控制台的安全警报:

您的应用包含不安全的加密加密模式。

enter image description here

然后我们被重定向到此链接:Remediation for Unsafe Cryptographic Encryption。 但是,此链接建议使用Jetpack Security软件包,在该软件包中,我找不到如何加密字符串以及如何为每个服务器请求生成安全的 KEY和IV

我访问过的所有示例和链接都将您的敏感数据保存到加密的文件和SharedPreferences中。

那么,我现在该怎么办?我是否必须找到可以在服务器端(Java)上解码的安全密钥生成机制,并将该密钥保存在Secured SharedPreferences中? Jetpack Security程序包仍处于Beta模式。

打开获取更多说明。

解决方法

我会拿你的签名:

public String encrypt(String data)并保持原样,但选择一种方法:

  1. 数据是否足够小,足以使用安全共享首选项存储数据? (由于Shared Pref的问题,这不是最好的主意。)

  2. 能否将数据保存在文件中(临时),然后将其返回?

您可以执行任何一种操作,两者之间的差异应该不会太大,因为您可能会使用class YourCryptoImplementation的某种形式来执行所有这些操作...

使用共享首选项

您可以有两种方法(抱歉,在Kotlin中,因为它更短,我已经使用过类似的代码):

private fun getEncryptedPreferences() = 
EncryptedSharedPreferences.create("your_shared_preferences",advancedKeyAlias,context,EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM)

您会想知道advancedKeyAlias是什么。那只是private var advancedKeyAlias: String,但实际值...将类似于:

    init {
        val advancedSpec = KeyGenParameterSpec.Builder("your_master_key_name",KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT).apply {
            setBlockModes(KeyProperties.BLOCK_MODE_GCM)
            setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
            setKeySize(256)
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                val hasStrongBox = context.packageManager.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE)
                if (hasStrongBox)
                    setIsStrongBoxBacked(true)
            }
        }.build()
        advancedKeyAlias = MasterKeys.getOrCreate(advancedSpec)
    }

因此,现在在您的此类的init()中,确保已创建密钥别名。

您可以使用它来加密或解密。

返回我们的SharedPref。例如:

假设您要存储字符串,可以提供:

    fun encryptToSharedPref(String data) {
        getEncryptedPrefs().edit().putString("the_key_you_want_to_use",data).apply()
    }

并“读取”值:

fun getValueFromSharedPreferencesWith(key: String) = getEncryptedPreferences().getString(key,null)

如果字符串适合SharedPref并且您不关心其他“共享首选项”问题,那将行得通...

那文件呢?

差别不大,但是假设您属于同一班级(即advancedKeyAlias存在)。

您将有一个getEncryptedFile辅助方法:

    private fun getEncryptedFile(file: File) = EncryptedFile.Builder(file,EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB).build()

您可以解密以下文件:

  fun decryptFile(file: File): FileInputStream {
        return getEncryptedFile(file).openFileInput()
  }

非常简单,您显然可以像这样使用它

val rawData = yourCryptoClassAbove.decryptFile(File("path/to/file").readBytes()

val decryptedString = String(rawData)

现在要加密文件,您可以使用FileOutputStream,它是将字节直接输出到文件的流……在我们的例子中,是加密文件。

例如:

    fun encryptFile(bytes: ByteArray,file: File) {
        var outputStream: FileOutputStream? = null
        try {
            outputStream = getEncryptedFile(file).openFileOutput().apply {
                write(bytes)
            }
        } catch (exception: IOException) {
            Log.e(TAG,"output file already exists,please use a new file",exception)
        } finally {
            outputStream?.close()
        }
    }

虽然您会收到一个ByteArray,但是如果您有一个字符串,这并不难...

var dataToEncrypt = ... //any "String"
yourCryptoClassAbove.encryptFile(File("path/to/file",dataToEncrypt.toByteArray())

这基本上就是您可能需要的大部分。显然,您可以使用任何方法来生成“ advancedKey”。

我不知道这是否对您有帮助,但是它肯定会抽象出使用它加密代码的复杂性。

免责声明:其中一些是我使用过的代码,有些仅仅是“伪代码”,可以使您了解我的想法。

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...