我正在开发一款Android应用程序,用户需要登录才能执行操作。但在大多数情况下,人们使用"保持登录状态"功能,这时我需要在我的应用程序中维护用户名和密码的值。我应该使用SharedPreferences
还是SQLite数据库,或者有其他更好的方法。
如何确保它的安全性呢?
我正在开发一款Android应用程序,用户需要登录才能执行操作。但在大多数情况下,人们使用"保持登录状态"功能,这时我需要在我的应用程序中维护用户名和密码的值。我应该使用SharedPreferences
还是SQLite数据库,或者有其他更好的方法。
如何确保它的安全性呢?
在安卓上,这很棘手。你不想在偏好设置中存储明文密码,因为任何拥有root权限设备的人基本上都会向全世界显示他们的密码。另一方面,你不能使用加密密码,因为你必须在设备上某处存储加密/解密密钥,同样容易遭受root攻击。
我以前用过的一个解决方法是让服务器生成一个“票证”,将其传回设备,有效期间内都可以使用。该票证由设备用于所有通信,当然要使用SSL,以防止人们窃取你的票证。这样,用户只需在服务器上验证密码一次,服务器就会返回一个过期的票证,而密码则不会存储在设备上的任何地方。
几个三脚认证机制,如OpenID、Facebook甚至Google APIs,都使用此机制。缺点是每隔一段时间,当票证过期时,用户需要重新登录。
最终,这取决于你希望你的应用程序有多安全。如果这只是为了区分用户,并没有像银行账户或血型之类的超级机密信息被存储,那么在设备上以纯文本方式保存密码也是可以的:)
祝你好运,无论你决定采用哪种方法都是最适合你特定情况的!
编辑:我应该注意到这种技术将安全责任转移到服务器——你需要在服务器上使用盐散列进行密码比较,这个想法在此问题的一些其他评论中也可以看到。这样可以防止明文密码出现在设备上的EditText视图、与服务器的SSL通信以及服务器的RAM中(用于盐散列密码)。它不会存储在磁盘上,这是一件好事™。
正如其他人所说,在Android中没有一种安全的方式来完全保护数据存储密码。哈希/加密密码是个好主意,但它只会减慢“破解者”的速度。
在这种情况下,我做了以下操作:
1)我使用了这个simplecryto.java
类,需要提供一个 seed 和文本来进行加密。
2)我在私有模式下使用了 SharedPreferences
来保护保存文件,可以在非root设备上使用。
3)我用于 simplecryto 的种子是一组字节,相对于字符串来说更难以被反编译工具找到。
我的应用程序最近被我公司聘请的“白帽”安全小组进行了审核。他们标记了这个问题,并指出我应该使用OAUTH,但他们也将其列为低风险问题,这意味着它不是很好,但不足以阻止发布。
请记住,“破解者”需要物理访问设备并对其进行root操作,并且还要关心找到种子。
如果您真的关心安全,请不要提供“保持登录状态”的选项。
至少,将其存储在SharedPreferences
(私有模式)中,并记得对密码进行哈希处理。虽然这不会对恶意用户(或已获取root权限的设备)产生实质性影响,但仍然是必要的。
SharedPreferences
,提供安全的加密/解密功能,同时保持与SharedPreferences
相同的API。 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();
我想把密码保存在SharedPreferences中,所以我先私下实现了以下代码:
public class PrefManager {
private SharedPreferences pref;
private SharedPreferences.Editor editor;
public PrefManager(Context context) {
pref = context.getSharedPreferences("PROJECT_NAME", Context.MODE_PRIVATE);
editor = pref.edit();
}
}
我使用了一种算法来加密和解密密码。
加密算法
public void setPassword(String password) {
int len = password.length();
len /= 2;
StringBuilder b1 = new StringBuilder(password.substring(0, len));
StringBuilder b2 = new StringBuilder(password.substring(len));
b1.reverse();
b2.reverse();
password = b1.toString() + b2.toString();
editor.putString("password", password);
editor.apply();
}
decrypt algorithm
public String getPassword() {
String password = pref.getString("password", null);
int len = password.length();
len /= 2;
StringBuilder b1 = new StringBuilder(password.substring(0, len));
StringBuilder b2 = new StringBuilder(password.substring(len));
password = b1.reverse().toString() + b2.reverse().toString();
return password;
}
注意:
在这个简单的算法中,我将密码从中间分成两半,倒过来,然后重新拼接起来。这只是一个想法,你可以使用你自己的算法来改变如何保存密码。
完整代码
import android.content.Context;
import android.content.SharedPreferences;
public class PrefManager {
private SharedPreferences pref;
private SharedPreferences.Editor editor;
public PrefManager(Context context) {
pref = context.getSharedPreferences("PROJECT_NAME", Context.MODE_PRIVATE);
editor = pref.edit();
}
public String getPassword() {
String password = pref.getString("password", null);
int len = password.length();
len /= 2;
StringBuilder b1 = new StringBuilder(password.substring(0, len));
StringBuilder b2 = new StringBuilder(password.substring(len));
password = b1.reverse().toString() + b2.reverse().toString();
return password;
}
public void setPassword(String password) {
int len = password.length();
len /= 2;
StringBuilder b1 = new StringBuilder(password.substring(0, len));
StringBuilder b2 = new StringBuilder(password.substring(len));
b1.reverse();
b2.reverse();
password = b1.toString() + b2.toString();
editor.putString("password", password);
editor.apply();
}
}
在不危及安全的情况下,最安全的方法是使用共享首选项仅存储上次登录用户的用户名。
此外,在用户表中引入一个列来保存数字布尔值(1或0),以表示该人是否勾选了“记住我”复选框。
启动应用程序时,使用getSharedPreferences()
函数获取用户名,并使用它查询托管的数据库,以查看signedin列是1还是0,其中1表示该人已勾选“记住我”复选框。
//encode password
pass_word_et = (EditText) v.findViewById(R.id.password_et);
String pwd = pass_word_et.getText().toString();
byte[] data = new byte[0];
try {
data = pwd.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
String base64 = Base64.encodeToString(data, Base64.DEFAULT);
hbha_pref_helper.saveStringValue("pass_word", base64);
//decode password
String base64=hbha_pref_helper.getStringValue("pass_word");
byte[] data = Base64.decode(base64, Base64.DEFAULT);
String decrypt_pwd="";
try {
decrypt_pwd = new String(data, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
Follow below steps :
1> create checkbox in xml file.
<CheckBox
android:id="@+id/cb_remember"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="@dimen/_25sdp"
android:background="@drawable/rememberme_background"
android:buttonTint="@android:color/white"
android:paddingLeft="@dimen/_10sdp"
android:paddingTop="@dimen/_5sdp"
android:paddingRight="@dimen/_10sdp"
android:paddingBottom="@dimen/_5sdp"
android:text="REMEMBER ME"
android:textColor="@android:color/white"
android:textSize="@dimen/_12sdp" />
2> put this below code in java file.
cb_remember.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
if(b){
Log.d("mytag","checkbox is-----true----");
Prefs.getPrefInstance().setValue(LoginActivity.this, Const.CHECKBOX_STATUS, "1");
String userName =Prefs.getPrefInstance().getValue(context, Const.LOGIN_USERNAME, "");
String password =Prefs.getPrefInstance().getValue(context, Const.LOGIN_PASSWORD, "");
Log.d("mytag","userName and password id----"+userName +" "+password);
edt_user_name.setText(userName);
edt_pwd.setText(password);
}else{
Log.d("mytag","checkbox is-----false----");
Prefs.getPrefInstance().setValue(LoginActivity.this, Const.CHECKBOX_STATUS, "0");
}
}
});
3> add this below code in java file before we check the checkbox.
String stst =Prefs.getPrefInstance().getValue(LoginActivity.this, Const.CHECKBOX_STATUS, "");
Log.d("mytag","statyus of the checkbox is----"+stst);
if(stst.equals("1")){
cb_remember.setChecked(true);
}else{
cb_remember.setChecked(false);
}