AES加密 - Java,Android,IOS三端聯調

引言

科技快速發展,廣泛的數據傳遞成爲人與人、人與物、物與物的平常。
高效、快速、安全的數據傳遞成了數據交流的基石。
爲了確保數據的安全性,保護用戶的隱私,出現了大量的加密算法。
今天對幾種常見的加密算法淺顯的記錄,而且對AES算法三端統一加密問題給出解決方法。javascript

1、常見概念

1.明文:加密前的信息
2.密文:加密後的信息
3.算法:加密或解密算法
4.密鑰:算法使用的鑰匙
5.對稱加密算法:加密算法和解密算法相對稱
6.非對稱加密算法:加密算法和解密算法不對稱
前端

對於這些概念,你們都不會陌生,可是須要本身去實現,咱們須要作哪些思考呢?java

舉一個小例子讓你們更清晰的理解這些概念:
將 123456 的每位數字加 1 後獲得的是 234567
其中明文就是 123456,密文就是 234567,算法就是給每位數字加 (+1) ,密鑰就是 1
這樣就很清晰了,那麼能夠看出,最核心的部分就是算法部分
由於加密方式的算法不一樣,從而誕生了各類各樣的加密方式。
而根據加密算法和解密算法的對稱性,產生了對稱算法和非對稱算法的概念
繼續使用上面的例子展示:
123456 ---> (+1) ---> 234567 的加密祕鑰爲 1 ,加密算法爲每位 (+1)
234567 ---> ( -1) ---> 123456 的解密祕鑰爲 1 ,解密算法爲每位 ( -1)
其中加密算法 (+1) 和解密算法 ( -1) 相對稱,這種加密算法就稱做對稱加密
一樣,若是加密算法和解密算法非對稱,就稱爲非對稱加密
複製代碼

2、加密方式

1.Base64加密

嚴格來講Base64不能算是一種加密方式,更確切的來講是一種編碼方式。
可是對於別的算法Base64有着不可忽視的重要性。
計算機中的數據都是二進制的,無論是字符串仍是文件,加密後的數據也是二級制的。
不少算法加密後輸出的都是byte[],而咱們須要的每每是字符串,因此須要須要使用Base64對其進行編碼。git

2.MD5加密

嚴格來講也不是加密算法,中文名爲消息摘要算法第五版,是一種哈希算法,是計算機普遍使用的雜湊算法之一。
其最重要的性質就是不可逆、無衝突
所謂的不可逆就是:當你知道x的HASH值,沒法求出x
所謂的無衝突就是:但你知道x,沒法求出一個y,使x與y的值相同
這兩條性質在數學上都是不成立的。
由於一個函數必然可逆,且因爲HASH函數的值域有限,理論上會有無窮多個不一樣的原始值,它們的HASH值都相同。
MD5作到的是求逆和求衝突在計算上的不可能,也就是正向計算很容易,而反向計算即便窮盡人類全部的計算資源都作不到
由於HASH的散列性,其在計算的過程當中散列了一些信息,因此沒法逆向求解,可是有人說網上有破解的方法。
網上的方法能夠說是一種對MD5創建的字典查詢,而不是真正意義的算法破解,我也有MD5破解腳本,須要的能夠q我。
想深刻研究的能夠百度彩虹表
舉個簡單的例子來理解HASH算法的不可逆性,好比每一個人都有指紋,虹膜等,警察叔叔能夠將你們的指紋,虹膜都錄入一個庫。
查詢的時候就能夠匹配出對應的信息。可是若是沒有錄入庫中,能夠根據指紋來推斷這我的的長相,身體特徵嗎?
若是HASH算法可逆,那麼數據壓縮技術會獲得里程碑式的發展
你愛的蒼老師大片被用HASH算法壓縮成一個長度爲128bit的大整數,那還須要種子幹嘛啊?github

特性: ① 壓縮性:任意長度的數據,算出的MD5值長度都是固定的
② 容易計算:用原數據計算出MD5很容易
③ 抗修改性:對原數據進行改動,哪怕只修改一個字節,所得的MD5值有很大差異。
④ 強抗碰撞:已知原數據和其MD5值,想找到一個具備相同MD5值的數據(僞造數據)很是困難
(除了MD5外,比較有名的HASH還有sha-一、RIPEMD、Haval等)算法

