我正在开发一个 Android 应用程序,用户需要登录才能执行操作。但大多数情况下,在安卓手机上,人们使用“让我登录”,在这种情况下,我必须在我的应用程序中维护用户名和密码的值。我应该使用SharedPreferences
, 还是 SQLite 数据库,或者还有其他可以使用的东西。
我怎样才能使它安全?
10 回答
是的,这在 Android 上很棘手。您不想将明文密码存储在首选项中,因为任何拥有根设备的人基本上都会向全世界显示他们的密码。另一方面,您不能使用加密密码,因为您必须将加密/解密密钥存储在设备上的某个位置,再次容易受到根攻击。
我不久前使用的一种解决方案是让服务器生成一个“票证”,并将其传回设备,这在一段时间内是有效的。设备使用此票证进行所有通信,当然使用 SSL,因此人们无法窃取您的票证。这样,用户在服务器上验证他们的密码一次,服务器发回一个过期的票,密码永远不会存储在设备上的任何地方。
几种三足身份验证机制,如 OpenID、Facebook,甚至 Google API,都使用这种机制。缺点是每隔一段时间,当票过期时,用户需要重新登录。
最终,这取决于您希望您的应用程序有多安全。如果这只是为了区分用户,并且没有像银行账户或血型那样存储超级机密信息,那么也许将 PWD 以明文形式保存在设备上就可以了 :)
祝你好运,无论您决定哪种方法最适合您的特定情况!
编辑:我应该注意,这种技术将安全责任转移给了服务器 - 您将希望在服务器上使用加盐哈希进行密码比较,您将在此问题的其他一些评论中看到这个想法。这可以防止明文密码出现在除设备上的 EditText 视图、与服务器的 SSL 通信以及服务器的 RAM 之外的任何地方,同时对密码进行加盐和哈希处理。它永远不会存储在磁盘上,这是一件好事™。
正如其他人所说,没有安全的方法可以在 Android 中存储完全保护数据的密码。散列/加密密码是一个好主意,但它所做的只是减慢“破解者”的速度。
话虽如此,这就是我所做的:
1)我使用了这个simplecryto.java
类,它接受一个种子和一个文本并对其进行加密。2)我SharedPreferences
在私人模式下使用它保护非根设备中保存的文件。3) 我用于 simplecryto 的种子是一个字节数组,反编译器比字符串更难找到它。
我的申请最近被我公司雇用的“白帽”安全小组审核。他们标记了这个问题,并指出我应该使用 OAUTH,但他们也将其列为低风险问题,这意味着它不是很好,但还不足以阻止发布。
请记住,“破解者”需要对设备进行物理访问并将其植根并足够小心以找到种子。
如果您真的关心安全性,请不要使用“让我保持登录”选项。
至少,将其存储在SharedPreferences
(私人模式)并且不要忘记散列密码。尽管这对恶意用户(或有根设备)并没有真正的影响,但它确实是。
您可以使用Jetpack 安全库中的EncryptedSharedPreferences。它适用于键值类型设置。
它包装SharedPreferences
,提供安全的加密/解密,同时保持与SharedPreferences
.
就像他们的例子一样:
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();
}
解密算法
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()
函数获取用户名并使用它来查询您的托管数据库以查看登录列是 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();
}
Google 提供了AccountManager的机制。这是用于创建帐户的标准机制。然后将登录数据存储在 Android 认为合适的位置,例如,如果设备提供安全区域,则会使用它。当然,root 设备仍然是一个问题,但至少这是使用标准机制,而不是自创的东西,它也没有从 Android 系统更新中受益。这还有一个好处是该帐户在 Android 设置中列出,另一个积极的功能是“同步”功能,它使帐户能够在应用程序和后端系统之间同步数据,因此您获得的不仅仅是登录。
除此之外,使用用户名和密码不再是最佳选择。现在所有更好的应用程序都在使用 OAuth。这里值得注意的区别是密码在登录期间只传输一次以交换访问令牌。访问令牌通常有一个到期日期,也可以在服务器上撤销。这降低了密码被截获且未存储在设备上的风险。您的后端应该支持这一点。
使用 NDK 进行加密和解密以及在此处定义字符串密钥变量而不是将其保存在共享首选项中或将其定义在字符串 xml 中,这将有助于防止大多数脚本小子窃取密钥。然后将生成的密文存储在共享首选项中。 此链接可能对示例代码有所帮助
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);
}