Android-加解密
參考:
- ofollow,noindex">https://mp.weixin.qq.com/s/UBwCpsK7kbPfmI4_PiJJCA
- https://mp.weixin.qq.com/s/hJJUbb6aLbxmNl3k91M7UQ
- https://www.jianshu.com/p/1a8837872ed0
今天說一說加解密,我們先了解一下相關的概念:
- 不可逆加密
- 可逆加密
從加密方式來說,加密分為可逆和不可逆加密,而可逆加密有具體分為:
- 演算法加密
- 對稱加密演算法
- 非對稱加密演算法
我們分別說說他們的區別和特性。
1.不可逆加密
不可逆加密演算法的特徵是加密過程中不需要使用金鑰,輸入明文後由系統直接經過加密演算法處理成密文,這種加密後的資料是無法被解密的,只有重新輸入明文,並再次經過同樣不可逆的加密演算法處理,得到相同的加密密文並被系統重新識別後,才能真正解密。
如資訊摘要(Message Digest)和安全雜湊(Secure Hash)演算法屬於此類,常見的演算法包括 MD5、SHA1、PBKDF2、bcrypt 等。
特點:

image.png
ok,那我們演示如何使用MD5和SHA進行加解密
// MD5加密 private static String toMd5(String str) { // 例項化一個指定摘要演算法為MD5的MessageDigest物件 MessageDigest algorithm; try { algorithm = MessageDigest.getInstance("MD5"); // 重置摘要以供再次使用 algorithm.reset(); // 使用bytes更新摘要 algorithm.update(str.getBytes()); // 使用指定的byte陣列對摘要進行最後更新,然後完成摘要計算 return toHexString(algorithm.digest(), ""); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return null; } // SHA加密 private static String toSHA(String str) { // 例項化一個指定摘要演算法為SHA的MessageDigest物件 MessageDigest algorithm; try { algorithm = MessageDigest.getInstance("SHA"); // 重置摘要以供再次使用 algorithm.reset(); // 使用bytes更新摘要 algorithm.update(str.getBytes()); // 使用指定的byte陣列對摘要進行最後更新,然後完成摘要計算 return toHexString(algorithm.digest(), ""); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return null; } // 將字串中的每個字元轉換為十六進位制 private static String toHexString(byte[] bytes, String separtor) { StringBuilder hexString = new StringBuilder(); for (byte b : bytes) { String hex = Integer.toHexString(0xff & b); if (hex.length() == 1) { hexString.append("0"); } hexString.append(hex).append(separtor); } return hexString.toString(); }
2.可逆加密
2.1演算法加密
基於演算法的加密演算法,也被稱為古典加密演算法,如 HTTP 認證中的 base64,是一種基於64個基本字元,加密後的內容只包含這64個字元,加密後長度會變大。它是最簡單的一種演算法,一般用於加密URL.
下圖為Base64編碼表

