1

目前我们将我们的字符串加密为:

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 管理中心的安全警报:

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

在此处输入图像描述

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

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

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

打开以获得更多说明。

4

1 回答 1

1

我要你的签名:

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

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

  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 并且您不关心其他 Shared Preferences 问题,那将起作用...

文件呢?

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

您将有一个getEncryptedFile辅助方法:

    private fun getEncryptedFile(file: File) = EncryptedFile.Builder(file, context, advancedKeyAlias,
            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())

这基本上是您可能需要的大部分内容。显然,您可以使用任何方法来生成“高级密钥”。

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

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

于 2020-09-18T11:04:55.807 回答