淺談android資料儲存加密
寫在開頭
在移動端的開發中,資料安全的問題一直是大家備受關注的,資料加密技術也受到了大家的青睞。專案中也用到了一些,在這裡學習總結下,完善下自己的知識,也分享給大家,一起交流!(末尾也會說下自己在專案中的使用)
- 按可逆性:加密可分為可逆演算法和不可逆演算法
- 按對稱性:加密可分為對稱演算法和非對稱演算法
一般的加密分為以下幾種,下面會分別簡單講解原理和其使用方法:
- Base64編碼演算法 (可逆)
- MD5加密 (不可逆)(還有一個sha1值,可能做過支付寶的會比較熟悉)
- Des加密 (對稱,可逆)
- Aes加密 (對稱,可逆)
- Rsa加密(非對稱,可逆)
淺談對稱非對稱
對稱
對稱加密演算法是較傳統的加密體制,即通訊雙方在加/解密過程中使用他們共享的單一金鑰,鑑於其演算法簡單和加密速度快的優點,目前仍然在使用,但是安全性方面就差一點可能。最常用的對稱密碼演算法是DES演算法,而DES金鑰長度較短,已經不適合當今分散式開放網路對資料加密安全性的要求。一種新的基於Rijndael演算法(自己腦補,本人也不太熟悉)對稱高階資料加密標準AES取代了資料加密標準DES,彌補了DES的缺陷,目前使用比較多一點
非對稱
非對稱加密由於加/解金鑰不同(公鑰加密,私鑰解密),金鑰管理簡單,得到了很廣泛的應用。RSA是非對稱加密系統最著名的公鑰密碼演算法。但是由於RSA演算法進行的都是大數計算,使得RSA最快的情況也比AES慢上倍,這是RSA最大的缺陷。但是其安全性較高,這也是大家比較喜歡的地方吧!
演算法講解及使用
Base64演算法
Base64其實並不是安全領域的加密演算法,因為它的加密解密演算法都是公開的,典型的防菜鳥不防程式猿的例子哈哈,Base64編碼本質上是一種將二進位制資料轉成文字資料的方案。用處就是將一些不適合傳輸的資料內容進行編碼來適合傳輸。
字串進行Base64編碼
String encodedString = Base64.encodeToString("wenwen".getBytes(), Base64.DEFAULT);
第一個引數就是位元組陣列
字串進行Base64解碼
String decodedString =new String(Base64.decode(encodedString,Base64.DEFAULT));
decodedString 就是wenwen
MD5演算法
它是一種單向加密演算法,只能加密、無法解密。多用於密碼的儲存等等。對於MD5的安全性,網上有關MD5解密的網站數不勝數,破解機制採用窮舉法,就是手機所有可能的MD5值跑字典。所以常常採用對資料進行多次MD5加密或者採取加鹽(就是加一段獨有的字串在進行加密)的操作。
使用:
public static String md5(String string) {
if (TextUtils.isEmpty(string)) {
return "";
}
MessageDigest md5 = null;
try {
md5 = MessageDigest.getInstance("MD5");
byte[] bytes = md5.digest(string.getBytes());
String result = "";
for (byte b : bytes) {
String temp = Integer.toHexString(b & 0xff);
if (temp.length() == 1) {
temp = "0" + temp;
}
result += temp;
}
return result;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
對稱加密(這裡只說下應用廣泛的AES)
對稱加密祕鑰是唯一的,加密解密都是一個祕鑰。AES速度上佔優於RSA,但是隻有一個祕鑰,安全性較低一些。
使用
//常量介紹
private final static String HEX = "0123456789ABCDEF";
//AES是加密方式 CBC是工作模式 PKCS5Padding是填充模式
private static final String CBC_PKCS5_PADDING = "AES/CBC/PKCS5Padding";
//AES 加密
private static final String AES = "AES";
// SHA1PRNG 強隨機種子演算法, 要區別4.2以上版本的呼叫方法
private static final String SHA1PRNG="SHA1PRNG";
//生成隨機數,可以當做動態的金鑰 加密和解密的金鑰必須一致,不然將不能解密
public static String generateKey() {
try {
SecureRandom localSecureRandom = SecureRandom.getInstance(SHA1PRNG);
byte[] bytes_key = new byte[20];
localSecureRandom.nextBytes(bytes_key);
String str_key = toHex(bytes_key);
return str_key;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
// 對金鑰進行處理
private static byte[] getRawKey(byte[] seed) throws Exception {
KeyGenerator kgen = KeyGenerator.getInstance(AES);
//for android
SecureRandom sr = null;
// 在4.2以上版本中,SecureRandom獲取方式發生了改變
if (android.os.Build.VERSION.SDK_INT >= 17) {
sr = SecureRandom.getInstance(SHA1PRNG, "Crypto");
} else {
sr = SecureRandom.getInstance(SHA1PRNG);
}
// for Java
// secureRandom = SecureRandom.getInstance(SHA1PRNG);
sr.setSeed(seed);
kgen.init(128, sr); //256 bits or 128 bits,192bits
//AES中128位金鑰版本有10個加密迴圈,192位元金鑰版本有12個加密迴圈,256位元金鑰版本則有14個加密迴圈。
SecretKey skey = kgen.generateKey();
byte[] raw = skey.getEncoded();
return raw;
}
//加密
public static String encrypt(String key, String cleartext) {
if (TextUtils.isEmpty(cleartext)) {
return cleartext;
}
try {
byte[] result = encrypt(key, cleartext.getBytes());
return Base64Encoder.encode(result);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static byte[] encrypt(String key, byte[] clear) throws Exception {
byte[] raw = getRawKey(key.getBytes());
SecretKeySpec skeySpec = new SecretKeySpec(raw, AES);
Cipher cipher = Cipher.getInstance(CBC_PKCS5_PADDING);
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()]));
byte[] encrypted = cipher.doFinal(clear);
return encrypted;
}
/*
* 解密
*/
public static String decrypt(String key, String encrypted) {
if (TextUtils.isEmpty(encrypted)) {
return encrypted;
}
try {
byte[] enc = Base64Decoder.decodeToBytes(encrypted);
byte[] result = decrypt(key, enc);
return new String(result);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/*
* 解密
*/
private static byte[] decrypt(String key, byte[] encrypted) throws Exception {
byte[] raw = getRawKey(key.getBytes());
SecretKeySpec skeySpec = new SecretKeySpec(raw, AES);
Cipher cipher = Cipher.getInstance(CBC_PKCS5_PADDING);
cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()]));
byte[] decrypted = cipher.doFinal(encrypted);
return decrypted;
}
}
RSA加密
RSA演算法是最流行的公鑰密碼演算法,使用長度可以變化的金鑰。RSA是第一個既能用於資料加密也能用於數字簽名的演算法。它在很多密碼協議中都有應用,如SSL和S/MIME。RSA演算法是基於大質數的因數分解的公匙體系。簡單的講,就是兩個很大的質數,一個作為公鑰,另一個作為私鑰,如用其中一個加密,則用另一個解密。金鑰長度從40到2048位可變,金鑰越長,加密效果越好,但加密解密的開銷也大。所以他在加密的速度上回小於AES等對稱加密。
使用:
//全域性變數
public static final String RSA = "RSA";// 非對稱加密金鑰演算法
public static final String ECB_PKCS1_PADDING = "RSA/ECB/PKCS1Padding";//加密填充方式
public static final int DEFAULT_KEY_SIZE = 2048;//祕鑰預設長度
public static final byte[] DEFAULT_SPLIT = "#PART#".getBytes(); // 當要加密的內容超過bufferSize,則採用partSplit進行分塊加密
public static final int DEFAULT_BUFFERSIZE = (DEFAULT_KEY_SIZE / 8) - 11;// 當前祕鑰支援加密的最大位元組數
/**
* 隨機生成RSA金鑰對
* @param keyLength 金鑰長度,範圍:512~2048 一般1024
*/
public static KeyPair generateRSAKeyPair(int keyLength) {
try {
KeyPairGenerator kpg = KeyPairGenerator.getInstance(RSA);
kpg.initialize(keyLength);
return kpg.genKeyPair();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
公鑰加密私鑰解密
/**
* 用公鑰對字串進行加密
* @param data 原文
*/
public static byte[] encryptByPublicKey(byte[] data, byte[] publicKey) throws Exception {
// 得到公鑰
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
KeyFactory kf = KeyFactory.getInstance(RSA);
PublicKey keyPublic = kf.generatePublic(keySpec);
// 加密資料
Cipher cp = Cipher.getInstance(ECB_PKCS1_PADDING);
cp.init(Cipher.ENCRYPT_MODE, keyPublic);
return cp.doFinal(data);
}
/**
* 使用私鑰進行解密
*/
public static byte[] decryptByPrivateKey(byte[] encrypted, byte[] privateKey) throws Exception {
// 得到私鑰
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey);
KeyFactory kf = KeyFactory.getInstance(RSA);
PrivateKey keyPrivate = kf.generatePrivate(keySpec);
// 解密資料
Cipher cp = Cipher.getInstance(ECB_PKCS1_PADDING);
cp.init(Cipher.DECRYPT_MODE, keyPrivate);
byte[] arr = cp.doFinal(encrypted);
return arr;
}
私鑰加密公鑰解密
/**
* 私鑰加密
* @param data 待加密資料
* @param privateKey 金鑰
* @return byte[] 加密資料
*/
public static byte[] encryptByPrivateKey(byte[] data, byte[] privateKey) throws Exception {
// 得到私鑰
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey);
KeyFactory kf = KeyFactory.getInstance(RSA);
PrivateKey keyPrivate = kf.generatePrivate(keySpec);
// 資料加密
Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING);
cipher.init(Cipher.ENCRYPT_MODE, keyPrivate);
return cipher.doFinal(data);
}
/**
* 公鑰解密
*
* @param data 待解密資料
* @param publicKey 金鑰
* @return byte[] 解密資料
*/
public static byte[] decryptByPublicKey(byte[] data, byte[] publicKey) throws Exception {
// 得到公鑰
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
KeyFactory kf = KeyFactory.getInstance(RSA);
PublicKey keyPublic = kf.generatePublic(keySpec);
// 資料解密
Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING);
cipher.init(Cipher.DECRYPT_MODE, keyPublic);
return cipher.doFinal(data);
}
RSA所遇問題
關於加密填充方式:之前以為上面這些操作就能實現rsa加解密,以為萬事大吉了,呵呵,這事還沒完,悲劇還是發生了,Android這邊加密過的資料,伺服器端死活解密不了,原來android系統的RSA實現是”RSA/None/NoPadding”,而標準JDK實現是”RSA/None/PKCS1Padding” ,這造成了在android機上加密後無法在伺服器上解密的原因,所以在實現的時候這個一定要注意。
實現分段加密:搞定了填充方式之後又自信的認為萬事大吉了,可是意外還是發生了,RSA非對稱加密內容長度有限制,1024位key的最多隻能加密127位資料,否則就會報錯(javax.crypto.IllegalBlockSizeException: Data must not be longer than 117 bytes) , RSA 是常用的非對稱加密演算法。最近使用時卻出現了“不正確的長度”的異常,研究發現是由於待加密的資料超長所致。RSA 演算法規定:待加密的位元組數不能超過金鑰的長度值除以 8 再減去 11(即:KeySize / 8 - 11),而加密後得到密文的位元組數,正好是金鑰的長度值除以 8(即:KeySize / 8)。
分段公鑰加密私鑰加密
/**
* 用公鑰對字串進行分段加密
*
*/
public static byte[] encryptByPublicKeyForSpilt(byte[] data, byte[] publicKey) throws Exception {
int dataLen = data.length;
if (dataLen <= DEFAULT_BUFFERSIZE) {
return encryptByPublicKey(data, publicKey);
}
List<Byte> allBytes = new ArrayList<Byte>(2048);
int bufIndex = 0;
int subDataLoop = 0;
byte[] buf = new byte[DEFAULT_BUFFERSIZE];
for (int i = 0; i < dataLen; i++) {
buf[bufIndex] = data[i];
if (++bufIndex == DEFAULT_BUFFERSIZE || i == dataLen - 1) {
subDataLoop++;
if (subDataLoop != 1) {
for (byte b : DEFAULT_SPLIT) {
allBytes.add(b);
}
}
byte[] encryptBytes = encryptByPublicKey(buf, publicKey);
for (byte b : encryptBytes) {
allBytes.add(b);
}
bufIndex = 0;
if (i == dataLen - 1) {
buf = null;
} else {
buf = new byte[Math.min(DEFAULT_BUFFERSIZE, dataLen - i - 1)];
}
}
}
byte[] bytes = new byte[allBytes.size()];
{
int i = 0;
for (Byte b : allBytes) {
bytes[i++] = b.byteValue();
}
}
return bytes;
}
/**
* 使用私鑰分段解密
*
*/
public static byte[] decryptByPrivateKeyForSpilt(byte[] encrypted, byte[] privateKey) throws Exception {
int splitLen = DEFAULT_SPLIT.length;
if (splitLen <= 0) {
return decryptByPrivateKey(encrypted, privateKey);
}
int dataLen = encrypted.length;
List<Byte> allBytes = new ArrayList<Byte>(1024);
int latestStartIndex = 0;
for (int i = 0; i < dataLen; i++) {
byte bt = encrypted[i];
boolean isMatchSplit = false;
if (i == dataLen - 1) {
// 到data的最後了
byte[] part = new byte[dataLen - latestStartIndex];
System.arraycopy(encrypted, latestStartIndex, part, 0, part.length);
byte[] decryptPart = decryptByPrivateKey(part, privateKey);
for (byte b : decryptPart) {
allBytes.add(b);
}
latestStartIndex = i + splitLen;
i = latestStartIndex - 1;
} else if (bt == DEFAULT_SPLIT[0]) {
// 這個是以split[0]開頭
if (splitLen > 1) {
if (i + splitLen < dataLen) {
// 沒有超出data的範圍
for (int j = 1; j < splitLen; j++) {
if (DEFAULT_SPLIT[j] != encrypted[i + j]) {
break;
}
if (j == splitLen - 1) {
// 驗證到split的最後一位,都沒有break,則表明已經確認是split段
isMatchSplit = true;
}
}
}
} else {
// split只有一位,則已經匹配了
isMatchSplit = true;
}
}
if (isMatchSplit) {
byte[] part = new byte[i - latestStartIndex];
System.arraycopy(encrypted, latestStartIndex, part, 0, part.length);
byte[] decryptPart = decryptByPrivateKey(part, privateKey);
for (byte b : decryptPart) {
allBytes.add(b);
}
latestStartIndex = i + splitLen;
i = latestStartIndex - 1;
}
}
byte[] bytes = new byte[allBytes.size()];
{
int i = 0;
for (Byte b : allBytes) {
bytes[i++] = b.byteValue();
}
}
return bytes;
}
分段私鑰加密公鑰加密
/**
* 私鑰分段加密
* @param data 要加密的原始資料
* @param privateKey 祕鑰
*/
public static byte[] encryptByPrivateKeyForSpilt(byte[] data, byte[] privateKey) throws Exception {
int dataLen = data.length;
if (dataLen <= DEFAULT_BUFFERSIZE) {
return encryptByPrivateKey(data, privateKey);
}
List<Byte> allBytes = new ArrayList<Byte>(2048);
int bufIndex = 0;
int subDataLoop = 0;
byte[] buf = new byte[DEFAULT_BUFFERSIZE];
for (int i = 0; i < dataLen; i++) {
buf[bufIndex] = data[i];
if (++bufIndex == DEFAULT_BUFFERSIZE || i == dataLen - 1) {
subDataLoop++;
if (subDataLoop != 1) {
for (byte b : DEFAULT_SPLIT) {
allBytes.add(b);
}
}
byte[] encryptBytes = encryptByPrivateKey(buf, privateKey);
for (byte b : encryptBytes) {
allBytes.add(b);
}
bufIndex = 0;
if (i == dataLen - 1) {
buf = null;
} else {
buf = new byte[Math.min(DEFAULT_BUFFERSIZE, dataLen - i - 1)];
}
}
}
byte[] bytes = new byte[allBytes.size()];
{
int i = 0;
for (Byte b : allBytes) {
bytes[i++] = b.byteValue();
}
}
return bytes;
}
/**
* 公鑰分段解密
*
* @param encrypted 待解密資料
* @param publicKey 金鑰
*/
public static byte[] decryptByPublicKeyForSpilt(byte[] encrypted, byte[] publicKey) throws Exception {
int splitLen = DEFAULT_SPLIT.length;
if (splitLen <= 0) {
return decryptByPublicKey(encrypted, publicKey);
}
int dataLen = encrypted.length;
List<Byte> allBytes = new ArrayList<Byte>(1024);
int latestStartIndex = 0;
for (int i = 0; i < dataLen; i++) {
byte bt = encrypted[i];
boolean isMatchSplit = false;
if (i == dataLen - 1) {
// 到data的最後了
byte[] part = new byte[dataLen - latestStartIndex];
System.arraycopy(encrypted, latestStartIndex, part, 0, part.length);
byte[] decryptPart = decryptByPublicKey(part, publicKey);
for (byte b : decryptPart) {
allBytes.add(b);
}
latestStartIndex = i + splitLen;
i = latestStartIndex - 1;
} else if (bt == DEFAULT_SPLIT[0]) {
// 這個是以split[0]開頭
if (splitLen > 1) {
if (i + splitLen < dataLen) {
// 沒有超出data的範圍
for (int j = 1; j < splitLen; j++) {
if (DEFAULT_SPLIT[j] != encrypted[i + j]) {
break;
}
if (j == splitLen - 1) {
// 驗證到split的最後一位,都沒有break,則表明已經確認是split段
isMatchSplit = true;
}
}
}
} else {
// split只有一位,則已經匹配了
isMatchSplit = true;
}
}
if (isMatchSplit) {
byte[] part = new byte[i - latestStartIndex];
System.arraycopy(encrypted, latestStartIndex, part, 0, part.length);
byte[] decryptPart = decryptByPublicKey(part, publicKey);
for (byte b : decryptPart) {
allBytes.add(b);
}
latestStartIndex = i + splitLen;
i = latestStartIndex - 1;
}
}
byte[] bytes = new byte[allBytes.size()];
{
int i = 0;
for (Byte b : allBytes) {
bytes[i++] = b.byteValue();
}
}
return bytes;
}
專案中使用
在實際開發中,不是太重要的資料用一種加密方式感覺就可以了。但是比較重要的資料建議用多種加密方式結合的方式,比如我用的RSA+AES加密。他主要解決了兩個問題:1.RSA加解密速度慢,不適合大量資料檔案加密 2.AES加密速度很快,但是安全性沒有RSA加密方式的安全。其主要思想就是服務端生成公鑰私鑰,並提供介面將公鑰給android端,android端生成AES祕鑰,並用AES祕鑰對大量資料進行加密(解決RSA加解密速度慢的問題),然後用呼叫介面拿到的RSA公鑰對自己生成AES祕鑰進行加密,客戶端將得到的祕鑰和通過AES加密的資料傳送給伺服器。(祕鑰可以放在請求頭中,資料放在請求體中,這個隨意了)。服務拿到你的祕鑰和資料後,用私鑰加密得到AES祕鑰,再通過祕鑰得到傳送的資料就好了。