1、概述和思路
android应用跑在沙盒里,通常数据没有泄露风险。但是高手还是有办法获取应用生成的缓存、数据库等。如果将密码直接存储在数据库里面并不安全。即便是本地无关紧要的小应用最好也对存储的密码进行加密。
加密有很多方法,对称和非对称加密算法相比很多人都知道。AES是对称加密的代表,RSA是非对称加密算法的代表。在网络传输时,往往用RSA来加密AES的密钥传递给通讯方。tls通讯可以更深入了解一下。这里不再赘述。因为密码数据较少,使用RSA这种计算复杂度更高的加密算法对整体性能影响不是很大。
为了保证密码的安全,我们将用户名、用户组编号和密码用分隔符组合之后做一个字符串。然后将这个字符串存到byteArray里面进行加密。之后将用户名,用户组和加密后的这个字符串一起存在数据库中。
逻辑上似乎没有问题,但是我们到底怎么存储加密算法的密钥呐?
如果密钥以明文存储成文件或者存在一个非加密的数据库中,那整体上毫无意义。
读了官方的文档,它提供了两种方法(至少,如果有其它的请留言):EncryptedSharedPreferences和KeyStore两种方法。前者用系统的MasterKeys去加密SharedPreference存储的键值对。后者用TrustZone(需要硬件支持)等手段将密钥存在相对安全的区域。
有文章将KeyStore被删除,但具体机理没有搞明白。本来要用EncryptedSharedPreferences,却发现这个方法有几个问题。第一不容易写一个与ui无关的文件。无论MasterKey还是EncryptedSharedPreferences都需要context才可以。整个加密类的使用不太方便。放到viewModel里面不太好搞。另一个是EncryptedSharedPreferences存储密钥智能以String方式存储。需要将PublicKey和PrivateKey编码之后存在String里面用的时候再decode。非常麻烦,搞不好会出错。
多以最终选择了KeyStore的方式。
2、代码
话不多说,先上一段代码。这个是一个与ui无关的类。但是创建密钥和加解密这三个方法用suspend修饰。需要将其放在协程里面运行。更适合viewModel里面引用这个类。
package com.example.loginwithrsa.ui.navi
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import android.util.Base64
import java.security.KeyPairGenerator
import java.security.KeyStore
import javax.crypto.Cipher
class PswSecurity() {
private val cipherEncrypt = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256andMGF1Padding")
private val cipherDecrypt = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256andMGF1Pad