ChatGPT解决这个技术问题 Extra ChatGPT

如何在 Android 中安全地存储访问令牌和秘密?

我将使用 oAuth 从 google 获取邮件和联系人。我不想每次都要求用户登录以获取访问令牌和秘密。据我了解,我需要将它们与我的应用程序一起存储在数据库或 SharedPreferences 中。但我有点担心安全方面的问题。我读到您可以加密和解密令牌,但攻击者很容易只需反编译您的 apk 和类并获取加密密钥。
在 Android 中安全存储这些令牌的最佳方法是什么?

我如何存储消费者密钥和秘密(硬编码它们是不安全的)?我需要他们请求访问令牌和秘密.. 其他使用 oauth 的现有应用程序是如何做到的?嗯,终于有了oauth,你需要为我处理更多的安全问题......我需要安全地保存消费者令牌/秘密以及访问令牌和秘密......最后不是更简单吗?只是存储用户的用户名/密码加密?...最后,不是后者更好吗?我还是看不出oauth有多好......
你能告诉我..哪个文件存储访问令牌?我是 android 新手,我尝试运行示例 Plus 应用程序。但我在任何地方都找不到这个 [GoogleAuthUtil.getToken() 方法。]

C
CoolMind

将它们存储为 shared preferences。这些默认情况下是私有的,其他应用程序无法访问它们。在有根设备上,如果用户明确允许访问某些试图读取它们的应用程序,则该应用程序可能能够使用它们,但您无法防止这种情况发生。至于加密,您必须要求用户每次都输入解密密码(从而破坏了缓存凭据的目的),或者将密钥保存到文件中,您会遇到同样的问题。

存储令牌而不是实际的用户名密码有一些好处:

第三方应用程序不需要知道密码,用户可以确保他们只将密码发送到原始站点(Facebook、Twitter、Gmail 等)

即使有人窃取了令牌,他们也看不到密码(用户也可能在其他网站上使用该密码)

令牌通常有生命周期,并在一定时间后过期

如果您怀疑令牌已被泄露,则可以撤销令牌


谢谢你的回复!但是我怎么知道我的消费者密钥是否被泄露?大声笑这将很难说.. 好的关于存储访问令牌和秘密,好的,我将它们保存在 sharedpreferences 中并加密它们,但是消费者密钥和秘密呢?我无法将它们存储在 sharedpreferences 中(我需要在代码中明确编写消费者密钥和秘密,以便首先将其保存在 sharedpreferences 中).. 不知道您是否理解我的意思。
您必须以(有点)混淆的方式将其放入应用程序中,以使它们在反编译后不会立即可见,或者使用您自己的具有密钥和秘密的授权代理 webapp。将它们放入应用程序显然更容易,如果认为有人试图破解您的应用程序的风险足够低,请采用这种方法。顺便说一句,以上几点是针对用户密码的。如果您发现您的消费者密钥/秘密已被泄露,您也可以撤销它们(当然,这会破坏您的应用程序)。
@NikolayElenkov:您写道:“至于加密,您必须要求用户每次都输入解密密码(从而破坏了缓存凭据的目的),或者将密钥保存到文件中,您会遇到同样的问题。” .如果破解者反转您的应用程序以了解加密的工作原理怎么办?你的防御可能会被打破。使用本机代码存储此类信息(令牌、加密......)是最佳实践吗?
这不是现在存储令牌的最佳方式!
@RahulRastogi 最好的方法是什么?
a
aldok

您可以将它们存储在 AccountManager 中。根据这些人的说法,这被认为是最佳实践。

https://i.stack.imgur.com/oT3jR.png

这是官方定义:

此类提供对用户在线帐户的集中注册表的访问。用户为每个帐户输入一次凭据(用户名和密码),通过“一键式”批准授予应用程序访问在线资源的权限。

有关如何使用 AccountManager 的详细指南:

乌迪科恩教程

皮兰尼特博客

Google IO 演示文稿

但是,最终 AccountManager 仅将您的令牌存储为纯文本。所以,我建议在将它们存储在 AccountManager 之前加密你的秘密。您可以使用各种加密库,例如 AESCryptAESCrypto

另一种选择是使用 Conceal library。它对 Facebook 来说足够安全,并且比 AccountManager 更容易使用。这是使用 Conceal 保存秘密文件的代码片段。

byte[] cipherText = crypto.encrypt(plainText);
byte[] plainText = crypto.decrypt(cipherText);

隐藏的好提示。看起来很容易使用。对于许多用例。
无法通过链接找到隐藏。它可能已停用
a
apex39

SharedPreferences is not 本身就是一个安全位置。在有根设备上,我们可以轻松地读取和修改所有应用程序的 SharedPrefereces xml。所以令牌应该相对频繁地过期。但即使令牌每小时过期,更新的令牌仍然可以从 SharedPreferences 中窃取。 Android KeyStore 应该用于长期存储和检索加密密钥,这些密钥将用于加密我们的令牌,以便将它们存储在 SharedPreferences 或数据库中。密钥不存储在应用程序的进程中,因此它们are harder会被泄露。

比一个地方更重要的是它们本身如何安全,例如使用加密签名的短期 JWT,使用 Android KeyStore 加密它们并使用安全协议发送它们


那么我们可以在哪里存储它们呢?
@MilindMevada 使用 android account manager(使用手动加密,因为客户经理只存储纯文本)或 android keystore
Z
Zahidur Rahman Faisal

从 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,当您从外部文件加载它们时,他们也永远不会获得您的访问令牌和秘密。


看起来创建属性文件而不是硬编码没有区别。
我想在运行时写令牌可能是我的令牌每次打开我的应用程序时都会更改。
这是存储一些令牌(如 API 访问令牌)的好方法。如果您想存储用户凭据,NDK 是一种更好的方法。
这绝对不是您在应用程序中存储敏感信息的方式!即使存储库不包含使用这种方法的数据(数据被注入到构建过程中),这也会生成一个 BuildConfig 文件,其中包含纯文本的令牌/秘密,供所有人在简单的反编译后查看。
我同意@Hrafn。这种解决方案可以很容易地进行逆向工程(通过反编译apk等)。另一个问题是在像 OAuth2.0 这样的协议中,访问令牌是临时的。这意味着您需要在当前访问令牌过期时请求新的访问令牌。
k
kfrye

正如此问题的最新更新一样,您现在可以使用 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()