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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10161266/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 02:42:11  来源:igfitidea点击:

How to securely store access token and secret in Android?

androidsecurityoauthpreferencestoken

提问by yeahman

I am going to use oAuth to fetch mails and contacts from google. I don't want to ask the user each time to log in to obtain an access token and secret. From what I understood, I need to store them with my application either in a database or SharedPreferences. But I am a bit worried about security aspects with that. I read that you can encrypt and decrypt the tokens but it is easy for an attacker to just decompile your apk and classes and get the encryption key.
What's the best method to securely store these tokens in Android?

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

采纳答案by Nikolay Elenkov

Store them as shared preferences. Those are by default private, and other apps cannot access them. On a rooted devices, if the user explicitly allows access to some app that is trying to read them, the app might be able to use them, but you cannot protect against that. As for encryption, you have to either require the user to enter the decrypt passphrase every time (thus defeating the purpose of caching credentials), or save the key to a file, and you get the same problem.

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

There are a few benefits of storing tokens instead of the actual username password:

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

  • Third party apps don't need to know the password and the user can be sure that they only send it to the original site (Facebook, Twitter, Gmail, etc.)
  • Even if someone steals a token, the don't get to see the password (which the user might be using on other sites too)
  • Tokens generally have a lifetime and expire after a certain time
  • Tokens can be revoked if you suspect they have been compromised
  • 第三方应用不需要知道密码,用户可以确保他们只将密码发送到原始网站(Facebook、Twitter、Gmail 等)
  • 即使有人窃取了令牌,也看不到密码(用户也可能在其他网站上使用)
  • 令牌通常具有生命周期并在一定时间后到期
  • 如果您怀疑令牌已被盗用,则可以撤销令牌

回答by aldok

You can store them in AccountManager. It's considered best practice according to these guys.

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

enter image description here

在此处输入图片说明

Here's the official definition:

这是官方定义:

This class provides access to a centralized registry of the user's online accounts. The user enters credentials (username and password) once per account, granting applications access to online resources with "one-click" approval.

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

For detailed guide on how to use AccountManager:

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

However, in the end AccountManager only stores your token as a plain text. So, I would suggest encrypting your secret before storing them in AccountManager. You can utilize various Encryption library like AESCryptor AESCrypto

但是,最终 AccountManager 仅将您的令牌存储为纯文本。因此,我建议在将您的机密存储在 AccountManager 之前对其进行加密。您可以使用各种加密库,如AESCryptAESCrypto

Another option is to use Conceal library. It's safe enough for Facebook and much easier to use than AccountManager. Here's a code snippet to save a secret file using Conceal.

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

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

回答by apex39

SharedPreferences is nota secure location itself. On a rooted device we easily can read and modify all applications' SharedPrefereces xml's. So tokens should expire relatively frequent. But even if a token expires every hour, newer tokens can still be stolen from SharedPreferences. Android KeyStore should be used for long term storage and retrieval of cryptographic keys which will be used to encrypt our tokens in order to store them in e.g. SharedPreferences or a database. The keys are not stored within an application's process, so they are harderto be compromised.

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

So more relevant than a place is how they can be itself secure e.g. using cryptographically signed short-living JWTs, encrypting them using Android KeyStore and sending them with a secure protocol

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

回答by Zahidur Rahman Faisal

  1. From your Android Studio's Project pane, select "Project Files" and create a new file named "keystore.properties" in your project's root directory.
  1. 从 Android Studio 的项目窗格中,选择“项目文件”并在项目的根目录中创建一个名为“keystore.properties”的新文件。

enter image description here

在此处输入图片说明

  1. Open "keystore.properties" file and save your Access Token and Secret in the file.
  1. 打开“keystore.properties”文件并将您的访问令牌和秘密保存在文件中。

enter image description here

在此处输入图片说明

  1. Now load the read the Access Token and Secret in your appmodule's build.gradlefile. Then you need to define the BuildConfig variable for your Access Token and Secret so that you can you can directly access them from your code. Your build.gradlemay look like following:

    ... ... ... 
    
    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"]
        }
    }
    
  2. You can use your Access Token and Secret in your code like this:

    String accessToken = BuildConfig.ACCESS_TOKEN;
    String secret = BuildConfig.SECRET;
    
  1. 现在在您的应用程序模块的build.gradle文件中加载读取的访问令牌和秘密。然后您需要为您的访问令牌和秘密定义 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"]
        }
    }
    
  2. 您可以在代码中使用您的访问令牌和秘密,如下所示:

    String accessToken = BuildConfig.ACCESS_TOKEN;
    String secret = BuildConfig.SECRET;
    

This way you don't need store the Access Token and Secret in plain text inside your project. So even if someone decompiles your APK, they will never get your Access Token and Secret as you are loading them from a external file.

这样你就不需要在你的项目中以纯文本形式存储访问令牌和秘密。因此,即使有人反编译了您的 APK,他们也永远不会在您从外部文件加载它们时获得您的访问令牌和机密。

回答by M.Noman

Well you can secure you access token by following two options.

那么您可以通过以下两个选项来保护您的访问令牌。

  1. Use save your access token into android keystore that would not be reverse.
  2. Use NDK function with some calculation that save your token and NDK with c++ code that is very hard to reverse
  1. 使用将您的访问令牌保存到不会反转的 android 密钥库中。
  2. 使用 NDK 函数进行一些计算,使用很难逆转的 C++ 代码保存您的令牌和 NDK