关于安全性:如何在Android中安全存储访问令牌和密钥?

How to securely store access token and secret in Android?

我将使用OAuth从Google获取邮件和联系人。我不想每次都要求用户登录以获取访问令牌和机密。根据我的理解,我需要将它们与我的应用程序存储在数据库或SharedPreferences中。但我有点担心安全问题。我听说你可以加密和解密令牌,但是攻击者很容易就可以对你的apk和类进行反编译并获得加密密钥。在Android中安全存储这些令牌的最佳方法是什么?


将它们存储为共享首选项。默认情况下,它们是私有的,其他应用程序无法访问它们。在根设备上,如果用户明确地允许访问一些试图读取它们的应用程序,那么应用程序可能能够使用它们,但您无法对此进行保护。至于加密,您必须要求用户每次输入解密密码短语(从而破坏了缓存凭据的目的),或者将密钥保存到文件中,这样您就会遇到同样的问题。

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

  • 第三方应用程序不需要知道密码,用户可以确保只将其发送到原始站点(Facebook、Twitter、Gmail等)。
  • 即使有人偷了一个令牌,也不能看到密码(用户可能也在其他站点上使用)。
  • 令牌通常有一个生存期,并在一段时间后过期。
  • 如果怀疑令牌已被泄露,则可以撤销令牌。


您可以将它们存储在AccountManager中。这些人认为这是最佳实践。

enter image description here

官方定义如下:

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.

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

  • 乌迪科恩教程
  • 皮兰特博客
  • 谷歌IO演示

但是,在最后,AccountManager只将令牌存储为纯文本。所以,我建议在将您的秘密存储到accountmanager之前对其进行加密。您可以使用各种加密库,如aescrypt或aescrypt

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

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


sharedreferences本身不是安全位置。在根设备上,我们可以轻松地读取和修改所有应用程序的sharedreferencesxml,因此令牌应该相对频繁地过期。但是,即使令牌每小时过期一次,新的令牌仍然可以从共享的引用中被盗。Android密钥库应用于长期存储和检索加密密钥,这些密钥将用于加密我们的令牌,以便将它们存储在共享的引用或数据库中。密钥不存储在应用程序的进程中,因此很难被破坏。

因此,比一个地方更重要的是,它们如何能够自身安全,例如,使用加密签名的短命JWT,使用Android密钥库对它们进行加密,并使用安全协议发送它们。


  • 从Android Studio的项目窗格中,选择"项目文件",并在项目的根目录中创建一个名为"keystore.properties"的新文件。
  • enter image description here

  • 打开"keystore.properties"文件,并将访问令牌和机密保存在该文件中。
  • enter image description here

  • 现在将读取访问令牌和秘密加载到应用程序模块的build.gradle文件中。然后您需要为您的访问令牌和秘密定义buildconfig变量,这样您就可以直接从代码中访问它们。build.gradle可能如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    ... ... ...

    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"]
        }
    }
  • 您可以在代码中使用访问令牌和密码,如下所示:

    1
    2
    String accessToken = BuildConfig.ACCESS_TOKEN;
    String secret = BuildConfig.SECRET;
  • 这样,您就不需要将访问令牌和秘密以纯文本的形式存储在项目中。因此,即使有人解压缩您的APK,他们永远不会得到您的访问令牌和秘密,因为您正在从外部文件加载它们。


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

  • 使用将您的访问令牌保存到不会反转的Android密钥库中。
  • 使用NDK函数进行一些计算,用C++代码保存令牌和NDK,这是很难逆转的。