3.對稱加密

① 優勢:算法公開、計算量小、加密速度快、加密效率高、可逆
② 缺點:雙方使用相同祕鑰,安全性下降。對稱密碼體中只有一種密鑰,而且是非公開的,若是要解密就得對方知道密鑰,因此保證其安全性就是保證密鑰的安全。
③ 常見算法:AES、DES、3DES、RC二、RC四、RC五、IDEA、TDEA、Blowfish、SKIPJACK等後端

4.非對稱加密

① 特色:
非對稱加密有兩個密鑰:公開密鑰(公鑰publickey)和私有密鑰(私鑰privatekey)
公開密鑰和私有密鑰是一對,若是用公開密鑰對數據進行加密,只有用對應的私有密鑰才能解密;
若是用私有密鑰對數據進行加密,那麼只有用對應的公開密鑰才能解密。
算法強度複雜、安全依賴於算法與密鑰可是因爲其算法複雜,而使得加密解密速度沒有對稱加密解密的速度快
非對稱密鑰體制有兩種密鑰,其中一個是公開的,這樣就能夠不須要像對稱密碼那樣傳輸對方的密鑰了。
非對稱加密算法通常效率差,對大型數據加密時間很長,通常用於小數據。
② 常見算法:RSA、Rabin、Elgamal、D - H、ECC(橢圓曲線加密算法)、揹包算法等。數組

3、重點 AES 加密

AES是一種被普遍使用的加密算法,因爲其快速、高效、安全的特色,被許多人所青睞。
然而因爲先後端開發使用的語言同,常常致使前端加密後然後端沒法解密,或者各端對相同的明文加出來的密文不一樣。
然而不管什麼語言,AES的算法老是相同的,所以致使結果不一樣的緣由是加密設置的參數不一致。
所以咱們須要統一的參數有如下幾個:
① 密鑰長度:Key Size
② 加密模式:Cipher Mode
③ 填充方式:Padding
④ 初始向量:Initialization Vector安全

1.密鑰長度

AES算法下,key的長度有三種:12八、19二、256(bits)。
JDK默認只支持不大於128bits 的密鑰,而128bits已可以知足商用需求,在此使用126bits長度。
實現:key爲16位128bits,本身定義,保證各端一致
private static String key = "128bitslength*@#";
NSString *key = @"128bitslength*@#";bash

2.加密模式

AES屬於塊加密(Block Cipher),塊加密中有CBC、ECB、CTR、OFB、CFB等幾種工做模式。
要保證加出的密文一致,先後端必須使用一樣的加密模式,此處使用CBC模式來實現。

3.填充方式

因爲塊加密只能對特定長度數據塊進行加密,所以CBC、ECB模式須要在最後一數據塊加密前進行數據填充。
CFB、OFB、CTR模式因爲與key進行加密操做的是上一塊加密後的密文,所以不須要對最後一段明文進行填充。
在 IOS SDK中提供了PKCS7Padding,而JDK則提供了PKCS5Padding。
原則上PKCS5Padding限制了填充的Block Size爲8 bytes。
而Java實際上當塊大於該值時,其PKCS5Padding與PKCS7Padding是相等的:每須要填充X個字節,填充的值就是X。

4.初始向量

使用除ECB之外的其餘加密模式均須要傳入一個初始向量,其大小與Block Size相等(AES的Block Size默認爲128bits)
兩個平臺的API均指出當不傳入初始向量時,系統將默認使用一個全0的初始向量

4、AES加密具體實現

Java端:

AESCipher.java

import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Base64.Decoder;
import java.util.Base64.Encoder;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import java.io.UnsupportedEncodingException;

public class AESCipher {
	private static String key = "128bitslength*@#";
	private static final String IV_STRING = "A-16-Byte-String";
	private static final String charset = "UTF-8";

