我将使用 oAuth 从 google 获取邮件和联系人。我不想每次都要求用户登录以获取访问令牌和秘密。据我了解,我需要将它们与我的应用程序一起存储在数据库或 SharedPreferences
中。但我有点担心安全方面的问题。我读到您可以加密和解密令牌,但攻击者很容易只需反编译您的 apk 和类并获取加密密钥。
在 Android 中安全存储这些令牌的最佳方法是什么?强>
将它们存储为 shared preferences。这些默认情况下是私有的,其他应用程序无法访问它们。在有根设备上,如果用户明确允许访问某些试图读取它们的应用程序,则该应用程序可能能够使用它们,但您无法防止这种情况发生。至于加密,您必须要求用户每次都输入解密密码(从而破坏了缓存凭据的目的),或者将密钥保存到文件中,您会遇到同样的问题。
存储令牌而不是实际的用户名密码有一些好处:
第三方应用程序不需要知道密码,用户可以确保他们只将密码发送到原始站点(Facebook、Twitter、Gmail 等)
即使有人窃取了令牌,他们也看不到密码(用户也可能在其他网站上使用该密码)
令牌通常有生命周期,并在一定时间后过期
如果您怀疑令牌已被泄露,则可以撤销令牌
您可以将它们存储在 AccountManager 中。根据这些人的说法,这被认为是最佳实践。
https://i.stack.imgur.com/oT3jR.png
这是官方定义:
此类提供对用户在线帐户的集中注册表的访问。用户为每个帐户输入一次凭据(用户名和密码),通过“一键式”批准授予应用程序访问在线资源的权限。
有关如何使用 AccountManager 的详细指南:
乌迪科恩教程
皮兰尼特博客
Google IO 演示文稿
但是,最终 AccountManager 仅将您的令牌存储为纯文本。所以,我建议在将它们存储在 AccountManager 之前加密你的秘密。您可以使用各种加密库,例如 AESCrypt 或 AESCrypto
另一种选择是使用 Conceal library。它对 Facebook 来说足够安全,并且比 AccountManager 更容易使用。这是使用 Conceal 保存秘密文件的代码片段。
byte[] cipherText = crypto.encrypt(plainText);
byte[] plainText = crypto.decrypt(cipherText);
SharedPreferences is not 本身就是一个安全位置。在有根设备上,我们可以轻松地读取和修改所有应用程序的 SharedPrefereces xml。所以令牌应该相对频繁地过期。但即使令牌每小时过期,更新的令牌仍然可以从 SharedPreferences 中窃取。 Android KeyStore 应该用于长期存储和检索加密密钥,这些密钥将用于加密我们的令牌,以便将它们存储在 SharedPreferences 或数据库中。密钥不存储在应用程序的进程中,因此它们are harder会被泄露。
比一个地方更重要的是它们本身如何安全,例如使用加密签名的短期 JWT,使用 Android KeyStore 加密它们并使用安全协议发送它们
从 Android Studio 的项目窗格中,选择“项目文件”并在项目的根目录中创建一个名为“keystore.properties”的新文件。
https://i.stack.imgur.com/69UJB.png
打开“keystore.properties”文件并将您的访问令牌和秘密保存在文件中。
https://i.stack.imgur.com/y9wld.png
现在在您的应用程序模块的 build.gradle 文件中加载读取访问令牌和秘密。然后,您需要为您的访问令牌和秘密定义 BuildConfig 变量,以便您可以直接从您的代码中访问它们。您的 build.gradle 可能如下所示: ... ... ... android { compileSdkVersion 26 // 从 keystore.properties 文件中加载值 def keystorePropertiesFile = rootProject.file("keystore.properties") def keystoreProperties = new Properties( ) keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) defaultConfig { applicationId "com.yourdomain.appname" minSdkVersion 16 targetSdkVersion 26 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" // 创建 BuildConfig 变量 buildConfigField " String", "ACCESS_TOKEN", keystoreProperties["ACCESS_TOKEN"] buildConfigField "String", "SECRET", keystoreProperties["SECRET"] } } 您可以在代码中使用您的访问令牌和秘密,如下所示: String accessToken = BuildConfig.ACCESS_TOKEN ;字符串秘密 = BuildConfig.SECRET;
这样您就不需要在项目中以纯文本形式存储访问令牌和秘密。因此,即使有人反编译了您的 APK,当您从外部文件加载它们时,他们也永远不会获得您的访问令牌和秘密。
正如此问题的最新更新一样,您现在可以使用 EncryptedSharedPreferences 安全地存储数据。界面非常相似,只是您还需要生成一个 MasterKey。
EncryptedSharedPreferences 的大多数文档都使用 MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC,但似乎不赞成使用 MasterKey.Builder。
private var masterKeyAlias = MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
private val preferences = EncryptedSharedPreferences.create(
context,
"auth_token_secured",
masterKeyAlias,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
var authToken: String?
get() = preferences.getString("auth_token", "")
set(value) = preferences.edit().putString("auth_token", value).apply()