Android 加解密類Cipher
近日在做一個關於簡訊及檔案加解密的小專案,查看了一些Android加解密方面的知識。關於加解密這部分以前完全沒有接觸過,所以網上亂翻了一天對於什麼DES,AES,RSA,BASE64,MD5之類的還是懵懵懂懂,這裡也就不再來說它們的原理了,實在是我自己也沒弄懂。寫這篇部落格時也就大致瞭解了一下Cipher類,並實現了一個簡單的AES編解碼工具類,當然是用的都是些最簡單的預設模式,更詳細的知識還有待進一步的學習。 此博文主要是複習記錄一下一天學習所得,加深印象。
Android的Cipher類其實是來自於JAVA的加密環境JCE,檢視官方的文件就可以看到: javax.crypto.Cipher。當然據說這個類也不在官方的正式JDK裡面,而且有些解密方法在Android中不可用(這部分不敢下結論,加密的東西我也剛剛開始瞭解)。下面就來具體看一下實現AES加密的過程吧。
Step1:實現一個簡單XML檔案做測試介面,實現一個EditText,兩個Button,一個TextView顯示結果:
Step2:Activity程式碼就不貼出來了,無非就是獲得上面4個控制元件,然後監聽加密、解密兩個按鈕執行加解密工具類的加解密方法。<EditText android:id="@+id/et_input" android:layout_width="match_parent" android:layout_height="wrap_content" /> <Button android:id="@+id/btn_encrypt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="加密"/> <Button android:id="@+id/btn_decrypt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="解密"/> <TextView android:id="@+id/tv_output" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
Step3:實現加解密工具類,這也是此篇博文的關鍵。下面來一步一步的分析:
先來看看官方給出的加密大致流程:Use a secure random number generator, , to initialize any cryptographic keys, . 這句話大致意思就是使用一個安全的隨機數來產生一個密匙。當然這個密匙是我們用來加密使用的。把這段話轉換成一段程式碼如下:
當我們通過上面的方法獲得密匙後,就可以使用Cypher提供的方法來進行加解密操作了。注意Cypher無法直接獲得例項,只能通過getInstance方法來獲得例項:private static byte[] getRawKey(byte[] seed) throws NoSuchAlgorithmException{ SecureRandom sr = SecureRandom.getInstance("SHA1PRNG"); // 獲得一個隨機數,傳入的引數為預設方式。 sr.setSeed(seed); // 設定一個種子,這個種子一般是使用者設定的密碼。也可以是其它某個固定的字串 KeyGenerator keyGen = KeyGenerator.getInstance("AES"); // 獲得一個key生成器(AES加密模式) keyGen.init(128, sr); // 設定密匙長度128位 SecretKey key = keyGen.generateKey(); // 獲得密匙 byte[] raw = key.getEncoded(); // 返回密匙的byte陣列供加解密使用 return raw; }
This class provides access to implementations of cryptographic ciphers for encryption and decryption. Cipher classes can not be instantiated directly, one has to call the Cipher's getInstance
method
with the name of a requested transformation, optionally with a provider. A transformation specifies an operation (or a set of operations) as a string in the form:
- "algorithm/mode/padding"or
- "algorithm"
private static byte[] encry(byte[] raw, byte[] input) throws Exception { // 加密
SecretKeySpec keySpec = new SecretKeySpec(raw, "AES"); // 根據上一步生成的密匙指定一個密匙(密匙二次加密?)
Cipher cipher = Cipher.getInstance("AES"); // 獲得Cypher例項物件
cipher.init(Cipher.ENCRYPT_MODE, keySpec); // 初始化模式為加密模式,並指定密匙
byte[] encode = cipher.doFinal(input); // 執行加密操作。 input為需要加密的byte陣列
return encode; // 返回加密後的密文(byte陣列)
}
private static byte[] decry(byte[] raw, byte[] encode) throws Exception{ // 解密
SecretKeySpec keySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, keySpec); // 解密的的方法差不多,只是這裡的模式不一樣
byte[] decode = cipher.doFinal(encode); // 加解密都通過doFinal方法來執行最終的實際操作
return decode;
}
最後再對上述兩個方法進行一次封裝,提供對外的介面即可完成此工具類的簡單功能。加解密工具要求實現字串明文輸入,字串密文輸出,下面來看看具體實現程式碼:
public static String decryptString(String seed,
byte[] encode)
throws Exception{
byte[] raw = getRawKey(seed.getBytes());
byte[] decode = decry(raw, encode);
return new String(decode);
}
public static byte[] encryptString(String seed, String input) throws Exception{
byte[] raw = getRawKey(seed.getBytes());
byte[] encode = encry(raw, input.getBytes());
//return new String(encode); // 這裡返回加密後的密文字串是有問題的,後面再解釋。
return encode;
}
上面程式碼中基本實現了需求,只不過加密時輸出的是byte陣列,解密時輸入也是byte陣列。當然這樣對稱設計在同一個應用裡是不會出現問題的,不過傳輸時大家都習慣將byte陣列轉成字串的格式,而在byte轉字串時會因為字元編碼的原因出現一些問題,下面就來看看如何來解決加解密使用字串密文異常問題。
大家都知道,計算機儲存字元使用ASCII碼形式,當出現中文等字元時將無法使用一個8位的字元來表示,國際通用標準UNICODE使用16位來表示,但不同語種已經實現了大量不同的編碼格式,而我們的加密演算法是針對byte陣列來進行的,加密後的byte陣列將無法使用某種字元編碼來轉成字串,所以這裡將加密後的byte陣列轉換成字串時將出現問題。
這裡再舉一個簡單的例子來說明會出現何種問題,大家可以使用下面這段程式碼來跟蹤一下具體實現效果:
byte[] b = -120;
String str = new String(b);
byte[] bytes = str.getBytes();
大家執行一下這段程式碼,看看bytes得到是什麼? 我想不用我說,現在應該都知道為什麼轉換會出現問題了吧。這對這種情況,我們需要在byte與String轉換時進行一個byte轉hex,以及hex轉byte的中轉操作。具體程式碼如下:
private static String bytesToHexString(byte[] src){
StringBuilder stringBuilder = new StringBuilder("");
if (src == null || src.length <= 0) {
return null;
}
for (int i = 0; i < src.length; i++) {
int v = src[i] & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
return stringBuilder.toString();
}
private static byte[] hexStringToBytes(String hexString) {
if (hexString == null || hexString.equals("")) {
return null;
}
hexString = hexString.toUpperCase();
int length = hexString.length() / 2;
char[] hexChars = hexString.toCharArray();
byte[] d = new byte[length];
for (int i = 0; i < length; i++) {
int pos = i * 2;
d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
}
return d;
}
private static byte charToByte(char c) {
return (byte) "0123456789ABCDEF".indexOf(c);
}
到這裡就完美解決了轉換問題,把上述前面封裝的兩個介面中標註紅色的部分分別進行轉換即可達到進出皆為String的目的。 AES演算法的簡單實現到這裡就完成了,關於加解密方面的知識後續還需要抽時間再好好補一下。