问题描述
当前,我们将字符串加密为:
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模式。
打开获取更多说明。
解决方法
我会拿你的签名:
public String encrypt(String data)
并保持原样,但选择一种方法:
-
数据是否足够小,足以使用安全共享首选项存储数据? (由于Shared Pref的问题,这不是最好的主意。)
-
能否将数据保存在文件中(临时),然后将其返回?
您可以执行任何一种操作,两者之间的差异应该不会太大,因为您可能会使用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”。
我不知道这是否对您有帮助,但是它肯定会抽象出使用它加密代码的复杂性。
免责声明:其中一些是我使用过的代码,有些仅仅是“伪代码”,可以使您了解我的想法。