Android登录记住密码,最常见的方式是用SharedPreferences。
SharedPreference是Android提供的一种轻量级的数据存储方式,主要用来存储一些简单的配置信息,例如,默认欢迎语,登录用户名和密码等。其以键值对的方式存储,使得我们能很方便进行读取和存入。
文章中的记住密码功能,也是用的SharedPreference实现的,其中保存的密码用AES算法加密。不多说,页面如下图:
先来看布局文件,布局中有很多string资源和drawable资源的引用,可在完整代码文件中找到。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/padding1"
android:gravity="center"
android:orientation="vertical"
android:background="@color/login_bg"
tools:context="com.example.remenberpassword.MainActivity" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:orientation="vertical">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="@dimen/padding2"
android:gravity="center_vertical">
<ImageView
android:id="@+id/img_username"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:src="@drawable/username" />
<EditText
android:id="@+id/edit_username"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/margin1"
android:layout_weight="7"
android:hint="@string/username"
android:background="@null"/>
</LinearLayout>
<View
android:layout_width="fill_parent"
android:layout_height="1dp"
android:background="@color/gainsboro"/>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="@dimen/padding2"
android:gravity="center_vertical">
<ImageView
android:id="@+id/img_password"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:src="@drawable/password" />
<EditText
android:id="@+id/edit_password"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:layout_marginLeft="@dimen/margin1"
android:inputType="textPassword"
android:hint="@string/password"
android:background="@null"/>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="@dimen/margin1"
android:gravity="center_vertical">
<CheckBox
android:id="@+id/check_remenber_password"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:checked="true"
android:text="@string/remember_password" />
</LinearLayout>
<Button
android:id="@+id/btn_login"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin1"
android:textSize="@dimen/btn_textsize1"
android:textColor="@color/white"
android:background="@drawable/btn_selector"
android:text="@string/login" />
</LinearLayout>
package com.example.remenberpassword;
import com.example.common.CommonUtils;
import com.example.common.EncrypAES;
import com.example.common.User;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
public class MainActivity extends ActionBarActivity {
/**
* 用了保存用户名和密码的文件名,文件是xml文件,但是不用加后缀,系统会自动加上。。
*/
private static final String USER_INFO="User_Info";
/**
* 文件中记录用户名的关键字。
*/
private static final String USER_NAME="User_Name";
/**
* 文件中记录密码的关键字。
*/
private static final String PASSWORD="Password";
/**
* 文件中记录是否选择记住密码功能的关键字
*/
private static final String IS_REMENBER_PASSWORD="Is_Remenber_Password";
private User mUser;
private EditText editUsername;
private EditText editPassword;
private CheckBox checkRememberPassword;
private Button btnLogin;
private SharedPreferences mSharedPreferences;
/**
*对用户名和密码进行加解密
*/
private EncrypAES mAes;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
InitData();
InitWidget();
}
public void InitData(){
mUser=new User();
//取得保存用户名和密码的xml文件,如果文件不存在,系统会自动创建
mSharedPreferences=getSharedPreferences(USER_INFO, Context.MODE_PRIVATE);
mAes=new EncrypAES();
}
public void InitWidget(){
editUsername=(EditText)findViewById(R.id.edit_username);
editPassword=(EditText)findViewById(R.id.edit_password);
checkRememberPassword=(CheckBox)findViewById(R.id.check_remenber_password);
btnLogin=(Button)findViewById(R.id.btn_login);
//如果选中了记住密码,则从记住的密码中获取用户名和密码
if(mSharedPreferences.getBoolean(IS_REMENBER_PASSWORD, true)){
editUsername.setText(mSharedPreferences.getString(USER_NAME, ""));
//对读取到的密码进行解密
String password=mSharedPreferences.getString(PASSWORD, "");
if(0!=password.length()){
password=mAes.DecryptorString(password);
}
editPassword.setText(password);
}
//记住密码CheckBox监听函数
checkRememberPassword.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
mSharedPreferences.edit().putBoolean(IS_REMENBER_PASSWORD, isChecked).commit();
}
});
//登录按钮监听函数
btnLogin.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//判断是否打开网络连接
if(!CommonUtils.CheckNetwork(MainActivity.this)){
CommonUtils.ShowToast(MainActivity.this, R.string.network_hint);
return ;
}
if(!CheckUserInfoInput()){
return ;
}
savaUserNameAndPassword(mUser);
Intent intent=new Intent(MainActivity.this,SecondActivity.class);
startActivity(intent);
}
});
}
/**
* 检查用户名和密码输入
* @return
*/
public boolean CheckUserInfoInput(){
String strUserName;
String strPassword;
strUserName=editUsername.getText().toString();
strPassword=editPassword.getText().toString();
if(0==strUserName.length() | 0==strPassword.length()){
CommonUtils.ShowToast(MainActivity.this,R.string.username_password_inputhint);
return false;
}
//设置用户名和密码
mUser.setUserName(strUserName);
mUser.setPassword(strPassword);
return true;
}
/**
* 如果选中了“记住密码”功能,则保存用户名和密码
* @param user 包含了用户名和密码
*/
public void savaUserNameAndPassword(User user){
if(checkRememberPassword.isChecked()){
Editor editor=mSharedPreferences.edit();
editor.putBoolean(IS_REMENBER_PASSWORD, true);
editor.putString(USER_NAME, user.getUserName());
//对密码进行加密保存
String password=mAes.EncryptorString(user.getPassword());
editor.putString(PASSWORD, password);
editor.commit();
}
}
}
代码中,SecureRandom sr = SecureRandom.getInstance("SHA1PRNG", "Crypto");这一条语句是需要特别注意的,在代码中有详细的说明。
另外,用c.doFinal(buff)函数加解密得到的结果都是byte[]型的,不能对加密得到的byte[]型结果直接new String(byte[]);保存,然后在解密时用str.getBytes();转换。
所以,借用了网上其他网友的方法,有toHex、fromHex、toByte、toHex、appendHex。可以确保byte[]和String之间的转换不出问题。
package com.example.common;
import java.security.InvalidKeyException;
import java.security.SecureRandom;
import java.security.Security;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import android.util.Log;
/**
* AES算法加密类
* @author Administrator
*
*/
public class EncrypAES {
private static final String TAG="EncryAES";
/**
* Cipher负责完成加密或解密工作
*/
private Cipher c;
/**
* 该字节数组负责保存加密的结果
*/
private byte[] cipherByte;
/**
* 用于生产密钥
*/
private static final String SECRETKET="AESDemo";
private SecretKeySpec deskey;
public EncrypAES(){
Security.addProvider(new com.sun.crypto.provider.SunJCE());
try {
deskey = new SecretKeySpec(getRawKey(SECRETKET.getBytes()),"AES");
//生成Cipher对象,指定其支持的DES算法
c = Cipher.getInstance("AES");
} catch (Exception e) {
Log.e(TAG, "EnrypAES construct failed.",e);
}
}
/**
* 对字符串加密
*
* @param str
* @return
* @throws InvalidKeyException
* @throws IllegalBlockSizeException
* @throws BadPaddingException
*/
private byte[] Encrytor(String str) throws InvalidKeyException,
IllegalBlockSizeException, BadPaddingException {
// 根据密钥,对Cipher对象进行初始化,ENCRYPT_MODE表示加密模式
c.init(Cipher.ENCRYPT_MODE, deskey);
byte[] src = str.getBytes();
cipherByte = c.doFinal(src); // 加密,结果保存进cipherByte
return cipherByte;
}
/**
* 对字符串解密
*
* @param buff
* @return
* @throws InvalidKeyException
* @throws IllegalBlockSizeException
* @throws BadPaddingException
*/
private byte[] Decryptor(byte[] buff) throws InvalidKeyException,
IllegalBlockSizeException, BadPaddingException {
// 根据密钥,对Cipher对象进行初始化,DECRYPT_MODE表示加密模式
c.init(Cipher.DECRYPT_MODE, deskey);
cipherByte = c.doFinal(buff);
return cipherByte;
}
/**
* 对字符串进行加密
* @param string 要加密的字符串
* @return 加密后的字符串
*/
public String EncryptorString(String string){
String result =null;
byte[] encontent;
try {
encontent = Encrytor(string);
result=toHex(encontent);
} catch (InvalidKeyException e) {
Log.e(TAG, "EncryptorString",e);
} catch (IllegalBlockSizeException e) {
Log.e(TAG, "EncryptorString",e);
} catch (BadPaddingException e) {
Log.e(TAG, "EncryptorString",e);
}
return result;
}
/**
* 对字符串进行解密
* @param string 要解密的字符串
* @return 解密后的字符串
*/
public String DecryptorString(String string){
byte[] cryptcontent=toByte(string);
byte[] decontent;
String result=null;
try {
decontent=Decryptor(cryptcontent);
result=new String(decontent);
} catch (InvalidKeyException e) {
Log.e(TAG,"DecryptorString failed.",e);
} catch (IllegalBlockSizeException e) {
Log.e(TAG,"DecryptorString failed.",e);
} catch (BadPaddingException e) {
Log.e(TAG,"DecryptorString failed.",e);
}
return result;
}
private static byte[] getRawKey(byte[] seed) throws Exception {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
/**
* 这一句很关键。
* <br>网上有的代码是这一句SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
* <br>在getInstance函数中少了一个String参数。
* 这个参数是不能少的,因为如果少了这个参数的话,生成的密钥是随机的,而加密和解密必须是用同样的密钥的
* 所以会出现不能解密的问题(总是抛出BadPaddingException异常)。而且这种方法中,对比少了一个参数的加密结果,
* 会发现每一次加密的结果都是不一样的。
* <br>而用下面的语句得到的密钥加密,同样的字符串任何时候得到的加密结果都是一样的。
*/
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG", "Crypto");
sr.setSeed(seed);
kgen.init(128, sr); // 192 and 256 bits may not be available
SecretKey skey = kgen.generateKey();
byte[] raw = skey.getEncoded();
return raw;
}
public static String toHex(String txt) {
return toHex(txt.getBytes());
}
public static String fromHex(String hex) {
return new String(toByte(hex));
}
public static byte[] toByte(String hexString) {
int len = hexString.length()/2;
byte[] result = new byte[len];
for (int i = 0; i < len; i++)
result[i] = Integer.valueOf(hexString.substring(2*i, 2*i+2), 16).byteValue();
return result;
}
public static String toHex(byte[] buf) {
if (buf == null)
return "";
StringBuffer result = new StringBuffer(2*buf.length);
for (int i = 0; i < buf.length; i++) {
appendHex(result, buf[i]);
}
return result.toString();
}
private final static String HEX = "0123456789ABCDEF";
private static void appendHex(StringBuffer sb, byte b) {
sb.append(HEX.charAt((b>>4)&0x0f)).append(HEX.charAt(b&0x0f));
}
}
至于代码中其他的内容,比如把用户名和密码封装在了User对象中,大家自己去看完整的代码。