Nik*_*lin 302 java encryption android credentials preferences
我正在创建一个使用用户名/密码连接到服务器的应用程序,我想启用"保存密码"选项,这样用户每次启动应用程序时都不必输入密码.
我试图使用共享偏好设置,但不确定这是否是最佳解决方案.
我将不胜感激任何有关如何在Android应用程序中存储用户值/设置的建议.
Ret*_*ier 232
通常,SharedPreferences是存储首选项的最佳选择,因此通常我建议使用这种方法来保存应用程序和用户设置.
这里唯一关注的领域是你正在拯救的东西.密码总是存储起来很棘手,我特别担心将它们存储为明文.Android体系结构使得应用程序的SharedPreferences被沙箱化,以防止其他应用程序访问这些值,因此存在一些安全性,但对手机的物理访问可能允许访问这些值.
如果可能,我会考虑修改服务器以使用协商令牌来提供访问,例如OAuth.或者,您可能需要构建某种加密存储,尽管这是非平凡的.至少,确保在将密码写入磁盘之前加密密码.
emm*_*mby 209
我同意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);
Run Code Online (Sandbox Code Playgroud)
这是该类的代码:
/**
* 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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
Jer*_*gan 28
关于在Android Activity中存储单个首选项的最简单方法是执行以下操作:
Editor e = this.getPreferences(Context.MODE_PRIVATE).edit();
e.putString("password", mPassword);
e.commit();
Run Code Online (Sandbox Code Playgroud)
如果您担心这些安全性,那么您可以在存储之前始终加密密码.
Mar*_*ark 10
使用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;
}
Run Code Online (Sandbox Code Playgroud)
第二种方式,也就是我现在喜欢的方式,是创建自己的自定义首选项,扩展EditTextPreference,@覆盖setText()和getText()方法,以便setText()加密密码,并getText()返回null.
好的; 已经有一段时间了,因为答案有点混乱,但这里有一些常见的答案.我研究这个就像疯了一样,很难建立一个好的答案
如果您假设用户没有root设备,则认为MODE_PRIVATE方法通常是安全的.您的数据以纯文本格式存储在文件系统的一部分中,只能由原始程序访问.这个makings很容易在root设备上使用另一个应用程序获取密码.那么,你想支持root设备吗?
AES仍然是您可以做的最佳加密.如果你开始一个新的实现,如果我发布这个已经有一段时间了,请记得查看这个.最大的问题是"如何处理加密密钥?"
那么,现在我们正处于"如何处理密钥?" 一部分.这是困难的部分.获得密钥并没有那么糟糕.您可以使用密钥派生函数来获取一些密码并使其成为非常安全的密钥.您确实会遇到诸如"您使用PKFDF2传递多少次?"等问题,但这是另一个主题
理想情况下,您将AES密钥存储在设备上.您必须找到一种安全,可靠和安全地从服务器检索密钥的好方法
您有某种登录顺序(甚至是您为远程访问所做的原始登录顺序).您可以使用相同的密码执行两次密钥生成器运行.这是如何工作的,您使用新的盐和新的安全初始化向量导出密钥两次.您将其中一个生成的密码存储在设备上,并使用第二个密码作为AES密钥.
登录时,您将在本地登录名上重新导出密钥并将其与存储的密钥进行比较.完成后,您将使用派生密钥#2进行AES.
你可以做很多这些变化.例如,您可以执行快速PIN(派生),而不是完整登录序列.快速PIN可能不如完整登录序列安全,但它比纯文本安全多倍
我知道这有点神秘,但你应该使用Android AccountManager.它是专为此方案而构建的.这有点麻烦,但如果SIM卡发生变化,它所做的一件事就是使本地凭证失效,所以如果有人刷你的手机并在其中抛出一个新的SIM卡,你的凭证就不会受到损害.
这也为用户提供了一种快速简便的方法,可以从一个位置访问(并可能删除)他们在设备上拥有的任何帐户的存储凭据.
SampleSyncAdapter是一个使用存储帐户凭据的示例.
小智 5
我会把我的帽子扔进戒指,只是为了谈论在Android上保护密码.在Android上,设备二进制文件应被视为已泄露 - 对于任何直接用户控制的终端应用程序而言,这都是相同的.从概念上讲,黑客可以使用对二进制文件的必要访问来对其进行反编译并根除您的加密密码等.
因此,如果安全性是您的主要关注点,那么我有两条建议可以抛弃:
1)不要存储实际密码.存储授予的访问令牌,并使用访问令牌和电话签名对会话服务器端进行身份验证.这样做的好处是,您可以使令牌具有有限的持续时间,您不会破坏原始密码,并且您有一个很好的签名,您可以使用该签名以便稍后与流量相关联(例如,检查入侵尝试并使其无效)令牌呈现它无用).
2)利用2因素认证.这可能更令人讨厌和侵扰,但对于某些合规情况是不可避免的.
这是针对那些根据问题标题到达这里的人(就像我所做的那样)的补充答案,并且不需要处理与保存密码相关的安全问题。
用户设置通常使用SharedPreferences键值对保存在 Android 本地。您可以使用该String键保存或查找关联的值。
String key = "myInt";
int valueToSave = 10;
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt(key, valueToSave).commit();
Run Code Online (Sandbox Code Playgroud)
使用apply()而不是commit()在后台保存而不是立即保存。
String key = "myInt";
int defaultValue = 0;
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
int savedValue = sharedPref.getInt(key, defaultValue);
Run Code Online (Sandbox Code Playgroud)
如果未找到密钥,则使用默认值。
与其像我上面那样在多个地方使用本地键字符串,不如在单个位置使用常量。您可以在设置活动的顶部使用类似的内容:
final static String PREF_MY_INT_KEY = "myInt";
Run Code Online (Sandbox Code Playgroud)
int我在示例中使用了 an ,但您也可以使用putString(), putBoolean(), getString(),getBoolean()等。
请参阅文档了解更多详细信息。
有多种方法可以获取 SharedPreferences。请参阅此答案以了解需要注意的事项。
| 归档时间: |
|
| 查看次数: |
147169 次 |
| 最近记录: |