	public static String aesEncryptString(String content, String key) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {
		byte[] contentBytes = content.getBytes(charset);
		byte[] keyBytes = key.getBytes(charset);
		byte[] encryptedBytes = aesEncryptBytes(contentBytes, keyBytes);
		Encoder encoder = Base64.getEncoder();
		return encoder.encodeToString(encryptedBytes);
	}

	public static String aesDecryptString(String content, String key) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {
		Decoder decoder = Base64.getDecoder();
		byte[] encryptedBytes = decoder.decode(content);
		byte[] keyBytes = key.getBytes(charset);
		byte[] decryptedBytes = aesDecryptBytes(encryptedBytes, keyBytes);
		return new String(decryptedBytes, charset);
	}

	public static byte[] aesEncryptBytes(byte[] contentBytes, byte[] keyBytes) throws NoSuchAlgorithmException,
			NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException,
			BadPaddingException, UnsupportedEncodingException {
		return cipherOperation(contentBytes, keyBytes, Cipher.ENCRYPT_MODE);
	}

	public static byte[] aesDecryptBytes(byte[] contentBytes, byte[] keyBytes) throws NoSuchAlgorithmException,
			NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException,
			BadPaddingException, UnsupportedEncodingException {
		return cipherOperation(contentBytes, keyBytes, Cipher.DECRYPT_MODE);
	}

	private static byte[] cipherOperation(byte[] contentBytes, byte[] keyBytes, int mode)
			throws UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
			InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
		SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");

		byte[] initParam = IV_STRING.getBytes(charset);
		IvParameterSpec ivParameterSpec = new IvParameterSpec(initParam);

		Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
		cipher.init(mode, secretKey, ivParameterSpec);

		return cipher.doFinal(contentBytes);
	}

	public static void main(String[] args) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, UnsupportedEncodingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
		 String pdaes = AESCipher.aesEncryptString("wylq2018",key);
		 System.out.println(pdaes);
		 String pd = AESCipher.aesDecryptString(pdaes,key);
		 System.out.println(pd);
	}
}
複製代碼

IOS端:

AESCipher.h

#import <Foundation/Foundation.h>
NSString * aesEncryptString(NSString *content, NSString *key);
NSString * aesDecryptString(NSString *content, NSString *key);
NSData * aesEncryptData(NSData *data, NSData *key);
NSData * aesDecryptData(NSData *data, NSData *key);

AESCipher.m

#import "AESCipher.h"
#import <CommonCrypto/CommonCryptor.h>

NSString const *kInitVector = @"A-16-Byte-String";
size_t const kKeySize = kCCKeySizeAES128;

NSData * cipherOperation(NSData *contentData, NSData *keyData, CCOperation operation) {
    NSUInteger dataLength = contentData.length;
       void const *initVectorBytes = [kInitVector dataUsingEncoding:NSUTF8StringEncoding].bytes;
    void const *contentBytes = contentData.bytes;
    void const *keyBytes = keyData.bytes;
    size_t operationSize = dataLength + kCCBlockSizeAES128;
    void *operationBytes = malloc(operationSize);
    if (operationBytes == NULL) {
         return nil;
    }
    size_t actualOutSize = 0;
    CCCryptorStatus cryptStatus = CCCrypt(operation,
                                          kCCAlgorithmAES,
                                          kCCOptionPKCS7Padding,
                                          keyBytes,
                                          kKeySize,
                                          initVectorBytes,
                                          contentBytes,
                                          dataLength,
                                          operationBytes,
                                          operationSize,
                                          &actualOutSize);
    if (cryptStatus == kCCSuccess) {
        return [NSData dataWithBytesNoCopy:operationBytes length:actualOutSize];
    }
    free(operationBytes);
    operationBytes = NULL;
    return nil;
}

NSString * aesEncryptString(NSString *content, NSString *key) {
    NSCParameterAssert(content);
    NSCParameterAssert(key);

    NSData *contentData = [content dataUsingEncoding:NSUTF8StringEncoding];
    NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding];
    NSData *encrptedData = aesEncryptData(contentData, keyData);
    return [encrptedData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
}

