Android登入記住密碼,AES加密儲存密碼
阿新 • • 發佈:2019-01-07
Android登入記住密碼,最常見的方式是用SharedPreferences。
SharedPreference是Android提供的一種輕量級的資料儲存方式,主要用來儲存一些簡單的配置資訊,例如,預設歡迎語,登入使用者名稱和密碼等。其以鍵值對的方式儲存,使得我們能很方便進行讀取和存入。
文章中的記住密碼功能,也是用的SharedPreference實現的,其中儲存的密碼用AES演算法加密。不多說,頁面如下圖:
先來看佈局檔案,佈局中有很多string資源和drawable資源的引用,可在完整程式碼檔案中找到。
下面是Activity程式碼。<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>
接下來是AES演算法加密程式碼。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物件中,大家自己去看完整的程式碼。