Android安全加密專題文章索引
- Android安全加密:對稱式加密
- Android安全加密:非對稱式加密
- Android安全加密:訊息摘要Message Digest
- Android安全加密:數位簽章和數位憑證
- Android安全加密:Https編程
以上學習所有內容,對稱式加密、非對稱式加密、訊息摘要、數位簽章等知識都是為了理解數位憑證工作原理而作為一個預備知識。數位憑證是密碼學裡的終極武器,是人類幾千年歷史總結的智慧的結晶,只有在明白了數位憑證工作原理後,才能理解Https 協議的安全通訊機制。最終才能在SSL 開發過程中得心應手。
另外,對稱式加密和訊息摘要這兩個知識點是可以單獨拿來使用的。
數位憑證使用到了以上學習的所有知識
- 對稱式加密與非對稱式加密結合使用實現了秘鑰交換,之後通訊雙方使用該秘鑰進行對稱式加密通訊。
- 訊息摘要與非對稱式加密實現了數位簽章,根憑證機構對目標認證進行簽名,在校正的時候,根憑證用公開金鑰對其進行校正。若校正成功,則說明該認證是受信任的。
- Keytool 工具可以建立認證,之後交給根憑證機構認證後直接使用自我簽署憑證,還可以輸出認證的RFC格式資訊等。
- 數位簽章技術實現了身份認證與資料完整性保證。
- 加密技術保證了資料的保密性,訊息摘要演算法保證了資料的完整性,對稱式加密的高效保證了資料處理的可靠性,數位簽章技術保證了操作的不可否認性。
通過以上內容的學習,我們要能掌握以下知識點:
- 基礎知識:bit 位、位元組、字元、字元編碼、進位轉換、io
- 知道怎樣在實際開發裡怎樣使用對稱式加密解決問題
- 知道對稱式加密、非對稱式加密、訊息摘要、數位簽章、數位憑證是為瞭解決什麼問題而出現的
- 瞭解SSL 通訊流程
- 實際開發裡怎樣請求Https 的介面
凱撒密碼
1. 介紹
凱撒密碼作為一種最為古老的對稱式加密體制,在古羅馬的時候都已經很流行,他的基本思想是:通過把字母移動一定的位元來實現加密和解密。明文中的所有字母都在字母表上向後(或向前)按照一個固定數目進行位移後被替換成密文。例如,當位移量是3 的時候,所有的字母A 將被替換成D,B 變成E,由此可見,位元就是凱撒密碼加密和解密的密鑰。
例如:字串”ABC”的每個字元都右移3 位則變成”DEF”,解密的時候”DEF”的每個字元左移3 位即能還原,如下圖所示:
2. 準備知識
//字元轉換成ASCII 碼數值 char charA = 'a'; int intA = charA; //char 強轉為int 即得到對應的ASCII 碼值,'a'的值為97//ASCII 碼值轉成charint intA = 97;//97 對應的ASCII 碼'a'char charA = (char) intA; //int 值強轉為char 即得到對應的ASCII 字元,即'a'
3. 凱撒密碼的簡單代碼實現
/** * 加密 * @param input 資料來源(需要加密的資料) * @param key 秘鑰,即位移量 * @return 返回加密後的資料 */ public static String encrypt(String input, int key) { //得到字串裡的每一個字元 char[] array = input.toCharArray(); for (int i = 0; i < array.length; ++i) { //字元轉換成ASCII 碼值 int ascii = array[i]; //字元位移,例如a->b ascii = ascii + key; //ASCII 碼值轉換為char char newChar = (char) ascii; //替換原有字元 array[i] = newChar; //以上4 行代碼可以簡寫為一行 //array[i] = (char) (array[i] + key); } //字元數群組轉換成String return new String(array); } /** * 解密 * @param input 資料來源(被加密後的資料) * @param key 秘鑰,即位移量 * @return 返回解密後的資料 */ public static String decrypt(String input, int key) { //得到字串裡的每一個字元 char[] array = input.toCharArray(); for (int i = 0; i < array.length; ++i) { //字元轉換成ASCII 碼值 int ascii = array[i]; //恢複字元位移,例如b->a ascii = ascii - key; //ASCII 碼值轉換為char char newChar = (char) ascii; //替換原有字元 array[i] = newChar; //以上4 行代碼可以簡寫為一行 //array[i] = (char) (array[i] - key); } //字元數群組轉換成String return new String(array); }
代碼輸出結果:
4. 破解凱撒密碼:頻率分析法
凱撒密碼加密強度太低,只需要用頻度分析法即可破解。
在任何一種書面語言中,不同的字母或字母組合出現的頻率各不相同。而且,對於以這種語言書寫的任意一段文本,都具有大致相同的特徵字母分布。比如,在英語中,字母E 出現的頻率很高,而X 則出現得較少。
英語文本中典型的字母分布情況如下圖所示:
5. 破解流程
- 統計密文裡出現次數最多的字元,例如出現次數最多的字元是是'h'。
- 計算字元'h'到'e'的位移量,值為3,則表示原文位移了3 個位置。
- 將密文所有字元恢複位移3 個位置。
注意點:統計密文裡出現次數最多的字元時,需多統計幾個備選,因為最多的可能是空格或者其他字元,例如下圖出現次數最多的字元'#'是空格加密後的字元,'h'才是'e'位移後的值。
解密時要多幾次嘗試,因為不一定出現次數最多的字元就是我們想要的目標字元,如下圖,第二次解密的結果才是正確的。
/** * 頻率分析法破解凱撒密碼 */public class FrequencyAnalysis { //英文裡出現次數最多的字元 private static final char MAGIC_CHAR = 'e'; //破解產生的最大檔案數 private static final int DE_MAX_FILE = 4; public static void main(String[] args) throws Exception { //測試1,統計字元個數 //printCharCount("article1_en.txt"); //加密檔案 //int key = 3; //encryptFile("article1.txt", "article1_en.txt", key); //讀取加密後的檔案 String artile = file2String("article1_en.txt"); //解密(會產生多個備選檔案) decryptCaesarCode(artile, "article1_de.txt"); } public static void printCharCount(String path) throws IOException{ String data = file2String(path); List<Entry<Character, Integer>> mapList = getMaxCountChar(data); for (Entry<Character, Integer> entry : mapList) { //輸出前幾位的統計資訊 System.out.println("字元'" + entry.getKey() + "'出現" + entry.getValue() + "次"); } } public static void encryptFile(String srcFile, String destFile, int key) throws IOException { String artile = file2String(srcFile); //加密檔案 String encryptData = MyEncrypt.encrypt(artile, key); //儲存加密後的檔案 string2File(encryptData, destFile); } /** * 破解凱撒密碼 * @param input 資料來源 * @return 返回解密後的資料 */ public static void decryptCaesarCode(String input, String destPath) { int deCount = 0;//當前解密產生的備選檔案數 //擷取出現頻率最高的字元資訊(出現次數越多越靠前) List<Entry<Character, Integer>> mapList = getMaxCountChar(input); for (Entry<Character, Integer> entry : mapList) { //限制解密檔案備選數 if (deCount >= DE_MAX_FILE) { break; } //輸出前幾位的統計資訊 System.out.println("字元'" + entry.getKey() + "'出現" + entry.getValue() + "次"); ++deCount; //出現次數最高的字元跟MAGIC_CHAR的位移量即為秘鑰 int key = entry.getKey() - MAGIC_CHAR; System.out.println("猜測key = " + key + ", 解密產生第" + deCount + "個備選檔案" + "\n"); String decrypt = MyEncrypt.decrypt(input, key); String fileName = "de_" + deCount + destPath; string2File(decrypt, fileName); } } //統計String裡出現最多的字元 public static List<Entry<Character, Integer>> getMaxCountChar(String data) { Map<Character, Integer> map = new HashMap<Character, Integer>(); char[] array = data.toCharArray(); for (char c : array) { if(!map.containsKey(c)) { map.put(c, 1); }else{ Integer count = map.get(c); map.put(c, count + 1); } } //輸出統計資訊 /*for (Entry<Character, Integer> entry : map.entrySet()) { System.out.println(entry.getKey() + "出現" + entry.getValue() + "次"); }*/ //擷取擷取最大值 int maxCount = 0; for (Entry<Character, Integer> entry : map.entrySet()) { //不統計空格 if (/*entry.getKey() != ' ' && */entry.getValue() > maxCount) { maxCount = entry.getValue(); } } //map轉換成list便於排序 List<Entry<Character, Integer>> mapList = new ArrayList<Map.Entry<Character,Integer>>(map.entrySet()); //根據字元出現次數排序 Collections.sort(mapList, new Comparator<Entry<Character, Integer>>(){ @Override public int compare(Entry<Character, Integer> o1, Entry<Character, Integer> o2) { return o2.getValue().compareTo(o1.getValue()); } }); return mapList; } public static String file2String(String path) throws IOException { FileReader reader = new FileReader(new File(path)); char[] buffer = new char[1024]; int len = -1; StringBuffer sb = new StringBuffer(); while ((len = reader.read(buffer)) != -1) { sb.append(buffer, 0, len); } return sb.toString(); } public static void string2File(String data, String path){ FileWriter writer = null; try { writer = new FileWriter(new File(path)); writer.write(data); } catch (Exception e) { e.printStackTrace(); }finally { if (writer != null) { try { writer.close(); } catch (IOException e) { e.printStackTrace(); } } } }}
對稱式加密
介紹
加密和解密都使用同一把秘鑰,這種加密方法稱為對稱式加密,也稱為單祕密金鑰加密。
簡單理解為:加密解密都是同一把鑰匙。
凱撒密碼就屬於對稱式加密,他的字元位移量即為秘鑰。
對稱式加密常用演算法
AES、DES、3DES、TDEA、Blowfish、RC2、RC4、RC5、IDEA、SKIPJACK 等。
DES:全稱為Data Encryption Standard,即資料加密標準,是一種使用祕密金鑰加密的塊演算法,1976 年被美國聯邦政府的國家標準局確定為聯邦資料處理標準(FIPS),隨後在國際上廣泛流傳開來。
3DES:也叫Triple DES,是三重資料加密演算法(TDEA,Triple Data Encryption Algorithm)塊密碼的通稱。
它相當於是對每個資料區塊應用三次DES 密碼編譯演算法。由於電腦運算能力的增強,原版DES 密碼的密鑰長度變得容易被暴力破解;3DES 即是設計用來提供一種相對簡單的方法,即通過增加DES 的密鑰長度來避免類似的攻擊,而不是設計一種全新的塊密碼演算法。
AES: 進階加密標準(英語:Advanced Encryption Standard,縮寫:AES),在密碼學中又稱Rijndael 加密法,是美國聯邦政府採用的一種區塊加密標準。這個標準用來替代原先的DES,已經被多方分析且廣為全世界所使用。經過五年的甄選流程,進階加密標準由美國國家標準與技術研究院(NIST)於2001 年11 月26 日發佈於FIPS PUB 197,並在2002 年5 月26 日成為有效標準。2006 年,進階加密標準已然成為對稱金鑰密碼編譯中最流行的演算法之一。
DES 演算法簡介
DES 加密原理(對位元位進行操作,交換位置,異或等等,無需詳細瞭解)
準備知識
Bit 是電腦最小的傳輸單位。以0 或1 來表示位元位的值
例如數字3 對應的位元據為:00000011
程式碼範例
int i = 97; String bit = Integer.toBinaryString(i); //輸出:97 對應的位元據為: 1100001 System.out.println(i + "對應的位元據為: " + bit);
Byte 與Bit 區別
資料存放區是以“位元組”(Byte)為單位,資料轉送是以大多是以“位”(bit,又名“位元”)為單位,一個位就代表一個0 或1(即二進位),每8 個位(bit,簡寫為b)組成一個位元組(Byte,簡寫為B),是最小一級的資訊單位。
Byte 的取值範圍:
//byte 的取值範圍:-128 到127System.out.println(Byte.MIN_VALUE + "到" + Byte.MAX_VALUE);
即10000000 到01111111 之間,一個位元組佔8 個位元位
二進位轉十進位圖示:
任何字串都可以轉換為位元組數組
String data = "1234abcd";
byte[] bytes = data.getBytes();//內容為:49 50 51 52 97 98 99 100
上面資料49 50 51 52 97 98 99 100 對應的位元據(即位元位為):
00110001
00110010
00110011
00110100
01100001
01100010
01100011
01100100
將他們間距調大一點,可看做一個矩陣:
之後可對他們進行各種操作,例如交換位置、分割、異或運算等,常見的加密方式就是這樣操作位元位的,例如下圖的IP 置換以及S-Box 操作都是常見加密的一些方式:
IP 置換:
S-BOX 置換:
DES 加密過程圖解(流程很複雜,只需要知道內部是操作位元位即可):
對稱式加密應用情境
- 本機資料加密(例如加密android 裡SharedPreferences 裡面的某些敏感性資料)
- 網路傳輸:登入介面post 請求參數加密{username=lisi,pwd=oJYa4i9VASRoxVLh75wPCg==}
- 加密使用者登入結果資訊並序列化到本地磁碟(將user 對象序列化到本地磁碟,下次登入時還原序列化到記憶體裡)
- 網頁互動資料加密(即後面學到的Https)
DES 演算法代碼實現
//1,得到cipher 對象(可翻譯為密碼器或密碼系統) Cipher cipher = Cipher.getInstance("DES"); //2,建立秘鑰 SecretKey key = KeyGenerator.getInstance("DES").generateKey(); //3,設定作業模式(加密/解密) cipher.init(Cipher.ENCRYPT_MODE, key); //4,執行操作 byte[] result = cipher.doFinal("黑馬".getBytes());
AES 演算法代碼實現
用法同上,只需把”DES”參數換成”AES”即可。
使用Base 64 編碼加密後的結果
byte[] result = cipher.doFinal("黑馬".getBytes());
System.out.println(new String(result));
輸出結果:
加密後的結果是位元組數組,這些被加密後的位元組在碼錶(例如UTF-8 碼錶)上找不到對應字元,會出現亂碼,當亂碼字串再次轉換為位元組數組時,長度會變化,導致解密失敗,所以轉換後的資料是不安全的。
使用Base64 對位元組數組進行編碼,任何位元組都能映射成對應的Base64 字元,之後能恢複到位元組數組,利於加密後資料的儲存於傳輸,所以轉換是安全的。同樣,位元組數群組轉換成16 進位字串也是安全的。
密文轉換成Base 64 編碼後的輸出結果:
密文轉換成16 進位編碼後的輸出結果:
Java 裡沒有直接提供Base64 以及位元組數組轉16 進位的Api,開發中一般是自己手寫或直接使用第三方提供的成熟穩定的工具類(例如apache 的commons-codec)。
Base64 字元對應表
對稱式加密的具體應用方式
1. 產生秘鑰並儲存到硬碟上,以後讀取該秘鑰進行加密解密操作,實際開發中用得比較少
//產生隨機秘鑰SecretKey secretKey = KeyGenerator.getInstance("AES").generateKey();//序列化秘鑰到磁碟上FileOutputStream fos = new FileOutputStream(new File("heima.key"));ObjectOutputStream oos = new ObjectOutputStream(fos);oos.writeObject(secretKey);//從磁碟裡讀取秘鑰FileInputStream fis = new FileInputStream(new File("heima.key"));ObjectInputStream ois = new ObjectInputStream(fis);Key key = (Key) ois.readObject();
2. 使用自訂秘鑰(秘鑰寫在代碼裡)
//建立密鑰寫法1KeySpec keySpec = new DESKeySpec(key.getBytes());SecretKey secretKey = SecretKeyFactory.getInstance(ALGORITHM).generateSecret(keySpec);//建立密鑰寫法2//SecretKey secretKey = new SecretKeySpec(key.getBytes(), KEY_ALGORITHM);Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);cipher.init(Cipher.DECRYPT_MODE, secretKey);//得到key 後,後續代碼就是Cipher 的寫法,此處省略...
注意事項
把秘鑰寫在代碼裡有一定風險,當別人反編譯代碼的時候,可能會看到秘鑰,Android 開發裡建議用JNI 把秘鑰值寫到C 代碼裡,甚至拆分成幾份,最後再組合成真正的秘鑰
演算法/工作模式/填充模式
初始化cipher 對象時,參數可以直接傳演算法名:例如:
Cipher c = Cipher.getInstance("DES");
也可以指定更詳細的參數,格式:”algorithm/mode/padding” ,即”演算法/工作模式/填充模式”
Cipher c = Cipher.getInstance("DES/CBC/PKCS5Padding");
密碼塊工作模式
塊密碼工作模式(Block cipher mode of operation),是對於按塊處理密碼的加密方式的一種擴充,不僅僅適用於AES,包括DES, RSA 等加密方法同樣適用。
填充模式
填充(Padding),是對需要按塊處理的資料,當資料長度不符合塊處理需求時,按照一定方法填充滿塊長的一種規則。
具體代碼:
//秘鑰演算法private static final String KEY_ALGORITHM = "DES";//密碼編譯演算法:algorithm/mode/padding 演算法/工作模式/填充模式private static final String CIPHER_ALGORITHM = "DES/ECB/PKCS5Padding";//秘鑰private static final String KEY = "12345678";//DES 秘鑰長度必須是8 位或以上//private static final String KEY = "1234567890123456";//AES 秘鑰長度必須是16 位//初始化秘鑰SecretKey secretKey = new SecretKeySpec(KEY.getBytes(), KEY_ALGORITHM);Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);//加密cipher.init(Cipher.ENCRYPT_MODE, secretKey);byte[] result = cipher.doFinal(input.getBytes());
注意:AES、DES 在CBC 操作模式下需要iv 參數
//AES、DES 在CBC 操作模式下需要iv 參數IvParameterSpec iv = new IvParameterSpec(key.getBytes());//加密cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
總結
DES 安全度在現代已經不夠高,後來又出現的3DES 演算法強度提高了很多,但是其執行效率低下,AES演算法加密強度大,執行效率高,使用簡單,實際開發中建議選擇AES 演算法。實際android 開發中可以用對稱式加密(例如選擇AES 演算法)來解決很多問題,例如:
- 做一個管理密碼的app,我們在不同的網站裡使用不同帳號密碼,很難記住,想做個app 統一管理,但是帳號密碼儲存在手機裡,一旦丟失了容易造成安全隱患,所以需要一種密碼編譯演算法,將帳號密碼資訊加密起來保管,這時候如果使用對稱式加密演算法,將資料進行加密,秘鑰我們自己記在心裡,只需要記住一個密碼。需要的時候可以還原資訊。
- android 裡需要把一些敏感性資料儲存到SharedPrefrence 裡的時候,也可以使用對稱式加密,這樣可以在需要的時候還原。
- 請求網路介面的時候,我們需要上傳一些敏感性資料,同樣也可以使用對稱式加密,服務端使用同樣的演算法就可以解密。或者服務端需要給用戶端傳遞資料,同樣也可以先加密,然後用戶端使用同樣演算法解密.