我正在创建一个使用用户名/密码连接到服务器的应用程序,我想启用“保存密码”选项,这样用户就不必在每次应用程序启动时输入密码。

我试图用共享偏好来做,但不确定这是否是最好的解决方案。

我很感激任何关于如何在Android应用程序中存储用户值/设置的建议。


当前回答

好吧;有一段时间了,答案有点复杂,但这里有一些常见的答案。我疯狂地研究了这个问题,很难找到一个好的答案

The MODE_PRIVATE method is considered generally safe, if you assume that the user didn't root the device. Your data is stored in plain text in a part of the file system that can only be accessed by the original program. This makings grabbing the password with another app on a rooted device easy. Then again, do you want to support rooted devices? AES is still the best encryption you can do. Remember to look this up if you are starting a new implementation if it's been a while since I posted this. The largest issue with this is "What to do with the encryption key?"

所以,现在我们到了“如何处理密钥?”的部分。这是最难的部分。拿到钥匙其实也没那么糟。您可以使用密钥派生函数获取某个密码,并使其成为相当安全的密钥。你确实会遇到诸如“你对PKFDF2进行了多少次传递?”之类的问题,但这是另一个话题

Ideally, you store the AES key off the device. You have to figure out a good way to retrieve the key from the server safely, reliably, and securely though You have a login sequence of some sort (even the original login sequence you do for remote access). You can do two runs of your key generator on the same password. How this works is that you derive the key twice with a new salt and a new secure initialization vector. You store one of those generated passwords on the device, and you use the second password as the AES key.

登录时,在本地登录时重新派生密钥,并将其与存储的密钥进行比较。完成此操作后,您将使用派生键#2用于AES。

使用“一般安全”的方法,使用AES加密数据并将密钥存储在MODE_PRIVATE中。这是最近一篇Android博客文章推荐的。不是很安全,但对一些人来说,纯文本要好得多

你可以做很多变化。例如,您可以使用一个快速的PIN(派生的),而不是完整的登录序列。快速PIN可能不像完整的登录序列那么安全,但它比纯文本安全很多倍

其他回答

使用Richard提供的代码片段,您可以在保存密码之前对其进行加密。然而,首选项API并没有提供一种简单的方法来拦截值并加密它-你可以通过OnPreferenceChange监听器阻止它被保存,理论上你可以通过preferenceChangeListener修改它,但这会导致一个无休止的循环。

我之前建议添加一个“隐藏的”首选项来完成这个任务。这绝对不是最好的方法。我将提出另外两个我认为更可行的选择。

首先,最简单的,是在preferenceChangeListener中,你可以抓取输入的值,加密,然后保存到另一个首选项文件中:

  public boolean onPreferenceChange(Preference preference, Object newValue) {
      // get our "secure" shared preferences file.
      SharedPreferences secure = context.getSharedPreferences(
         "SECURE",
         Context.MODE_PRIVATE
      );
      String encryptedText = null;
      // encrypt and set the preference.
      try {
         encryptedText = SimpleCrypto.encrypt(Preferences.SEED,(String)newValue);

         Editor editor = secure.getEditor();
         editor.putString("encryptedPassword",encryptedText);
         editor.commit();
      }
      catch (Exception e) {
         e.printStackTrace();
      }
      // always return false.
      return false; 
   }

第二种方法,也是我现在更喜欢的方法,是创建自己的自定义首选项,扩展EditTextPreference, @Override setText()和getText()方法,以便setText()加密密码,getText()返回null。

这是对那些根据问题标题来这里的人的补充回答(就像我做的那样),不需要处理与保存密码相关的安全问题。

如何使用共享首选项

用户设置通常使用SharedPreferences和键值对保存在Android本地。使用String键保存或查找相关值。

写入共享首选项

String key = "myInt";
int valueToSave = 10;

SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt(key, valueToSave).commit();

使用apply()而不是commit()在后台保存,而不是立即保存。

从共享首选项读取

String key = "myInt";
int defaultValue = 0;

SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
int savedValue = sharedPref.getInt(key, defaultValue);

如果没有找到键,则使用默认值。

笔记

Rather than using a local key String in multiple places like I did above, it would be better to use a constant in a single location. You could use something like this at the top of your settings activity: final static String PREF_MY_INT_KEY = "myInt"; I used an int in my example, but you can also use putString(), putBoolean(), getString(), getBoolean(), etc. See the documentation for more details. There are multiple ways to get SharedPreferences. See this answer for what to look out for.

正如其他人已经指出的,你可以使用SharedPreferences一般,但如果你想存储加密的数据有点不方便。幸运的是,现在有一种更简单、更快速的方法来加密数据,因为有一个SharedPreferences的实现可以加密密钥和值。你可以在Android JetPack Security中使用EncryptedSharedPreferences。

只需在build.gradle中添加AndroidX Security:

implementation 'androidx.security:security-crypto:1.0.0-rc01'

你可以这样使用它:

String masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC);

SharedPreferences sharedPreferences = EncryptedSharedPreferences.create(
    "secret_shared_prefs",
    masterKeyAlias,
    context,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);

// use the shared preferences and editor as you normally would
SharedPreferences.Editor editor = sharedPreferences.edit();

详情请访问:https://android-developers.googleblog.com/2020/02/data-encryption-on-android-with-jetpack.html

官方文档:https://developer.android.com/reference/androidx/security/crypto/EncryptedSharedPreferences

我就是这么做的。

这不会在严格模式下给出错误。

public class UserPreferenceUtil
{

    private static final String  THEME = "THEME";
    private static final String  LANGUAGE = "LANGUAGE";

    public static String getLanguagePreference(Context context)
    {

        String lang = getPreferenceByKey(context,LANGUAGE);

        if( lang==null || "System".equalsIgnoreCase(lang))
        {
            return null;
        }


        return lang;
    }

    public static void saveLanguagePreference(Context context,String value)
    {
        savePreferenceKeyValue(context, LANGUAGE,value);
    }

    public static String getThemePreference(Context context)
    {

        return getPreferenceByKey(context,THEME);
    }

    public static void saveThemePreference(Context context, String value)
    {
        savePreferenceKeyValue(context,THEME,value);

    }

    public static String getPreferenceByKey(Context context, String preferenceKey )
    {
        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);

        String value = sharedPreferences.getString(preferenceKey, null);

        return value;
    }

    private static void savePreferenceKeyValue(Context context, String preferenceKey, String value)
    {
        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.putString(preferenceKey,value);
        editor.apply();

    }


}

我的应用程序不需要密码。但是,我不会保存密码或加密密码,而是保存单向散列。当用户登录时,我将以同样的方式哈希输入,并将其与存储的哈希匹配。

关于在Android Activity中存储单个首选项的最简单的方法是这样做:

Editor e = this.getPreferences(Context.MODE_PRIVATE).edit();
e.putString("password", mPassword);
e.commit();

如果您担心这些密码的安全性,那么您可以在存储密码之前对其进行加密。