AES加密 - Java,Android,IOS三端联调

2,273 阅读12分钟

引言

科技快速发展,普遍的数据传递成为人与人、人与物、物与物的日常。
高效、快速、安全的数据传递成了数据交流的基石。
为了确保数据的安全性,保护用户的隐私,出现了大量的加密算法。
今天对几种常见的加密算法浅显的记录,并且对AES算法三端统一加密问题给出解决方法。

一、常见概念

1.明文:加密前的信息
2.密文:加密后的信息
3.算法:加密或解密算法
4.密钥:算法使用的钥匙
5.对称加密算法:加密算法和解密算法相对称
6.非对称加密算法:加密算法和解密算法不对称

对于这些概念,大家都不会陌生,但是需要自己去实现,我们需要做哪些思考呢?

举一个小例子让大家更清晰的理解这些概念:
将 123456 的每位数字加 1 后得到的是 234567
其中明文就是 123456,密文就是 234567,算法就是给每位数字加 (+1) ,密钥就是 1
这样就很清晰了,那么可以看出,最核心的部分就是算法部分
因为加密方式的算法不同,从而诞生了各种各样的加密方式。
而根据加密算法和解密算法的对称性,产生了对称算法和非对称算法的概念
继续使用上面的例子展现:
123456 ---> (+1) ---> 234567 的加密秘钥为 1 ,加密算法为每位 (+1)
234567 ---> ( -1) ---> 123456 的解密秘钥为 1 ,解密算法为每位 ( -1)
其中加密算法 (+1) 和解密算法 ( -1) 相对称,这种加密算法就称作对称加密
同样,如果加密算法和解密算法非对称,就称为非对称加密

二、加密方式

1.Base64加密

严格来说Base64不能算是一种加密方式,更确切的来说是一种编码方式。
但是对于别的算法Base64有着不可忽视的重要性。
计算机中的数据都是二进制的,不管是字符串还是文件,加密后的数据也是二级制的。
很多算法加密后输出的都是byte[],而我们需要的往往是字符串,所以需要需要使用Base64对其进行编码。

2.MD5加密

严格来说也不是加密算法,中文名为消息摘要算法第五版,是一种哈希算法,是计算机广泛使用的杂凑算法之一。
其最重要的性质就是不可逆、无冲突
所谓的不可逆就是:当你知道x的HASH值,无法求出x
所谓的无冲突就是:但你知道x,无法求出一个y,使x与y的值相同
这两条性质在数学上都是不成立的。
因为一个函数必然可逆,且由于HASH函数的值域有限,理论上会有无穷多个不同的原始值,它们的HASH值都相同。
MD5做到的是求逆和求冲突在计算上的不可能,也就是正向计算很容易,而反向计算即使穷尽人类所有的计算资源都做不到
因为HASH的散列性,其在计算的过程中散列了一些信息,所以无法逆向求解,但是有人说网上有破解的方法。
网上的方法可以说是一种对MD5建立的字典查询,而不是真正意义的算法破解,我也有MD5破解脚本,需要的可以q我。
想深入研究的可以百度彩虹表
举个简单的例子来理解HASH算法的不可逆性,比如每个人都有指纹,虹膜等,警察叔叔可以将大家的指纹,虹膜都录入一个库。
查询的时候就可以匹配出对应的信息。但是如果没有录入库中,可以根据指纹来推断这个人的长相,身体特征吗?
如果HASH算法可逆,那么数据压缩技术会得到里程碑式的发展
你爱的苍老师大片被用HASH算法压缩成一个长度为128bit的大整数,那还需要种子干吗啊?

特性: ① 压缩性:任意长度的数据,算出的MD5值长度都是固定的
② 容易计算:用原数据计算出MD5很容易
③ 抗修改性:对原数据进行改动,哪怕只修改一个字节,所得的MD5值有很大差别。
④ 强抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(伪造数据)非常困难
(除了MD5外,比较有名的HASH还有sha-1、RIPEMD、Haval等)

3.对称加密

① 优点:算法公开、计算量小、加密速度快、加密效率高、可逆
② 缺点:双方使用相同秘钥,安全性降低。对称密码体中只有一种密钥,并且是非公开的,如果要解密就得对方知道密钥,所以保证其安全性就是保证密钥的安全。
③ 常见算法:AES、DES、3DES、RC2、RC4、RC5、IDEA、TDEA、Blowfish、SKIPJACK等

4.非对称加密

① 特点:
非对称加密有两个密钥:公开密钥(公钥publickey)和私有密钥(私钥privatekey)
公开密钥和私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;
如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。
算法强度复杂、安全依赖于算法与密钥但是由于其算法复杂,而使得加密解密速度没有对称加密解密的速度快
非对称密钥体制有两种密钥,其中一个是公开的,这样就可以不需要像对称密码那样传输对方的密钥了。
非对称加密算法一般效率差,对大型数据加密时间很长,一般用于小数据。
② 常见算法:RSA、Rabin、Elgamal、D - H、ECC(椭圆曲线加密算法)、背包算法等。

三、重点 AES 加密

AES是一种被广泛使用的加密算法,由于其快速、高效、安全的特点,被许多人所青睐。
然而由于前后端开发使用的语言同,经常导致前端加密后而后端无法解密,或者各端对相同的明文加出来的密文不同。
然而无论什么语言,AES的算法总是相同的,因此导致结果不同的原因是加密设置的参数不一致。
因此我们需要统一的参数有以下几个:
① 密钥长度:Key Size
② 加密模式:Cipher Mode
③ 填充方式:Padding
④ 初始向量:Initialization Vector

1.密钥长度

AES算法下,key的长度有三种:128、192、256(bits)。
JDK默认只支持不大于128bits 的密钥,而128bits已能够满足商用需求,在此使用126bits长度。
实现:key为16位128bits,自己定义,保证各端一致
private static String key = "128bitslength*@#";
NSString *key = @"128bitslength*@#";

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的初始向量

四、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();
    }
}

五、运行结果

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/102879…
我的GitHub:github.com/wuyang2000
个人网站:www.xiyangkeji.cn
个人app(茜茜)蒲公英连接:www.pgyer.com/KMdT
我的微信公众号:茜洋 (定期推送优质技术文章,欢迎关注)
Android技术交流群:691174792

以上文章均可转载,转载请注明原创。