NSString * aesDecryptString(NSString *content, NSString *key) {
    NSCParameterAssert(content);
    NSCParameterAssert(key);
    NSData *contentData = [[NSData alloc] initWithBase64EncodedString:content options:NSDataBase64DecodingIgnoreUnknownCharacters];
    NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding];
    NSData *decryptedData = aesDecryptData(contentData, keyData);
    return [[NSString alloc] initWithData:decryptedData encoding:NSUTF8StringEncoding];
}

NSData * aesEncryptData(NSData *contentData, NSData *keyData) {
    NSCParameterAssert(contentData);
    NSCParameterAssert(keyData);
    NSString *hint = [NSString stringWithFormat:@"The key size of AES-%lu should be %lu bytes!", kKeySize * 8, kKeySize];
    NSCAssert(keyData.length == kKeySize, hint);
    return cipherOperation(contentData, keyData, kCCEncrypt);
}

NSData * aesDecryptData(NSData *contentData, NSData *keyData) {
    NSCParameterAssert(contentData);
    NSCParameterAssert(keyData);
    NSString *hint = [NSString stringWithFormat:@"The key size of AES-%lu should be %lu bytes!", kKeySize * 8, kKeySize];
    NSCAssert(keyData.length == kKeySize, hint);
    return cipherOperation(contentData, keyData, kCCDecrypt);
}

ViewController.m

#import "ViewController.h"
#import "AESCipher.h"
@interface ViewController ()
@end 
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSString *plainText = @"wy123456";
    NSString *key = @"128bitslength*@#";
    NSString *cipherText = aesEncryptString(plainText, key);
    NSLog(@"%@", cipherText);
    NSString *decryptedText = aesDecryptString(cipherText, key);
    NSLog(@"%@", decryptedText);
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}
@end
複製代碼

Android端:

Android端可使用Java的端的代碼,可是由於java.util.Base64包的aesEncryptBytes()方法getEncoder()方法須要API26以上纔可使用,因此須要進行一些更改,建立Base64Util來進行Base64編碼。

具體實現爲:

AESUtils.java

package com.moie.wy.lib.utils.aes;

import com.tzcm.factory.chumeifactory.utils.LogUtils;

