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

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

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


当前回答

我知道这有点像巫术,但你应该使用Android AccountManager。它是专门为这个场景构建的。这有点麻烦,但它所做的一件事是,如果SIM卡发生变化,本地凭据就会失效,所以如果有人刷你的手机,把一个新的SIM卡扔进去,你的凭据不会被泄露。

这也为用户提供了一种快速简单的方法,可以从一个地方访问(可能还会删除)他们在设备上的任何帐户的存储凭据。

SampleSyncAdapter就是一个使用存储帐户凭据的例子。

其他回答

您还可以查看这个小库,其中包含您提到的功能。

https://github.com/kovmarci86/android-secure-preferences

它与这里的其他一些方法类似。希望会有所帮助:)

这个答案是基于Mark建议的方法。创建EditTextPreference类的自定义版本,它在视图中看到的纯文本和存储在首选项存储中的密码的加密版本之间来回转换。

正如大多数在这篇文章中回答问题的人所指出的,这不是一种非常安全的技术,尽管安全程度在一定程度上取决于所使用的加密/解密代码。但它相当简单和方便,并将阻止大多数随意窥探。

下面是自定义EditTextPreference类的代码:

package com.Merlinia.OutBack_Client;

import android.content.Context;
import android.preference.EditTextPreference;
import android.util.AttributeSet;
import android.util.Base64;

import com.Merlinia.MEncryption_Main.MEncryptionUserPassword;


/**
 * This class extends the EditTextPreference view, providing encryption and decryption services for
 * OutBack user passwords. The passwords in the preferences store are first encrypted using the
 * MEncryption classes and then converted to string using Base64 since the preferences store can not
 * store byte arrays.
 *
 * This is largely copied from this article, except for the encryption/decryption parts:
 * https://groups.google.com/forum/#!topic/android-developers/pMYNEVXMa6M
 */
public class EditPasswordPreference  extends EditTextPreference {

    // Constructor - needed despite what compiler says, otherwise app crashes
    public EditPasswordPreference(Context context) {
        super(context);
    }


    // Constructor - needed despite what compiler says, otherwise app crashes
    public EditPasswordPreference(Context context, AttributeSet attributeSet) {
        super(context, attributeSet);
    }


    // Constructor - needed despite what compiler says, otherwise app crashes
    public EditPasswordPreference(Context context, AttributeSet attributeSet, int defaultStyle) {
        super(context, attributeSet, defaultStyle);
    }


    /**
     * Override the method that gets a preference from the preferences storage, for display by the
     * EditText view. This gets the base64 password, converts it to a byte array, and then decrypts
     * it so it can be displayed in plain text.
     * @return  OutBack user password in plain text
     */
    @Override
    public String getText() {
        String decryptedPassword;

        try {
            decryptedPassword = MEncryptionUserPassword.aesDecrypt(
                     Base64.decode(getSharedPreferences().getString(getKey(), ""), Base64.DEFAULT));
        } catch (Exception e) {
            e.printStackTrace();
            decryptedPassword = "";
        }

        return decryptedPassword;
    }


    /**
     * Override the method that gets a text string from the EditText view and stores the value in
     * the preferences storage. This encrypts the password into a byte array and then encodes that
     * in base64 format.
     * @param passwordText  OutBack user password in plain text
     */
    @Override
    public void setText(String passwordText) {
        byte[] encryptedPassword;

        try {
            encryptedPassword = MEncryptionUserPassword.aesEncrypt(passwordText);
        } catch (Exception e) {
            e.printStackTrace();
            encryptedPassword = new byte[0];
        }

        getSharedPreferences().edit().putString(getKey(),
                                          Base64.encodeToString(encryptedPassword, Base64.DEFAULT))
                .commit();
    }


    @Override
    protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
        if (restoreValue)
            getEditText().setText(getText());
        else
            super.onSetInitialValue(restoreValue, defaultValue);
    }
}

这显示了如何使用它-这是驱动首选项显示的“items”文件。注意,它包含三个普通EditTextPreference视图和一个自定义EditPasswordPreference视图。

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">

    <EditTextPreference
        android:key="@string/useraccountname_key"
        android:title="@string/useraccountname_title"
        android:summary="@string/useraccountname_summary"
        android:defaultValue="@string/useraccountname_default"
        />

    <com.Merlinia.OutBack_Client.EditPasswordPreference
        android:key="@string/useraccountpassword_key"
        android:title="@string/useraccountpassword_title"
        android:summary="@string/useraccountpassword_summary"
        android:defaultValue="@string/useraccountpassword_default"
        />

    <EditTextPreference
        android:key="@string/outbackserverip_key"
        android:title="@string/outbackserverip_title"
        android:summary="@string/outbackserverip_summary"
        android:defaultValue="@string/outbackserverip_default"
        />

    <EditTextPreference
        android:key="@string/outbackserverport_key"
        android:title="@string/outbackserverport_title"
        android:summary="@string/outbackserverport_summary"
        android:defaultValue="@string/outbackserverport_default"
        />

</PreferenceScreen>

至于实际的加密/解密,留给读者练习。我目前正在使用基于本文的一些代码http://zenu.wordpress.com/2011/09/21/aes-128bit-cross-platform-java-and-c-encryption-compatibility/,尽管键和初始化向量的值不同。

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

如何使用共享首选项

用户设置通常使用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.

我将加入这个圈子,只是为了谈谈在Android上保护密码的一般问题。在Android上,设备二进制文件应该被认为是被破坏的——这对于任何终端应用程序都是一样的,因为它是直接由用户控制的。从概念上讲,黑客可以使用对二进制文件的必要访问来反编译它,并找出你的加密密码等等。

因此,如果安全是你主要关注的问题,我想提出两个建议:

1)不要保存实际密码。存储已授予的访问令牌,并使用该访问令牌和话机的签名对会话服务器端进行认证。这样做的好处是,您可以使令牌有一个有限的持续时间,您不会损害原始密码,并且您有一个很好的签名,您可以使用它来关联以后的流量(例如检查入侵尝试和使令牌无效,使其无用)。

2)利用2因素认证。这可能更令人讨厌和侵入,但在某些合规情况下是不可避免的。

共享首选项是存储应用程序数据的最简单方法。但任何人都可以通过应用程序管理器清除我们共享的首选项数据。所以我不认为它对我们的应用是完全安全的。