我正在创建一个使用用户名/密码连接到服务器的应用程序,我想启用“保存密码”选项,这样用户就不必在每次应用程序启动时输入密码。
我试图用共享偏好来做,但不确定这是否是最好的解决方案。
我很感激任何关于如何在Android应用程序中存储用户值/设置的建议。
我正在创建一个使用用户名/密码连接到服务器的应用程序,我想启用“保存密码”选项,这样用户就不必在每次应用程序启动时输入密码。
我试图用共享偏好来做,但不确定这是否是最好的解决方案。
我很感激任何关于如何在Android应用程序中存储用户值/设置的建议。
当前回答
关于在Android Activity中存储单个首选项的最简单的方法是这样做:
Editor e = this.getPreferences(Context.MODE_PRIVATE).edit();
e.putString("password", mPassword);
e.commit();
如果您担心这些密码的安全性,那么您可以在存储密码之前对其进行加密。
其他回答
这是对那些根据问题标题来这里的人的补充回答(就像我做的那样),不需要处理与保存密码相关的安全问题。
如何使用共享首选项
用户设置通常使用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.
首先,我认为用户的数据不应该存储在手机上,如果必须将数据存储在手机上的某个地方,它应该在应用程序的私有数据中加密。用户凭证的安全性应该是应用程序的优先级。
敏感数据必须安全存储,否则就不存储。在设备丢失或恶意软件感染的情况下,不安全存储的数据可能会受到损害。
您需要使用sqlite,安全工具来存储密码。 下面是最好的例子,它存储密码——passwordsafe。 这是来源和解释的链接 http://code.google.com/p/android-passwordsafe/
一般来说,SharedPreferences是存储首选项的最佳选择,所以一般来说,我推荐使用这种方法来保存应用程序和用户设置。
这里唯一需要关注的是你节省了什么。存储密码总是一件很棘手的事情,我对以明文形式存储密码特别谨慎。Android架构是这样的,你的应用程序的SharedPreferences是沙盒的,以防止其他应用程序能够访问这些值,所以有一些安全性,但对手机的物理访问可能允许访问这些值。
如果可能的话,我会考虑修改服务器,使用协商的令牌来提供访问,比如OAuth。或者,您可能需要构造某种加密存储,尽管这不是简单的。至少,要确保在将密码写入磁盘之前对其进行了加密。
我同意Reto和fiXedd。客观地说,在SharedPreferences中投入大量的时间和精力来加密密码并没有多大意义,因为任何可以访问您的首选项文件的攻击者都很可能也可以访问您的应用程序的二进制文件,因此也可以访问解密密码的密钥。
然而,话虽如此,似乎确实有一种宣传活动正在进行,即识别在SharedPreferences中以明文存储密码的移动应用程序,并对这些应用程序进行不利的报道。参见http://blogs.wsj.com/digits/2011/06/08/some-top-apps-put-data-at-risk/和http://viaforensics.com/appwatchdog获得一些示例。
虽然我们总体上需要更多地关注安全问题,但我认为,这种对这一特定问题的关注实际上并不能显著提高我们的整体安全。然而,鉴于人们的看法,这里有一个解决方案来加密您放置在SharedPreferences中的数据。
只需将您自己的SharedPreferences对象包装在这个对象中,您读/写的任何数据都将自动加密和解密。如。
final SharedPreferences prefs = new ObscuredSharedPreferences(
this, this.getSharedPreferences(MY_PREFS_FILE_NAME, Context.MODE_PRIVATE) );
// eg.
prefs.edit().putString("foo","bar").commit();
prefs.getString("foo", null);
下面是这个类的代码:
/**
* Warning, this gives a false sense of security. If an attacker has enough access to
* acquire your password store, then he almost certainly has enough access to acquire your
* source binary and figure out your encryption key. However, it will prevent casual
* investigators from acquiring passwords, and thereby may prevent undesired negative
* publicity.
*/
public class ObscuredSharedPreferences implements SharedPreferences {
protected static final String UTF8 = "utf-8";
private static final char[] SEKRIT = ... ; // INSERT A RANDOM PASSWORD HERE.
// Don't use anything you wouldn't want to
// get out there if someone decompiled
// your app.
protected SharedPreferences delegate;
protected Context context;
public ObscuredSharedPreferences(Context context, SharedPreferences delegate) {
this.delegate = delegate;
this.context = context;
}
public class Editor implements SharedPreferences.Editor {
protected SharedPreferences.Editor delegate;
public Editor() {
this.delegate = ObscuredSharedPreferences.this.delegate.edit();
}
@Override
public Editor putBoolean(String key, boolean value) {
delegate.putString(key, encrypt(Boolean.toString(value)));
return this;
}
@Override
public Editor putFloat(String key, float value) {
delegate.putString(key, encrypt(Float.toString(value)));
return this;
}
@Override
public Editor putInt(String key, int value) {
delegate.putString(key, encrypt(Integer.toString(value)));
return this;
}
@Override
public Editor putLong(String key, long value) {
delegate.putString(key, encrypt(Long.toString(value)));
return this;
}
@Override
public Editor putString(String key, String value) {
delegate.putString(key, encrypt(value));
return this;
}
@Override
public void apply() {
delegate.apply();
}
@Override
public Editor clear() {
delegate.clear();
return this;
}
@Override
public boolean commit() {
return delegate.commit();
}
@Override
public Editor remove(String s) {
delegate.remove(s);
return this;
}
}
public Editor edit() {
return new Editor();
}
@Override
public Map<String, ?> getAll() {
throw new UnsupportedOperationException(); // left as an exercise to the reader
}
@Override
public boolean getBoolean(String key, boolean defValue) {
final String v = delegate.getString(key, null);
return v!=null ? Boolean.parseBoolean(decrypt(v)) : defValue;
}
@Override
public float getFloat(String key, float defValue) {
final String v = delegate.getString(key, null);
return v!=null ? Float.parseFloat(decrypt(v)) : defValue;
}
@Override
public int getInt(String key, int defValue) {
final String v = delegate.getString(key, null);
return v!=null ? Integer.parseInt(decrypt(v)) : defValue;
}
@Override
public long getLong(String key, long defValue) {
final String v = delegate.getString(key, null);
return v!=null ? Long.parseLong(decrypt(v)) : defValue;
}
@Override
public String getString(String key, String defValue) {
final String v = delegate.getString(key, null);
return v != null ? decrypt(v) : defValue;
}
@Override
public boolean contains(String s) {
return delegate.contains(s);
}
@Override
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener onSharedPreferenceChangeListener) {
delegate.registerOnSharedPreferenceChangeListener(onSharedPreferenceChangeListener);
}
@Override
public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener onSharedPreferenceChangeListener) {
delegate.unregisterOnSharedPreferenceChangeListener(onSharedPreferenceChangeListener);
}
protected String encrypt( String value ) {
try {
final byte[] bytes = value!=null ? value.getBytes(UTF8) : new byte[0];
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = keyFactory.generateSecret(new PBEKeySpec(SEKRIT));
Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
pbeCipher.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(Settings.Secure.getString(context.getContentResolver(),Settings.Secure.ANDROID_ID).getBytes(UTF8), 20));
return new String(Base64.encode(pbeCipher.doFinal(bytes), Base64.NO_WRAP),UTF8);
} catch( Exception e ) {
throw new RuntimeException(e);
}
}
protected String decrypt(String value){
try {
final byte[] bytes = value!=null ? Base64.decode(value,Base64.DEFAULT) : new byte[0];
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = keyFactory.generateSecret(new PBEKeySpec(SEKRIT));
Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
pbeCipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(Settings.Secure.getString(context.getContentResolver(),Settings.Secure.ANDROID_ID).getBytes(UTF8), 20));
return new String(pbeCipher.doFinal(bytes),UTF8);
} catch( Exception e) {
throw new RuntimeException(e);
}
}
}