import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AESUtils {
    private static String key = "128bitslength*@#";
    private static final String IV_STRING = "A-16-Byte-String";
    private static final String charset = "UTF-8";

    public static String EnCode(String content) {
        try {
            byte[] contentBytes = content.getBytes(charset);
            byte[] keyBytes = key.getBytes(charset);
            byte[] encryptedBytes = aesEncryptBytes(contentBytes, keyBytes);
            return Base64Util.encode(encryptedBytes);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static String DeCode(String content) {
        try {
            byte[] encryptedBytes = Base64Util.decode(content);
            byte[] keyBytes = key.getBytes(charset);
            byte[] decryptedBytes = aesDecryptBytes(encryptedBytes, keyBytes);
            return new String(decryptedBytes, charset);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static byte[] aesEncryptBytes(byte[] contentBytes, byte[] keyBytes) {
        try {
            return cipherOperation(contentBytes, keyBytes, Cipher.ENCRYPT_MODE);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static byte[] aesDecryptBytes(byte[] contentBytes, byte[] keyBytes) {
        try {
            return cipherOperation(contentBytes, keyBytes, Cipher.DECRYPT_MODE);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static byte[] cipherOperation(byte[] contentBytes, byte[] keyBytes, int mode) throws Exception {
        SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");
        byte[] initParam = IV_STRING.getBytes(charset);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(initParam);
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(mode, secretKey, ivParameterSpec);
        return cipher.doFinal(contentBytes);
    }

    public static void main(String[] args) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, UnsupportedEncodingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
        String pdaes = AESUtils.EnCode("wy123456");
        LogUtils.error(pdaes);
        String pd = AESUtils.DeCode(pdaes);
        LogUtils.error(pd);
    }
}
複製代碼

Base64Util.java

package com.moie.wy.lib.utils.aes;

import java.io.ByteArrayOutputStream;

/** * @author yangyang_2000 * @version v1.0 * @date 2017/12/20 16:22 */
public class Base64Util {
    private static final char[] base64EncodeChars = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
            'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
            's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};

    private static byte[] base64DecodeChars = new byte[]{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60,
            61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1,
            -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1};

    private Base64Util() {
    }

    /** * 將字節數組編碼爲字符串 */
    public static String encode(byte[] data) {
        StringBuffer sb = new StringBuffer();
        int len = data.length;
        int i = 0;
        int b1, b2, b3;
        while (i < len) {
            b1 = data[i++] & 0xff;
            if (i == len) {
                sb.append(base64EncodeChars[b1 >>> 2]);
                sb.append(base64EncodeChars[(b1 & 0x3) << 4]);
                sb.append("==");
                break;
            }
            b2 = data[i++] & 0xff;
            if (i == len) {
                sb.append(base64EncodeChars[b1 >>> 2]);
                sb.append(base64EncodeChars[((b1 & 0x03) << 4) | ((b2 & 0xf0) >>> 4)]);
                sb.append(base64EncodeChars[(b2 & 0x0f) << 2]);
                sb.append("=");
                break;
            }
            b3 = data[i++] & 0xff;
            sb.append(base64EncodeChars[b1 >>> 2]);
            sb.append(base64EncodeChars[((b1 & 0x03) << 4) | ((b2 & 0xf0) >>> 4)]);
            sb.append(base64EncodeChars[((b2 & 0x0f) << 2) | ((b3 & 0xc0) >>> 6)]);
            sb.append(base64EncodeChars[b3 & 0x3f]);
        }
        return sb.toString();
    }

    /** * 將字符串編碼爲字節數組 */
    public static byte[] decode(String str) throws Exception {
        byte[] data = str.getBytes("GBK");
        int len = data.length;
        ByteArrayOutputStream buf = new ByteArrayOutputStream(len);
        int i = 0;
        int b1, b2, b3, b4;
        while (i < len) {
            /* b1 */
            do {
                b1 = base64DecodeChars[data[i++]];
            } while (i < len && b1 == -1);
            if (b1 == -1) {
                break;
            }
            /* b2 */
            do {
                b2 = base64DecodeChars[data[i++]];
            } while (i < len && b2 == -1);
            if (b2 == -1) {
                break;
            }
            buf.write((b1 << 2) | ((b2 & 0x30) >>> 4));
            /* b3 */
            do {
                b3 = data[i++];
                if (b3 == 61) {
                    return buf.toByteArray();
                }
                b3 = base64DecodeChars[b3];
            } while (i < len && b3 == -1);
            if (b3 == -1) {
                break;
            }
            buf.write(((b2 & 0x0f) << 4) | ((b3 & 0x3c) >>> 2));
            /* b4 */
            do {
                b4 = data[i++];
                if (b4 == 61) {
                    return buf.toByteArray();
                }
                b4 = base64DecodeChars[b4];
            } while (i < len && b4 == -1);
            if (b4 == -1) {
                break;
            }
            buf.write(((b3 & 0x03) << 6) | b4);
        }
        return buf.toByteArray();
    }
}
複製代碼

5、運行結果

Java端:使用工具eclipse

在這裏插入圖片描述
Android端:使用工具Android Studio
在這裏插入圖片描述
IOS端:使用工具Xcode
在這裏插入圖片描述
key:128bitslength*@#
明文:wy123456
三端加密後爲密文:c9GjhapX+Equ9Y09YDCVLA==
三端密文一致。

本文參考:
簡書WeLKinXie的文章
CSDN做者uikoo9的文章
百度百科Base64,RSA,AES算法
知乎蔣又新對《什麼是哈希算法》的回覆
知乎《爲何MD5算法不可逆》的回覆


長路漫漫,菜不是原罪,墮落纔是原罪。
個人CSDN:blog.csdn.net/wuyangyang_…
個人簡書:www.jianshu.com/u/20c2f2c35…
個人掘金:juejin.im/user/58009b…
個人GitHub:github.com/wuyang2000
我的網站:www.xiyangkeji.cn
我的app(茜茜)蒲公英鏈接:www.pgyer.com/KMdT
個人微信公衆號:茜洋 (按期推送優質技術文章,歡迎關注)
Android技術交流羣:691174792

以上文章都可轉載,轉載請註明原創。

相關文章
相關標籤/搜索