一、KeyStore描述
在 Android 开发中,KeyStore 是一个用于存储密钥和证书的安全容器。它提供了一种安全的方式来存储敏感信息,如密钥对、数字证书等,以防止它们被未授权的应用或攻击者访问。
KeyStore 通常用于加密数据、数字签名、TLS/SSL 连接等场景。
Android 开发中使用 KeyStore 的常见场景:
-
存储密钥对:可以使用 KeyStore 来生成和存储公钥和私钥的密钥对。这些密钥对通常用于数据加密、数字签名等操作。
-
存储数字证书:可以使用 KeyStore 来存储数字证书,用于验证身份、建立安全连接等场景。
-
安全存储密码:可以使用 KeyStore 来安全地存储密码、凭证、API 密钥等敏感信息,以防止它们被未授权的应用或攻击者访问。
-
TLS/SSL 连接:可以使用 KeyStore 来管理客户端证书和受信任的 CA 证书,用于安全通信、建立 TLS/SSL 连接等操作。
-
双因素身份验证:可以使用 KeyStore 来存储和管理双因素身份验证所需的密钥和证书,用于提高身份验证的安全性。
在 Android 中,KeyStore 是通过 java.security.KeyStore
类来实现的。可以使用该类来创建、加载、存储和检索密钥和证书。Android 提供了特定于 Android 平台的 KeyStore 实现,称为 AndroidKeyStore,它提供了更高级的安全功能,如硬件支持、密钥链随机生成等。
二、KeyStore使用
// 密钥库类型
private const val PP_KEYSTORE_TYPE = "AndroidKeyStore"
// 密钥库别名
private const val PP_KEYSTORE_ALIAS = "pp_keystore_alias"
// 加密算法标准算法名称
private const val PP_TRANSFORMATION = "RSA/ECB/PKCS1Padding"
1. 生成公私钥密钥对
/**
* 触发生成密钥对.
*
* 生成RSA 密钥对,包括公钥和私钥
*
* @return KeyPair 密钥对,包含公钥和私钥
*/
private fun generateKey(): KeyPair {
// 创建密钥生成器
val keyPairGenerator = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_RSA,
PP_KEYSTORE_TYPE
)
// 配置密钥生成器参数
KeyGenParameterSpec.Builder(
PP_KEYSTORE_ALIAS,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
.setDigests(KeyProperties.DIGEST_SHA256)
.build().run {
keyPairGenerator.initialize(this)
}
// 生成密钥对
return keyPairGenerator.generateKeyPair()
}
通过上述代码使用“AndroidKeyStore”类型的密钥库,生成 RSA 密钥对,包括公钥和私钥。
后续针对数据的加密和解密就需要使用此时密钥库中生成的 公钥和私钥。
2. AndroidKeyStore 密钥库得到密钥对
2.1 公钥
/**
* 获取公钥.
*
* @return 公钥
*/
private fun getPublicKey(): PublicKey? {
val keyStore = KeyStore.getInstance(PP_KEYSTORE_TYPE).apply {
load(null)
}
// 判断密钥是否存在
if (!keyStore.containsAlias(PP_KEYSTORE_ALIAS)) {
return generateKey().public
}
val entry = keyStore.getEntry(PP_KEYSTORE_ALIAS, null)
if (entry !is KeyStore.PrivateKeyEntry) {
return null
}
return entry.certificate.publicKey
}
2.2 私钥
/**
* 获取私钥.
*
* @return 密钥
*/
private fun getPrivateKey(): PrivateKey? {
val keyStore = KeyStore.getInstance(PP_KEYSTORE_TYPE).apply {
load(null)
}
// 判断密钥是否存在
if (!keyStore.containsAlias(PP_KEYSTORE_ALIAS)) {
return generateKey().private
}
val entry = keyStore.getEntry(PP_KEYSTORE_ALIAS, null)
if (entry !is KeyStore.PrivateKeyEntry) {
return null
}
return entry.privateKey
}
3. 加密、解密
3.1 加密
/**
* 数据加密.
*
* @param data 原始数据,字符串
* @return 加密数据,字节数组
*/
fun encryptData(data: String): ByteArray {
return encryptDataInternal(data.toByteArray())
}
/**
* 数据加密.
*
* @param bytes 原始数据
* @return 加密数据
*/
private fun encryptDataInternal(bytes: ByteArray): ByteArray {
return getPublicKey()?.run {
val cipher = Cipher.getInstance(PP_TRANSFORMATION)
cipher.init(Cipher.ENCRYPT_MODE, this)
cipher.doFinal(bytes)
} ?: byteArrayOf()
}
3.2 解密
/**
* 数据解密.
*
* @param bytes 加密数据
* @return 原始数据,字符串
*/
fun decryptData(bytes: ByteArray): String {
return String(decryptDataInternal(bytes))
}
/**
* 数据解密.
*
* @param bytes 加密数据
* @return 原始数据
*/
private fun decryptDataInternal(bytes: ByteArray): ByteArray {
return getPrivateKey()?.run {
val cipher = Cipher.getInstance(PP_TRANSFORMATION)
cipher.init(Cipher.DECRYPT_MODE, this)
cipher.doFinal(bytes)
} ?: byteArrayOf()
}
描述下Cipher对象参数:
Cipher.getInstance(String transformation)
是用于获取 Cipher 对象的静态方法。它接受一个字符串参数 transformation
,该参数指定了要使用的加密算法、模式和填充方式
3.3 加解密对象Clpher参数transformation介绍
transformation
参数的格式通常为 "algorithm/mode/padding"
,其中:
- algorithm:指定加密算法的名称,如 AES、DES、RSA 等。
- mode:指定加密模式,如 ECB、CBC、CTR 等。
- padding:指定填充方式,如 PKCS5Padding、NoPadding 等。
例如我们当前工具类,要使用 RSA 算法、ECB 模式和 PKCS5Padding 填充方式进行加密,你可以使用如下的 transformation
参数:
private const val PP_TRANSFORMATION = "RSA/ECB/PKCS1Padding"
然后调用 Cipher.getInstance(transformation)
方法来获取对应的 Cipher 对象,用于执行加密和解密操作。
在 Android 中,常见的加密算法和模式包括:
- 加密算法:AES、DES、RSA 等。
- 加密模式:ECB、CBC、CTR、GCM 等。
- 填充方式:PKCS5Padding、NoPadding 等。
3.3.1 Android支持的transformation类型,参考Cipher文档:
3.3.2 注意:padding使用
OAEP填充方式
private const val PP_TRANSFORMATION = "RSA/ECB/OAEPwithSHA-512andMGF1Padding"
如果填充模式为OAEP,需要修改的代码如下
1. 密钥对生成需要修改代码
// padding: 主要是生成KeySotre密钥对,填充模式:ENCRYPTION_PADDING_RSA_OAEP
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
// mode: ECB
.setBlockModes(KeyProperties.BLOCK_MODE_ECB)
// 消息摘要只支持512和256,OAEPwithSHA-512andMGF1Padding
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
2. 加密需要修改代码
/**
* 数据加密.
*
* @param bytes 原始数据
* @return 加密数据
*/
private fun encryptDataInternal(bytes: ByteArray): ByteArray {
return getPublicKey()?.run {
val cipher = Cipher.getInstance(TRANSLATOR_TRANSFORMATION)
cipher.init(
Cipher.ENCRYPT_MODE, this,
OAEPParameterSpec(
"SHA-512",
"MGF1",
MGF1ParameterSpec.SHA1,
PSource.PSpecified.DEFAULT
)
)
cipher.doFinal(bytes)
} ?: byteArrayOf()
}
注意:
Android平台在使用RSA/ECB/OAEPWithSHA-256AndMGF1Padding加密模式时,对MGF1摘要算法的支持存在一些限制。
- Android密钥库(AndroidKeyStore)在实现OAEP加密时,只支持使用SHA-1作为MGF1摘要算法,不支持SHA-256或更高版本的SHA算法作为MGF1。
- 尽管Android开发者文档中给出了使用OAEPParameterSpec设置不同摘要算法的示例代码加密介绍官网,但实际上如果将MGF1摘要设置为SHA-256或更高,会抛出
java.security.InvalidAlgorithmParameterException
异常。 - 这一限制存在于Android框架的实现中,目的是为了与其他系统保持加密算法的互操作。
- 因此,在Android平台上使用RSA/ECB/OAEPWithSHA-256AndMGF1Padding加密时,开发者需要将MGF1摘要算法显式设置为SHA-1,而不能使用SHA-256或更高版本,否则会导致异常。
提示:正常我们需要对加密的数据进行本地存储,上述加密数据是ByteArray,字节数组不太适合本地存储,因此我们可以通过Base64将ByteArray数据转换为字符串进行保存,取出数据之时再做Base64解码。
// ByteArray转Base64字符串
Base64.encodeToString(encryptedBytes, Base64.DEFAULT)
// Base64字符串转ByteArray
Base64.decode(encryptedString, Base64.DEFAULT)
到此为止,基本的使用和简单的参数描述已经完成。
4. 完整代码
object KeyStoreHelper {
// 密钥库类型
private const val PP_KEYSTORE_TYPE = "AndroidKeyStore"
// 密钥库别名
private const val PP_KEYSTORE_ALIAS = "pp_keystore_alias"
// 加密算法标准算法名称
private const val PP_TRANSFORMATION = "RSA/ECB/PKCS1Padding"
/**
* 数据加密.
*
* @param data 原始数据,字符串
* @return 加密数据,字节数组
*/
fun encryptData(data: String): ByteArray {
return encryptDataInternal(data.toByteArray())
}
/**
* 数据解密.
*
* @param bytes 加密数据
* @return 原始数据,字符串
*/
fun decryptData(bytes: ByteArray): String {
return String(decryptDataInternal(bytes))
}
/**
* 数据加密.
*
* @param bytes 原始数据
* @return 加密数据
*/
private fun encryptDataInternal(bytes: ByteArray): ByteArray {
return getPublicKey()?.run {
val cipher = Cipher.getInstance(PP_TRANSFORMATION)
cipher.init(Cipher.ENCRYPT_MODE, this)
cipher.doFinal(bytes)
} ?: byteArrayOf()
}
/**
* 数据解密.
*
* @param bytes 加密数据
* @return 原始数据
*/
private fun decryptDataInternal(bytes: ByteArray): ByteArray {
return getPrivateKey()?.run {
val cipher = Cipher.getInstance(PP_TRANSFORMATION)
cipher.init(Cipher.DECRYPT_MODE, this)
cipher.doFinal(bytes)
} ?: byteArrayOf()
}
/**
* 获取公钥.
*
* @return 公钥
*/
private fun getPublicKey(): PublicKey? {
val keyStore = KeyStore.getInstance(PP_KEYSTORE_TYPE).apply {
load(null)
}
// 判断密钥是否存在
if (!keyStore.containsAlias(PP_KEYSTORE_ALIAS)) {
return generateKey().public
}
val entry = keyStore.getEntry(PP_KEYSTORE_ALIAS, null)
if (entry !is KeyStore.PrivateKeyEntry) {
return null
}
return entry.certificate.publicKey
}
/**
* 获取私钥.
*
* @return 密钥
*/
private fun getPrivateKey(): PrivateKey? {
val keyStore = KeyStore.getInstance(PP_KEYSTORE_TYPE).apply {
load(null)
}
// 判断密钥是否存在
if (!keyStore.containsAlias(PP_KEYSTORE_ALIAS)) {
return generateKey().private
}
val entry = keyStore.getEntry(PP_KEYSTORE_ALIAS, null)
if (entry !is KeyStore.PrivateKeyEntry) {
return null
}
return entry.privateKey
}
/**
* 触发生成密钥对.
*
* 生成RSA 密钥对,包括公钥和私钥
*
* @return KeyPair 密钥对,包含公钥和私钥
*/
private fun generateKey(): KeyPair {
// 创建密钥生成器
val keyPairGenerator = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_RSA,
PP_KEYSTORE_TYPE
)
// 配置密钥生成器参数
KeyGenParameterSpec.Builder(
PP_KEYSTORE_ALIAS,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
.setDigests(KeyProperties.DIGEST_SHA256)
.build().run {
keyPairGenerator.initialize(this)
}
// 生成密钥对
return keyPairGenerator.generateKeyPair()
}
}
5. 新增AES加密(大数据)
前面我们举例的都是使用RSA加解密数据,正常的数据没什么问题,但是碰到大数据就无法加密,因此我们使用AES对称加密。
// 密钥库类型
private const val PP_KEYSTORE_TYPE = "AndroidKeyStore"
// 密钥库别名(AES,用于大数据加密)
private const val PP_KEYSTORE_AES_ALIAS = "pp_keystore_aes_alias"
// 加密算法标准算法名称; transformation(算法/模式/填充方式): 参数的格式通常为 "algorithm/mode/padding"(AES)
private const val PP_AES_TRANSFORMATION = "AES/GCM/NoPadding"
// AES加密中使用2个字节长度的数组存储IV的长度
private const val PP_AES_IV_BYTES_LEN = 2
5.1 秘钥
/**
* 从KeyStore获取AES密钥.
*/
private fun getAesSecretKey(): SecretKey? {
val keyStore = KeyStore.getInstance(PP_KEYSTORE_TYPE).apply {
load(null)
}
// 判断密钥是否存在
if (!keyStore.containsAlias(PP_KEYSTORE_AES_ALIAS)) {
return generateAesSecretKey()
}
val entry = keyStore.getEntry(PP_KEYSTORE_AES_ALIAS, null)
if (entry !is KeyStore.SecretKeyEntry) {
return null
}
return entry.secretKey
}
/**
* 生成对称加密密钥.
*
* @return 对称加密密钥
*/
private fun generateAesSecretKey(): SecretKey {
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, PP_KEYSTORE_TYPE)
val keyGenParameterSpec = KeyGenParameterSpec.Builder(
PP_KEYSTORE_AES_ALIAS,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build()
keyGenerator.init(keyGenParameterSpec)
return keyGenerator.generateKey()
}
5.2 加密
/**
* AES数据加密(数据有大小限制).
*
* @param bytes 原始数据,字节数组
* @return 加密数据,字节数组
*/
fun aesEncryptData(bytes: ByteArray): ByteArray {
return try {
if (bytes.isEmpty()) {
return byteArrayOf()
}
getAesSecretKey()?.let { secretKey ->
val cipher = Cipher.getInstance(PP_AES_TRANSFORMATION)
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
val iv = cipher.parameters.getParameterSpec(GCMParameterSpec::class.java).iv
val ivLenBytes = iv.size.toByteArray()
// 拼接加密数据:iv长度(2字节) + iv + 真实加密数据
ivLenBytes.plus(iv).plus(cipher.doFinal(bytes))
} ?: byteArrayOf()
} catch (e: Exception) {
"aesEncryptData error=${e.stackTraceToString()}".logI(TAG)
byteArrayOf()
}
}
5.3 解密
/**
* AES数据解密(数据有大小限制).
*
* @param bytes 加密数据
* @return 原始数据,字节数组
*/
fun aesDecryptData(bytes: ByteArray): ByteArray {
return try {
if (bytes.isEmpty() || bytes.size <= PP_AES_IV_BYTES_LEN) {
return byteArrayOf()
}
// 加密数据:iv长度(2字节) + iv + 真实加密数据
val ivLen = bytes.sliceArray(0 until PP_AES_IV_BYTES_LEN).toInt()
val inLenEndIndex = PP_AES_IV_BYTES_LEN + ivLen
val iv = bytes.sliceArray(PP_AES_IV_BYTES_LEN until inLenEndIndex)
val decryptBytes = bytes.sliceArray(inLenEndIndex until bytes.size)
return getAesSecretKey()?.let { secretKey ->
val cipher = Cipher.getInstance(PP_AES_TRANSFORMATION)
cipher.init(Cipher.DECRYPT_MODE, secretKey, GCMParameterSpec(128, iv))
cipher.doFinal(decryptBytes)
} ?: byteArrayOf()
} catch (e: Exception) {
"aesDecryptData error=${e.stackTraceToString()}".logI(TAG)
byteArrayOf()
}
}