Every Android developer sooner or later faced with situation when he needs to keep save sensitive information inside an app. You may think right now :
每个Android开发人员迟早都需要将敏感信息保存在应用程序内部的情况。 您可能现在想:
— uhh!! Seems we are going to code a lot!
恩! 似乎我们要编写很多代码!
and you will be almost right :) as in comparison with our iOS colleagues that simply use the KeyChain service, which is a part of Apple’s overall security framework for iOS. Ah, as always!
与您只使用KeyChain服务的 iOS同事相比,您几乎是正确的:),该服务是Apple iOS总体安全框架的一部分。 啊,一如既往!
And what should we do with data on Android? Yes, store it in SharedPreferences, but after it’s been encrypted.
我们应该如何处理Android上的数据? 是的,将其存储在SharedPreferences中,但是在加密之后。
Lets take a look to javax.crypto package and find out what we need to encrypt information:
让我们看一下javax.crypto包,找出我们需要对信息进行加密的内容:
Generate SecretKey
生成密钥
Retrieve it from KeyStore
从KeyStore检索它
Obtain instance of Cipher
获取密码实例
Initialize Cipher with corresponds encryption mode and SecretKey
初始化密码 与相应的加密模式和SecretKey
- Encrypt or decrypt data 加密或解密数据
- Fight with wish to hard code something during cipher initialization. 希望在密码初始化期间对某些内容进行硬编码。
Let’s start coding!
让我们开始编码!
Generate SecretKey
生成密钥
To generate SecretKey you will need to generate some alias that will bound to secret key in KeyStore. Using this alias you will be able to retrieve it from KeyStore. Usually this alias bound to user, it is very helpful if you application support multi accounts login.
要生成SecretKey,您将需要生成一些绑定到KeyStore中的密钥的别名 。 使用此别名,您将能够从KeyStore检索它。 通常,此别名绑定到用户,如果您的应用程序支持多帐户登录,这将非常有用。
const val KEYSTORE_PROVIDER_ANDROID = "AndroidKeyStore"private fun generateSecretKey(alias: String): SecretKey {
try {
val builder =
KeyGenParameterSpec.Builder(alias,
KeyProperties.PURPOSE_ENCRYPT or
KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
val keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
KEYSTORE_PROVIDER_ANDROID)
keyGenerator?.init(builder.build())
return keyGenerator?.generateKey()
} catch (e: Exception) {
Timber.d("Failed to create key")
throw e
}
}
2. Retrieve it from KeyStore
2.从KeyStore检索它
Retrieve SecretKey from KeyStore using alias from the previous step.
使用上一步中的别名从KeyStore中检索SecretKey 。
val keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER_ANDROID)
keyStore.load(null)private fun retrieveSecretKey(): SecretKey? {
val entry = keyStore.getEntry(privateKeyId, null)
val secretKey: SecretKey? = (entry as?
(KeyStore.SecretKeyEntry))?.secretKey return secretKey
}
3. Obtain instance of Cipher
3.获取密码实例
val AES_TRANSFORMATION = "AES/GCM/NoPadding"
val cipher = Cipher.getInstance(AES_TRANSFORMATION)
4. Initialize Cipher it with correspond encryption mode and SecretKey
4.用相应的加密模式和SecretKey初始化Cipher。
To initialize Cipher we will need Initialization Vector (IV). Initialization Vector is is a fixed-size input to a cryptographic primitive. It is typically required to be random or pseudorandom. The point of an IV is to tolerate the use of the same key to encrypt several distinct messages.
要初始化密码,我们将需要 初始化向量 (IV)。 初始化向量是加密原语的固定大小输入。 通常要求它是随机或伪随机的。 IV的重点是允许使用相同的密钥来加密几个不同的消息。
To put it simply, it’s a cryptographic feature that injects randomness to make it more secure.
简而言之,它是一种加密功能,可注入随机性以使其更加安全。
val secureRandom = SecureRandom()
val initVector = ByteArray(128)
secureRandom.nextBytes(initVector)
The important part is that the IV you use in the encryption must be the same one you use in the decryption. So it should be stored like this.
重要的是,加密中使用的IV必须与解密中使用的IV相同。 所以应该这样存储。
val encodedInitVector =
Base64.encodeToString(initVector, Base64.DEFAULT)sharedPreferences.edit()
.putString(ENCRYPTION_IV_KEY, encodedInitVector)
.apply()
Now we are ready to init Cipher for encryption
现在我们准备初始化密码进行加密
val gcmSpec = GCMParameterSpec(128, initVector)
cipher?.init(Cipher.ENCRYPT_MODE, getSecretKey(), gcmSpec)
And here we create Cipher for decryption
在这里,我们创建用于解密的 密码
private fun getInitVectorKey(): ByteArray? {
val encodedIv = preferenceManager
.getString(ENCRYPTION_IV_KEY, null)
return Base64.decode(encodedIv, Base64.DEFAULT)
}val cipher = Cipher.getInstance(AES_TRANSFORMATION)
val spec = GCMParameterSpec(AUTH_TAG_LENGTH, getInitVectorKey())
cipher?.init(Cipher.DECRYPT_MODE, retrieveSecretKey(), spec)
5. Encrypt or decrypt data
5.加密或解密数据
After Cipher has been created and initialized, we are able to encrypt or decrypt data
创建和初始化密码后,我们便可以加密或解密数据
@Throws(Exception::class)
fun encrypt(message: ByteArray): ByteArray? {
return encryptCipher.doFinal(message)
}@Throws(Exception::class)
fun decrypt(encryptionKey: ByteArray?): ByteArray? {
return decryptCipher.doFinal(encryptionKey)
}
6. Fight with wish to hard code something during cipher initialization.
6.希望在密码初始化期间对某些内容进行硬编码。
During setting Cipher you might be tempted to hard code Initialization Vector or store it from initialized Cipher with method that does not need GCMParameterSpec with IV
在设置Cipher的过程中,您可能会尝试对初始化向量进行硬编码或使用不需要IV的 GCMParameterSpec的方法从初始化的Cipher中存储它
val cipher = Cipher.getInstance(AES_TRANSFORMATION)
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey())
val initVector = cipher.iv
storeInitVector(initVector)
不要这样做 。
After data is encrypted it is ready to be stored in SharedPreferences as follows
数据加密后,可以按如下所示存储在SharedPreferences中
val encryptedBytes = aesCipher.encrypt(message.toByteArray())
val encodedMsg = Base64.encodeToString(
encryptedBytes, Base64.DEFAULT)
sharedPreferences.edit()
.putString(YOUR_DATA_KEY, encodedMsg)
.apply()
Or retrieved from SharedPreferences
或从SharedPreferences中检索
val encodedMsg = sharedPreferences.getString(YOUR_DATA_KEY, null)
val encryptedMsg = Base64.decode(encodedMsg, Base64.DEFAULT)
val messageBytes = aesCipher.decrypt(encryptedMsg)
val message = String(messageBytes, Charsets.UTF_8)
Integration
积分
This code sample intended to work on Android 6 and newer, so please check you build.gradle file
该代码示例旨在在Android 6及更高版本上运行,因此请检查您的build.gradle文件
android {
defaultConfig {
minSdkVersion 23
}
}
All this code may obsolete very soon as new Encrypted Shared Preference API from the AndroidX artifacts will be released, so that it will be available right from the box!
随着来自AndroidX工件的新的Encrypted Shared Preference API即将发布,所有这些代码可能很快就会过时,因此可以立即使用!
Enjoy coding!
享受编码!
翻译自: https://medium.com/@jacobvynnyk/simple-encryption-in-android-app-74a85e572b1b