image.png
Base64加解密相關程式碼
// 需要引入包:java.util.Base64 // Base64加密 private static String encode(String str) { byte[] encodeBytes = Base64.getEncoder().encode(str.getBytes()); return new String(encodeBytes); } // Base64解密 private static String decode(String str) { byte[] decodeBytes = Base64.getDecoder().decode(str.getBytes()); return new String(decodeBytes); }
2.2對稱加密
對稱加密:加密和解密的金鑰一樣。常見的對稱加密演算法有 DES、3DES、AES。這三者的關係可以理解為迭代和替代。3DES是對DES的發展,AES是為了替代DES.
DES加解密相關程式碼
public class DESUtil { // 初始化向量 private static byte[] iv = { 'a', 'b', 'c', 'd', 'e', 1, 2, '*' }; // DES加密 // encryptText為原文 // encryptKey為密匙 private static String encryptDES(String encryptText, String encryptKey) throws Exception { // 例項化IvParameterSpec物件,使用指定的初始化向量 IvParameterSpec spec = new IvParameterSpec(iv); // 例項化SecretKeySpec類,根據位元組陣列來構造SecretKeySpec SecretKeySpec key = new SecretKeySpec(encryptKey.getBytes(), "DES"); // 建立密碼器 Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding"); // 用密碼初始化Cipher物件 cipher.init(Cipher.ENCRYPT_MODE, key, spec); // 執行加密操作 byte[] encryptData = cipher.doFinal(encryptText.getBytes()); // 返回加密後的資料 return Base64.getEncoder().encodeToString(encryptData); } // 解密 private static String decryptDES(String decryptString, String decryptKey) throws Exception { // 先使用Base64解密 byte[] base64byte = Base64.getDecoder().decode(decryptString); // 例項化IvParameterSpec物件,使用指定的初始化向量 IvParameterSpec spec = new IvParameterSpec(iv); // 例項化SecretKeySpec類,根據位元組陣列來構造SecretKeySpec SecretKeySpec key = new SecretKeySpec(decryptKey.getBytes(), "DES"); // 建立密碼器 Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding"); // 用密碼初始化Cipher物件 cipher.init(Cipher.DECRYPT_MODE, key, spec); // 獲取解密後的資料 byte decryptedData[] = cipher.doFinal(base64byte); // 將解密後資料轉換為字串輸出 return new String(decryptedData); } }
AES加解密相關程式碼
public class AESUtil { // 採用對稱分組密碼體制,金鑰長度的最少支援為128、192、256 String key = "abcdefghijklmnop"; // 初始化向量引數,AES 為16bytes. DES 為8bytes, 16*8=128 String initVector = "0000000000000000"; IvParameterSpec iv; SecretKeySpec skeySpec; Cipher cipher; private static class HOLDER { private static AESUtil instance = new AESUtil(); } public static AESUtil getInstance() { return HOLDER.instance; } private AESUtil() { try { iv = new IvParameterSpec(initVector.getBytes("UTF-8")); skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES"); // 這是CBC模式 // cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING"); // 預設就是ECB模式 cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } public String encrypt(String value) { try { // CBC模式需要傳入向量,ECB模式不需要 // cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv); cipher.init(Cipher.ENCRYPT_MODE, skeySpec); byte[] encrypted = cipher.doFinal(value.getBytes()); return Base64.encodeToString(encrypted, Base64.DEFAULT); } catch (Exception e) { e.printStackTrace(); } return null; } public String decrypt(String encrypted) { try { // CBC模式需要傳入向量,ECB模式不需要 // cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv); cipher.init(Cipher.DECRYPT_MODE, skeySpec); byte[] original = cipher.doFinal(Base64.decode(encrypted, Base64.DEFAULT)); return new String(original); } catch (Exception ex) { ex.printStackTrace(); } return null; }
2.3非對稱加密
非對稱加密:加密和解密的金鑰不同,通常是公鑰加密私鑰解密,當然也可以私鑰加密公鑰解密,公鑰通常用來對內容加密,而私鑰既可以解密也可以用來確定是否是對應的公鑰加的密,防止他人用錯誤的公鑰進行加密。
非對稱加密中另外兩個重要的概念是公鑰和私鑰。公鑰對外公開,任何人均可持有和使用;私鑰自行保管,其安全性是通訊安危的關鍵。常見的演算法有 RSA、DH(Diffie-Hellman)、橢圓曲線演算法(Elliptic curve cryptography,ECC)。
私鑰和公鑰的作用一般分為兩種:
公鑰加密,私鑰解密,主要用於通訊;
私鑰加密(簽名),公鑰解密(驗證),主要用於數字簽名。
RSA演算法相關程式碼
public class RSAUtil { public static final String RSA = "RSA"; public static final String ECB_PKCS1_PADDING = "RSA/ECB/PKCS1Padding"; // 祕鑰預設長度 public static final int DEFAULT_KEY_SIZE = 2048; // 當要加密的內容超過bufferSize,則採用partSplit進行分塊加密 public static final byte[] DEFAULT_SPLIT = "#PART#".getBytes(); // 當前祕鑰支援加密的最大位元組數 public static final int DEFAULT_BUFFERSIZE = (DEFAULT_KEY_SIZE / 8) - 11; // 隨機生成RSA金鑰對,金鑰長度,範圍:512~2048 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待加密資料 * @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); } // 使用私鑰進行解密 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; } // 用公鑰對字串進行加密 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); } /** * 公鑰解密 * @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); } // 以下開始分段解密 // 使用私鑰分段解密 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; } // 私鑰分段加密 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; } // 用公鑰對字串進行分段加密 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[] 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; } }
3.數字簽名
數字簽名的出現一個很重要的作用是解決私鑰加密檔案過大,耗時過長。同樣公鑰解密的過程中也很耗時。
數字簽名我們來舉一個例子:
第一步:.故事主人公A要給B傳送一個檔案,他首先用雜湊演算法對檔案進行加密得到雜湊值,稱之為摘要,取名HashA。
第二步:對生成的雜湊值進行私鑰加密,稱之為數字簽名。
第三步:A將數字簽名和檔案一起傳送給B。
第四步:B對數字簽名進行公鑰解密得到Hash值即摘要,如果成功表示來自A。
第五步:B和A一樣對正文進行摘要得到Hash值,取名HashB,對比同A的HaahA.如果一致表示檔案沒有被篡改。
總結:數字簽名可以保證檔案的來源(即檔案來自於B)和完整性.
4.數字證書
說到數字證書,我們還需要拿上面的例子,說明一下數字簽名的侷限性。我們都知道公鑰通常是公開的儲存在本地的,如果此時有個C,在B的本地將A的公鑰替換為自己的公鑰,同時用自己的私鑰加密資料傳遞給B,這樣B是沒有辦法辨別的。
為了解決這個問題,我們引入了CA認證,也就是A拿著自己的公鑰去CA中心做認證,證書中心用自己的私鑰,對A的公鑰和一些相關資訊一起加密,生成"數字證書"(Digital Certificate)。然後將之前的數字簽名,正文內容,數字證書一起傳送給B。
B這邊怎麼做呢?B用CA給的公鑰先解密數字證書得到A的公鑰(注意之前公鑰可能儲存在B的本地中,現在改為網路傳輸了),然後用A的公鑰解密數字簽名,在解密的正文和傳遞的正文作比較。
-
說了這麼多,CA是什麼呢?
CA證書就是電子商務認證授權機構,也稱為電子商務認證中心,是負責發放和管理 數字證書 的權威機構,並作為電子商務交易中受信任的第三方,承擔公鑰體系中公鑰的合法性檢驗的責任。
證書的內容包括:電子簽證機關的資訊、公鑰使用者資訊、公鑰、權威機構的簽字和有效期等等。目前,證書的格式和驗證方法普遍遵循X.509 國際標準。
所以說CA具有可靠性,也就是說CA生成的數字證書也具有可靠性,如果數字證書被中途篡改,是無法被CA的公鑰解密的
-
要是有多個人要給B發郵件,難道B要儲存1萬份不同的CA公鑰嗎?
答案:不需要,CA認證中心給可以給B一份“根證書”,裡面儲存CA公鑰來驗證所有CA分中心頒發的數字證書。CA中心是分叉樹結構,類似於公安部->省公安廳->市級派出所,不管A從哪個CA分支機構申請的證書,B只要預存根證書就可以驗證下級證書可靠性。
關於加解密,主要內容就這麼多。