我正在创建一个使用用户名/密码连接到服务器的应用程序,我想启用“保存密码”选项,这样用户就不必在每次应用程序启动时输入密码。
我试图用共享偏好来做,但不确定这是否是最好的解决方案。
我很感激任何关于如何在Android应用程序中存储用户值/设置的建议。
我正在创建一个使用用户名/密码连接到服务器的应用程序,我想启用“保存密码”选项,这样用户就不必在每次应用程序启动时输入密码。
我试图用共享偏好来做,但不确定这是否是最好的解决方案。
我很感激任何关于如何在Android应用程序中存储用户值/设置的建议。
当前回答
使用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。
其他回答
这个答案是基于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.
一般来说,SharedPreferences是存储首选项的最佳选择,所以一般来说,我推荐使用这种方法来保存应用程序和用户设置。
这里唯一需要关注的是你节省了什么。存储密码总是一件很棘手的事情,我对以明文形式存储密码特别谨慎。Android架构是这样的,你的应用程序的SharedPreferences是沙盒的,以防止其他应用程序能够访问这些值,所以有一些安全性,但对手机的物理访问可能允许访问这些值。
如果可能的话,我会考虑修改服务器,使用协商的令牌来提供访问,比如OAuth。或者,您可能需要构造某种加密存储,尽管这不是简单的。至少,要确保在将密码写入磁盘之前对其进行了加密。
正如其他人已经指出的,你可以使用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
您需要使用sqlite,安全工具来存储密码。 下面是最好的例子,它存储密码——passwordsafe。 这是来源和解释的链接 http://code.google.com/p/android-passwordsafe/