如何在 Android 中安全地存储访问令牌和秘密?
Posted
技术标签:
【中文标题】如何在 Android 中安全地存储访问令牌和秘密?【英文标题】:How to securely store access token and secret in Android? 【发布时间】:2012-04-27 00:34:24 【问题描述】:我将使用 oAuth 从 google 获取邮件和联系人。我不想每次都要求用户登录以获取访问令牌和秘密。据我了解,我需要将它们与我的应用程序一起存储在数据库或SharedPreferences
中。但我有点担心安全方面的问题。我读到您可以加密和解密令牌,但攻击者很容易只需反编译您的 apk 和类并获取加密密钥。在 Android 中安全存储这些令牌的最佳方法是什么?强>
【问题讨论】:
我如何存储消费者密钥和秘密(硬编码它们是不安全的)?我需要他们请求访问令牌和秘密.. 其他使用 oauth 的现有应用程序如何做到这一点?嗯,终于有了oauth,你需要为我处理更多的安全问题......我需要安全地保存消费者令牌/秘密以及访问令牌和秘密......最后不是更简单吗?只是存储用户的用户名/密码加密?...最后,不是后者更好吗?我还是看不出 oauth 有多好...... 你能告诉我..哪个文件存储了访问令牌??我是 android 新手,我尝试运行示例 Plus 应用程序。但我在任何地方都找不到这个 [GoogleAuthUtil.getToken() 方法。] 【参考方案1】:将它们存储为shared preferences。这些默认情况下是私有的,其他应用程序无法访问它们。在有根设备上,如果用户明确允许访问某些试图读取它们的应用程序,则该应用程序可能能够使用它们,但您无法防止这种情况发生。至于加密,您必须要求用户每次都输入解密密码(从而破坏了缓存凭据的目的),或者将密钥保存到文件中,您会遇到同样的问题。
存储令牌而不是实际的用户名密码有几个好处:
第三方应用程序不需要知道密码,用户可以确保他们只将密码发送到原始网站(Facebook、Twitter、Gmail 等) 即使有人窃取了令牌,他们也看不到密码(用户也可能在其他网站上使用该密码) 令牌通常有一个生命周期,并在一定时间后过期 如果您怀疑令牌已被盗用,可以撤销令牌【讨论】:
感谢您的回复!但是我怎么知道我的消费者密钥是否被泄露?大声笑这将很难说.. 好的关于存储访问令牌和秘密,好的,我将它们保存在 sharedpreferences 中并加密它们,但是消费者密钥和秘密呢?我无法将它们存储在 sharedpreferences 中(我需要在代码中明确写入使用者密钥和秘密,以便首先将其保存在 sharedpreferences 中)..不知道你是否理解我的意思。 您必须以(有点)混淆的方式将它们放入应用程序中,因为它们在反编译后不会立即可见,或者使用您自己的具有密钥和秘密的授权代理 webapp。将它们放入应用程序显然更容易,如果认为有人试图破解您的应用程序的风险足够低,请采用这种方法。顺便说一句,以上几点是针对用户密码的。如果您发现您的消费者密钥/秘密已被泄露,您也可以撤销它们(当然,这会破坏您的应用程序)。 @NikolayElenkov:您写道“至于加密,您必须要求用户每次都输入解密密码(从而破坏了缓存凭据的目的),或者将密钥保存到文件中,你会遇到同样的问题。如果破解者反转您的应用程序以了解加密的工作原理怎么办?你的防御可能会被打破。使用本机代码存储此类信息(令牌、加密......)是最佳做法吗? 如果清除了应用数据,那么刷新令牌就会丢失,这可能不是用户想要的。 这不是现在存储令牌的最佳方式!【参考方案2】:您可以将它们存储在AccountManager。根据这些人的说法,这被认为是最佳实践。
这是官方定义:
此类提供对用户的集中注册表的访问 在线帐户。用户输入凭据(用户名和密码) 每个帐户一次,授予应用程序访问在线资源的权限 “一键式”批准。
有关如何使用 AccountManager 的详细指南:
Udi Cohen tutorial Pilanites blog Google IO presentation但是,最终 AccountManager 仅将您的令牌存储为纯文本。所以,我建议在将它们存储在 AccountManager 之前加密你的秘密。您可以使用各种加密库,例如 AESCrypt 或 AESCrypto
另一种选择是使用Conceal library。它对 Facebook 来说足够安全,并且比 AccountManager 更容易使用。这是使用 Conceal 保存秘密文件的代码 sn-p。
byte[] cipherText = crypto.encrypt(plainText);
byte[] plainText = crypto.decrypt(cipherText);
【讨论】:
隐藏的好技巧。看起来很容易使用。对于许多用例。 无法通过链接找到隐藏。它可能被停用【参考方案3】:-
从 Android Studio 的“项目”窗格中,选择“项目文件”并在项目的根目录中创建一个名为“keystore.properties”的新文件。
-
打开“keystore.properties”文件并将您的访问令牌和秘密保存在文件中。
现在在您的 app 模块的 build.gradle 文件中加载读取访问令牌和密钥。然后,您需要为您的 Access Token 和 Secret 定义 BuildConfig 变量,以便您可以直接从您的代码中访问它们。您的 build.gradle 可能如下所示:
... ... ...
android
compileSdkVersion 26
// Load values from keystore.properties file
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"
// Create BuildConfig variables
buildConfigField "String", "ACCESS_TOKEN", keystoreProperties["ACCESS_TOKEN"]
buildConfigField "String", "SECRET", keystoreProperties["SECRET"]
您可以像这样在代码中使用您的访问令牌和秘密:
String accessToken = BuildConfig.ACCESS_TOKEN;
String secret = BuildConfig.SECRET;
这样您就不需要在项目中以纯文本形式存储访问令牌和秘密。因此,即使有人反编译了您的 APK,当您从外部文件加载它们时,他们也永远不会获得您的 Access Token 和 Secret。
【讨论】:
看起来创建属性文件而不是硬编码没有区别。 我想在运行时写令牌可能是每次打开我的应用程序时我的令牌都会更改。 这是一种存储API访问令牌等令牌的好方法。如果您想存储用户凭据,NDK 是更好的方法。 这绝对不是您在应用程序中存储敏感信息的方式!即使存储库不包含使用这种方法的数据(数据被注入到构建过程中),这也会生成一个 BuildConfig 文件,其中包含纯文本的令牌/秘密,供所有人在简单的反编译后查看。 我同意@Hrafn。这种解决方案可以很容易地进行逆向工程(通过反编译apk等)。另一个问题是在像 OAuth2.0 这样的协议中,访问令牌是临时的。这意味着您需要在当前访问令牌过期时请求新的访问令牌。【参考方案4】:SharedPreferences is not 本身就是一个安全位置。在有根设备上,我们可以轻松地读取和修改所有应用程序的 SharedPrefereces xml。所以令牌应该相对频繁地过期。但即使令牌每小时过期,更新的令牌仍然可以从 SharedPreferences 中窃取。 Android KeyStore 应用于长期存储和检索加密密钥,这些密钥将用于加密我们的令牌,以便将它们存储在例如SharedPreferences 或数据库。密钥未存储在应用程序的进程中,因此它们are harder 会被泄露。
比一个地方更重要的是它们如何本身是安全的,例如使用加密签名的短期 JWT,使用 Android KeyStore 对其进行加密并使用安全协议发送它们
【讨论】:
那我们可以把它们存放在哪里? @MilindMevada 使用android account manager(使用手动加密,因为客户经理只存储纯文本)或android keystore以上是关于如何在 Android 中安全地存储访问令牌和秘密?的主要内容,如果未能解决你的问题,请参考以下文章
如何安全地存储 Discord(OAuth2) 用户的访问令牌?