一、AES演算法的基本概念
AES是進階加密標準(Advanced Encryption Standard)的簡稱,AES有如下三個特點。
1 AES是對稱式加密演算法
對稱式加密的概念,百度百科是這樣解釋的:在對稱式加密演算法中,資料發信方將明文(未經處理資料)和加密金鑰一起經過特殊密碼編譯演算法處理後,使其變成複雜的加密密文發送出去。收信方收到密文後,若想解讀原文,則需要使用加密用過的密鑰及相同演算法的逆演算法對密文進行解密,才能使其恢複成可讀明文。在對稱式加密演算法中,使用的密鑰只有一個,發收信雙方都使用這個金鑰組資料進行加密和解密,這就要求解密方事先必須知道加密金鑰。關鍵點有兩個:1、加密方、解密方使用同一個密鑰,2、密碼編譯演算法與解密演算法互為逆演算法。舉例說明:123456-->234567的加密金鑰就是1,密碼編譯演算法是每位+;234567-->123456的解密密鑰也是1,解密演算法是每位-;其中密碼編譯演算法(+)和解密演算法(-)互為逆演算法,這種密碼編譯演算法就稱作對稱式加密。
2、AES屬於塊加密
AES在加密前需要把明文分為固定長度的若干塊,然後對每塊明文進行加密,最後再拼成一個完成的加密字串。塊加密要填充滿最後一塊,加密方、解密方要使用相同的padding填充方式。
3、AES密鑰、初始向量、加密模式、填充方式
我們常說的AES-128、AES-192、AES-256三種加密方式,數字表示的是密鑰長度,比如AES-128說的是密鑰是128位,也就是16個位元組長度的字串。JDK目前只支援AES-128加密,也就是傳入的密鑰必須是長度為16的字串。
初始向量Iv(Initialization Vector),使用除ECB以外的其他加密模式均需要傳入一個初始向量,其大小與塊大小相等,AES塊大小是128bit,所以Iv的長度是16位元組,初始向量可以加強演算法強度。
加密模式(Cipher Mode)有CBC、ECB、CTR、OFB、CFB五種。
填充方式(Padding)決定了最後的一個塊需要填充的內容,填充方式有PKCS5Padding、PKCS7Padding、NOPADDING三種,但是JDK只提供了PKCS5Padding、NOPADDING兩種,填充方式為PKCS5Padding時,最後一個塊需要填充χ個位元組,填充的值就是χ;填充方式為NOPADDING時,最後的一個塊填充的內容由程式員自己決定,通常填充0。
二、AES演算法的Java實現
package com.xi.liuliu.topnews.utils;import android.util.Log;import java.io.UnsupportedEncodingException;import javax.crypto.Cipher;import javax.crypto.spec.IvParameterSpec;import javax.crypto.spec.SecretKeySpec;/** * Created by zhangxiaobei on 2017/1/16. */public class AesEncryptUtil { private static final String TAG = "AesEncryptUtil"; private static final String UTF8 = "UTF-8"; private static final String AES = "AES"; private static final String AES_CBC_PKCS5_PADDING = "AES/CBC/PKCS5Padding"; private static final String AES_CBC_NO_PADDING = "AES/CBC/NoPadding"; /** * JDK只支援AES-128加密,也就是密鑰長度必須是128bit;參數為密鑰key,key的長度小於16字元時用"0"補充,key長度大於16字元時截取前16位 **/ private static SecretKeySpec create128BitsKey(String key) { if (key == null) { key = ""; } byte[] data = null; StringBuffer buffer = new StringBuffer(16); buffer.append(key); //小於16後面補0 while (buffer.length() < 16) { buffer.append("0"); } //大於16,截取前16個字元 if (buffer.length() > 16) { buffer.setLength(16); } try { data = buffer.toString().getBytes(UTF8); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return new SecretKeySpec(data, AES); } /** * 建立128位的位移量,iv的長度小於16時後面補0,大於16,截取前16個字元; * * @param iv * @return */ private static IvParameterSpec create128BitsIV(String iv) { if (iv == null) { iv = ""; } byte[] data = null; StringBuffer buffer = new StringBuffer(16); buffer.append(iv); while (buffer.length() < 16) { buffer.append("0"); } if (buffer.length() > 16) { buffer.setLength(16); } try { data = buffer.toString().getBytes(UTF8); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return new IvParameterSpec(data); } /** * 填充方式為Pkcs5Padding時,最後一個塊需要填充χ個位元組,填充的值就是χ,也就是填充內容由JDK確定 * * @param srcContent * @param password * @param iv * @return */ public static byte[] aesCbcPkcs5PaddingEncrypt(byte[] srcContent, String password, String iv) { SecretKeySpec key = create128BitsKey(password); IvParameterSpec ivParameterSpec = create128BitsIV(iv); try { Cipher cipher = Cipher.getInstance(AES_CBC_PKCS5_PADDING); cipher.init(Cipher.ENCRYPT_MODE, key, ivParameterSpec); byte[] encryptedContent = cipher.doFinal(srcContent); return encryptedContent; } catch (Exception e) { e.printStackTrace(); } return null; } public static byte[] aesCbcPkcs5PaddingDecrypt(byte[] encryptedContent, String password, String iv) { SecretKeySpec key = create128BitsKey(password); IvParameterSpec ivParameterSpec = create128BitsIV(iv); try { Cipher cipher = Cipher.getInstance(AES_CBC_PKCS5_PADDING); cipher.init(Cipher.DECRYPT_MODE, key, ivParameterSpec); byte[] decryptedContent = cipher.doFinal(encryptedContent); return decryptedContent; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 填充方式為NoPadding時,最後一個塊的填充內容由程式員確定,通常為0. * AES/CBC/NoPadding加密的明文長度必須是16的整數倍,明文長度不滿足16時,程式員要擴充到16的整數倍 * * @param sSrc * @param aesKey * @param aesIV * @return */ public static byte[] aesCbcNoPaddingEncrypt(byte[] sSrc, String aesKey, String aesIV) { //加密的資料長度不是16的整數倍時,未經處理資料後面補0,直到長度滿足16的整數倍 int len = sSrc.length; //計算補0後的長度 while (len % 16 != 0) len++; byte[] result = new byte[len]; //在最後補0 for (int i = 0; i < len; ++i) { if (i < sSrc.length) { result[i] = sSrc[i]; } else { //填充字元'a' //result[i] = 'a'; result[i] = 0; } } SecretKeySpec skeySpec = create128BitsKey(aesKey); //使用CBC模式,需要一個初始向量iv,可增加密碼編譯演算法的強度 IvParameterSpec iv = create128BitsIV(aesIV); Cipher cipher = null; try { //演算法/模式/補碼方式 cipher = Cipher.getInstance(AES_CBC_NO_PADDING); cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv); } catch (Exception e) { e.printStackTrace(); Log.i(TAG, "aesCbcNoPaddingEncrypt Exception"); } byte[] encrypted = null; try { encrypted = cipher.doFinal(result); } catch (Exception e) { e.printStackTrace(); Log.i(TAG, "aesCbcNoPaddingEncrypt Exception"); } return encrypted; } public static byte[] aesCbcNoPaddingDecrypt(byte[] sSrc, String aesKey, String aesIV) { SecretKeySpec skeySpec = create128BitsKey(aesKey); IvParameterSpec iv = create128BitsIV(aesIV); try { Cipher cipher = Cipher.getInstance(AES_CBC_NO_PADDING); cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv); byte[] decryptContent = cipher.doFinal(sSrc); return decryptContent; } catch (Exception ex) { Log.i(TAG, "aesCbcNoPaddingDecrypt Exception"); } return null; }}
三、Base64轉碼概念及實現
AES加密後返回的是byte[ ],列印出來會顯示很多亂碼,為了提高可讀性,一般將byte[]轉為base64編碼,base64不是密碼編譯演算法,是一種編碼方式,base64編碼方式包括A-Z,a-z,0-9,+,/ 這些字元,編碼時會把byte[ ]轉成含有上述字元的字串。Base64Encoder Java實現:
package com.xi.liuliu.topnews.utils;/** * Created by zhangxiaobei on 2017/1/9. */public class Base64Encoder { private static final byte[] encodingTable = { (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) '+', (byte) '/' }; private static final byte[] decodingTable; static { decodingTable = new byte[128]; for (int i = 0; i < 128; i++) { decodingTable[i] = (byte) -1; } for (int i = 'A'; i <= 'Z'; i++) { decodingTable[i] = (byte) (i - 'A'); } for (int i = 'a'; i <= 'z'; i++) { decodingTable[i] = (byte) (i - 'a' + 26); } for (int i = '0'; i <= '9'; i++) { decodingTable[i] = (byte) (i - '0' + 52); } decodingTable['+'] = 62; decodingTable['/'] = 63; } public static byte[] encode(byte[] data) { byte[] bytes; int modulus = data.length % 3; if (modulus == 0) { bytes = new byte[(4 * data.length) / 3]; } else { bytes = new byte[4 * ((data.length / 3) + 1)]; } int dataLength = (data.length - modulus); int a1; int a2; int a3; for (int i = 0, j = 0; i < dataLength; i += 3, j += 4) { a1 = data[i] & 0xff; a2 = data[i + 1] & 0xff; a3 = data[i + 2] & 0xff; bytes[j] = encodingTable[(a1 >>> 2) & 0x3f]; bytes[j + 1] = encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3f]; bytes[j + 2] = encodingTable[((a2 << 2) | (a3 >>> 6)) & 0x3f]; bytes[j + 3] = encodingTable[a3 & 0x3f]; } int b1; int b2; int b3; int d1; int d2; switch (modulus) { case 0: /* nothing left to do */ break; case 1: d1 = data[data.length - 1] & 0xff; b1 = (d1 >>> 2) & 0x3f; b2 = (d1 << 4) & 0x3f; bytes[bytes.length - 4] = encodingTable[b1]; bytes[bytes.length - 3] = encodingTable[b2]; bytes[bytes.length - 2] = (byte) '='; bytes[bytes.length - 1] = (byte) '='; break; case 2: d1 = data[data.length - 2] & 0xff; d2 = data[data.length - 1] & 0xff; b1 = (d1 >>> 2) & 0x3f; b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f; b3 = (d2 << 2) & 0x3f; bytes[bytes.length - 4] = encodingTable[b1]; bytes[bytes.length - 3] = encodingTable[b2]; bytes[bytes.length - 2] = encodingTable[b3]; bytes[bytes.length - 1] = (byte) '='; break; } return bytes; } public static byte[] decode(byte[] data) { byte[] bytes; byte b1; byte b2; byte b3; byte b4; data = discardNonBase64Bytes(data); if (data[data.length - 2] == '=') { bytes = new byte[(((data.length / 4) - 1) * 3) + 1]; } else if (data[data.length - 1] == '=') { bytes = new byte[(((data.length / 4) - 1) * 3) + 2]; } else { bytes = new byte[((data.length / 4) * 3)]; } for (int i = 0, j = 0; i < (data.length - 4); i += 4, j += 3) { b1 = decodingTable[data[i]]; b2 = decodingTable[data[i + 1]]; b3 = decodingTable[data[i + 2]]; b4 = decodingTable[data[i + 3]]; bytes[j] = (byte) ((b1 << 2) | (b2 >> 4)); bytes[j + 1] = (byte) ((b2 << 4) | (b3 >> 2)); bytes[j + 2] = (byte) ((b3 << 6) | b4); } if (data[data.length - 2] == '=') { b1 = decodingTable[data[data.length - 4]]; b2 = decodingTable[data[data.length - 3]]; bytes[bytes.length - 1] = (byte) ((b1 << 2) | (b2 >> 4)); } else if (data[data.length - 1] == '=') { b1 = decodingTable[data[data.length - 4]]; b2 = decodingTable[data[data.length - 3]]; b3 = decodingTable[data[data.length - 2]]; bytes[bytes.length - 2] = (byte) ((b1 << 2) | (b2 >> 4)); bytes[bytes.length - 1] = (byte) ((b2 << 4) | (b3 >> 2)); } else { b1 = decodingTable[data[data.length - 4]]; b2 = decodingTable[data[data.length - 3]]; b3 = decodingTable[data[data.length - 2]]; b4 = decodingTable[data[data.length - 1]]; bytes[bytes.length - 3] = (byte) ((b1 << 2) | (b2 >> 4)); bytes[bytes.length - 2] = (byte) ((b2 << 4) | (b3 >> 2)); bytes[bytes.length - 1] = (byte) ((b3 << 6) | b4); } return bytes; } public static byte[] decode(String data) { byte[] bytes; byte b1; byte b2; byte b3; byte b4; data = discardNonBase64Chars(data); if (data.charAt(data.length() - 2) == '=') { bytes = new byte[(((data.length() / 4) - 1) * 3) + 1]; } else if (data.charAt(data.length() - 1) == '=') { bytes = new byte[(((data.length() / 4) - 1) * 3) + 2]; } else { bytes = new byte[((data.length() / 4) * 3)]; } for (int i = 0, j = 0; i < (data.length() - 4); i += 4, j += 3) { b1 = decodingTable[data.charAt(i)]; b2 = decodingTable[data.charAt(i + 1)]; b3 = decodingTable[data.charAt(i + 2)]; b4 = decodingTable[data.charAt(i + 3)]; bytes[j] = (byte) ((b1 << 2) | (b2 >> 4)); bytes[j + 1] = (byte) ((b2 << 4) | (b3 >> 2)); bytes[j + 2] = (byte) ((b3 << 6) | b4); } if (data.charAt(data.length() - 2) == '=') { b1 = decodingTable[data.charAt(data.length() - 4)]; b2 = decodingTable[data.charAt(data.length() - 3)]; bytes[bytes.length - 1] = (byte) ((b1 << 2) | (b2 >> 4)); } else if (data.charAt(data.length() - 1) == '=') { b1 = decodingTable[data.charAt(data.length() - 4)]; b2 = decodingTable[data.charAt(data.length() - 3)]; b3 = decodingTable[data.charAt(data.length() - 2)]; bytes[bytes.length - 2] = (byte) ((b1 << 2) | (b2 >> 4)); bytes[bytes.length - 1] = (byte) ((b2 << 4) | (b3 >> 2)); } else { b1 = decodingTable[data.charAt(data.length() - 4)]; b2 = decodingTable[data.charAt(data.length() - 3)]; b3 = decodingTable[data.charAt(data.length() - 2)]; b4 = decodingTable[data.charAt(data.length() - 1)]; bytes[bytes.length - 3] = (byte) ((b1 << 2) | (b2 >> 4)); bytes[bytes.length - 2] = (byte) ((b2 << 4) | (b3 >> 2)); bytes[bytes.length - 1] = (byte) ((b3 << 6) | b4); } return bytes; } private static byte[] discardNonBase64Bytes(byte[] data) { byte[] temp = new byte[data.length]; int bytesCopied = 0; for (int i = 0; i < data.length; i++) { if (isValidBase64Byte(data[i])) { temp[bytesCopied++] = data[i]; } } byte[] newData = new byte[bytesCopied]; System.arraycopy(temp, 0, newData, 0, bytesCopied); return newData; } private static String discardNonBase64Chars(String data) { StringBuffer sb = new StringBuffer(); int length = data.length(); for (int i = 0; i < length; i++) { if (isValidBase64Byte((byte) (data.charAt(i)))) { sb.append(data.charAt(i)); } } return sb.toString(); } private static boolean isValidBase64Byte(byte b) { if (b == '=') { return true; } else if ((b < 0) || (b >= 128)) { return false; } else if (decodingTable[b] == -1) { return false; } return true; }}
綜上我們經常使用的發送加密資料的流程是:
前端、移動端:
1 AES加密得到加密資料
2 Base64編碼加密資料
3 發送Base64編碼資料
後端:
4 Base64解碼資料
5 AES解密資料
6 儲存未經處理資料
參考:
http://blog.csdn.net/uikoo9/article/details/27983071
http://blog.csdn.net/qq_18870023/article/details/52183755