Android密码存储实践

Android密码存储实践,第1张

概述1、概述和思路android应用跑在沙盒里,通常数据没有泄露风险。但是高手还是有办法获取应用生成的缓存、数据库等。如果将密码直接存储在数据库里面并不安全。即便是本地无关紧要的小应用最好也对存储的密码进行加密。加密有很多方法,对称和非对称加密算法相比很多人都知道。AE

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.naviimport androID.security.keystore.KeyGenParameterSpecimport androID.security.keystore.KeyPropertIEsimport androID.util.Base64import java.security.KeyPairGeneratorimport java.security.KeyStoreimport javax.crypto.Cipherclass PswSecurity() {    private val cipherEncrypt = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256andMGF1padding")    private val cipherDecrypt = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256andMGF1padding")    private val charset = Charsets.UTF_8    private val pswKeyAlias = "user_psw_key"    private val provIDer = "AndroIDKeyStore"    private val keyStore = KeyStore.getInstance(provIDer).apply{                                    try {                                        load(null)                                    }catch (e:Throwable){                                        e.printstacktrace()                                    }                                }    suspend fun createKeyPair(){        /*try to find keystore*/        try{            if(keyStore == null || keyStore.containsAlias(pswKeyAlias)){                val privateKeyEntry = keyStore.getEntry(pswKeyAlias, null) as KeyStore.PrivateKeyEntry                val publicKey = privateKeyEntry.certificate.publicKey                val privateKey = privateKeyEntry.privateKey                cipherEncrypt.init(Cipher.ENCRYPT_MODE, publicKey)                cipherDecrypt.init(Cipher.DECRYPT_MODE, privateKey)            }else{                val keyPairGenerator : KeyPairGenerator = KeyPairGenerator.getInstance(                        KeyPropertIEs.KEY_ALGORITHM_RSA,                        provIDer)                /*create*/                val keyDecrypt = KeyGenParameterSpec.Builder(                        pswKeyAlias,                        KeyPropertIEs.PURPOSE_DECRYPT)                        .setDigests(KeyPropertIEs.DIGEST_SHA256, KeyPropertIEs.DIGEST_SHA512)                        .setEncryptionpaddings(KeyPropertIEs.ENCRYPTION_padding_RSA_OAEP)                        .build()                keyPairGenerator.initialize(keyDecrypt)                val keyPair = keyPairGenerator.generateKeyPair()                cipherEncrypt.init(Cipher.ENCRYPT_MODE, keyPair.public)                cipherDecrypt.init(Cipher.DECRYPT_MODE, keyPair.private)            }        }catch (e: Throwable){            e.printstacktrace()            // throw e        }    }    suspend fun encrypt(msg: String): String {        return try{            val iv = cipherEncrypt.iv            val secret = cipherEncrypt.doFinal(msg.toByteArray(charset))            Base64.encodetoString(secret, Base64.DEFAulT)        }catch (e: Throwable){            throw Throwable("can not encrypt", e)        }    }    suspend fun decrypt(secret: String): String {        return try {            val msg = cipherDecrypt.doFinal(Base64.decode(secret, Base64.DEFAulT))            msg.toString(charset)            //String(msg)        }catch (e: Throwable){            throw Throwable("Can not decrypt", e)        }    }}

3、讲解
上面在引用方法的时候需要先创建密钥对createKeyPair,创建ok之后才可以使用encrypt和decrypt实现加解密。
createKeyPair首先先检查是否存在pswKeyAlias ,如果存在表明这对密钥已经创建。用的时候直接使用就可以。否则就重新创建一对。KeyStore里面的这个Alias不知道能不能从其它应用里面读取。如果能够读取就非常不好。但是可以自己做一个加密的keyStore。这里暂时没有这么做。然后利用getEntry获取存储的入口。需要将其转换成KeyStore.PrivateKeyEntry。这样才可以从里面获取PublicKey和PrivateKey。(这一步刚开始没找到怎么搞,花了很多时间)
创建密钥官方有指导文档。就是用keyPairGenerator和KeyGenParameterSpec.Builder合作完成参数的设置。然后生成一对密钥。生成的密钥自动存到了keyStore里面。(之前一直没找到存到keyStore的方法。后来才理解。但是用户可以修改keyStore里面的值。)
加解密比较常规,使用Cipher。先getInstance设置算法。然后用Init初始化加密还是解密模式。用的时候用doFinal来实现。但是特别需要注意转码的过程。否则调试半天不知道哪里出错了。

4、参考文档或文章:
官方安全指南
官方keystore
Dson2020的Android KeyStore总结
github上一个哥们的gist

总结

以上是内存溢出为你收集整理的Android密码存储实践全部内容,希望文章能够帮你解决Android密码存储实践所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1035917.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-24
下一篇 2022-05-24

发表评论

登录后才能评论

评论列表(0条)

保存