前言

在开发应用过程中,客户端与服务端经常需要进行数据传输,涉及到重要隐私安全信息时,开发者自然会想到对其进行加密,即使传输过程中被”有心人”截取,也不会将信息泄露。对于加密算法,相信很多开发者都有了解,比如MD5加密、BASE64加密,SHA-1加密、DES加密、AES加密、RSA加密等等。在这里主要总结一下常用的加密算法。

1. MD5加密

1.1 概述

MD5的全称是Message-Digest Algorithm 5(信息-摘要算法),在90年代初由MIT Laboratory for Computer Science 和 RSA Data Security IncRonald L.Riverst开发出来,经MD2、MD3和MD4发展而来。

MD5用于确保信息传输完整一致,是计算机广泛使用的杂凑算法之一,MD5的作用是让大容量信息在数字签名软件签署私人秘钥前被压缩成一种保密的格式(即把一个任意长度的字节串变成一定长的十六进制数字串)。

1.2 算法原理

MD5以512位分组来处理输入信息,且每一分组又被划分为32位子分组,经过了一系列的处理后,算法的输出由4个32位分组组成,将4个32位分组级联后将生成一个128位散列值。

1.3 MD5的特点

  1. 压缩性: 任意长度的数据,算出的MD5值长度都是固定的;
  2. 容易计算: 从原数据计算出MD5很容易
  3. 抗修改性: 对原始数据进行任何改动,哪怕只修改一个字节,所得到的MD5值都有很大的区别。
  4. 弱抗碰撞: 已知原数据和其MD5值,想找到一个相同的MD5值的数据是非常困难的;
  5. 强抗碰撞: 想找到两个不同的数据,使它们具有相同的MD5值是非常困难的。

1.4 JAVA中MD5的实现

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
* Created by jianweilin on 2017/10/11.
*/
public class MD5Utils {
private static final String MD5_KEY = "MD5";

/**
* MD5加密 生成32位MD5码
* @param content 待加密字符串
* @return 返回32位MD5码
* @throws UnsupportedEncodingException
*/
public static String md5Encode(String content) throws UnsupportedEncodingException {
MessageDigest md5 = null;
try{
md5 = MessageDigest.getInstance(MD5_KEY);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return "";
}

byte[] byteArray = content.getBytes("UTF-8");
byte[] md5Bytes = md5.digest(byteArray);
StringBuffer hexValue = new StringBuffer();
for (int i = 0; i < md5Bytes.length; i ++) {
int val = ((int) md5Bytes[i]) & 0xff;
if(val < 16) {
hexValue.append(0);
}
hexValue.append(Integer.toHexString(val));
}
return hexValue.toString();
}

public static void main(String[] args) throws UnsupportedEncodingException {
String content = "This is a MD5 Test";
String hash = md5Encode(content);
System.out.println("original: " + content);
System.out.println("md5 after: " + hash + " length => " + hash.length());
}
}

2. SHA加密

2.1 概述

SHA是一种数据加密算法,其算法思想是,接收一段明文,然后以一种不可逆的方式将它转换成一段密文,也可以简单的理解为取出一串输入码,并把他们转化为长度较短、位数固定的输出序列即散列值。

2.2 SHA-1和MD5的比较

因为两者均由MD4导出,SHA-1和MD5彼此很相似,他们的强度和其他特性也相识,但有以下几点不同:

  1. 对强行攻击的安全性: 最显著和最重要的区别是SHA-1摘要比MD5摘要长32位,使用强行技术,产生任何一个报文, 对MD5是2^128数据级的操作,对SHA-1是2^160数量级的操作,这样SHA-1对强行攻击有更大的强度。
  2. 对密码分析的安全性:由于MD5的设计,易受密码分析的攻击,SHA-1显得不易受这样的攻击。
  3. 速度: 在相同的硬件上,SHA-A的运行速度比MD慢。

2.3 JAVA中SHA实现

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
* Created by jianweilin on 2017/10/11.
*/
public class SHAUtils {
private static final String SHA_KEY = "SHA";

/**
* MD5加密 生成32位MD5码
* @param content 待加密字符串
* @return 返回32位MD5码
* @throws UnsupportedEncodingException
*/
public static String shaEncode(String content) throws UnsupportedEncodingException {
MessageDigest sha = null;
try{
sha = MessageDigest.getInstance(SHA_KEY);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return "";
}

byte[] byteArray = content.getBytes("UTF-8");
byte[] shaBytes = sha.digest(byteArray);
StringBuffer hexValue = new StringBuffer();
for (int i = 0; i < shaBytes.length; i ++) {
int val = ((int) shaBytes[i]) & 0xff;
if(val < 16) {
hexValue.append(0);
}
hexValue.append(Integer.toHexString(val));
}
return hexValue.toString();
}

public static void main(String[] args) throws UnsupportedEncodingException {
String content = "This is a SHA Test";
String hash = shaEncode(content);
System.out.println("original: " + content);
System.out.println("sha after: " + hash + " length => " + hash.length());
}
}

3. 对称加密与非对称加密

3.1 对称加密算法

甲方选择某一种加密规则,对信息进行加密;

乙方使用同一种规则,对信息进行解密。

特点: 对称加密算法的特定是算法公开、计算量下、加密速度快、加密效率高。

不足: 甲方必须把加密规则告诉乙方,否则无法解密。保存和传递秘钥,就成了最头疼的问题

常见的对称加密算法: AES DES 3DES IDEA RC4 RC5 RC6 等

3.2 非对称加密算法

乙方生成两把密钥(公钥和私钥),公钥是公开的,任何人都可以获得,私钥则是保密的。

甲方获取乙方的公钥,然后用它对信息加密

乙方得到加密后的信息,用私钥解密

特点: 公钥和私钥是一对,如果公开密钥对数据进行加密,只有对应的私钥才能解密;如果用私钥进行加密,只有对应的公钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫非对称加密算法。

4. AES算法和RSA算法

4.1 AES算法

高级加密标准(Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。

4.2 RSA算法

1977年,三位科学家RivestShamirAdleman设计了一种算法,可以实现非对称加密。这种算法用他们三个人的名字命名,加做RSA算法。RSA属于非对称加密算法。

4.3 两种算法使用的场景

  1. 客户端传输重要信息给服务端,服务端返回的信息不需加密的情况,如绑定银行卡的时候,需要传递用户的银行卡号、手机号等重要信息,客户端这边就需要对这些重要信息进行加密,使用RSA公钥加密、服务端使用RSA解密,然后返回一些普通信息,比如状态码code,提示信息msg等
  2. 客户端传输重要的信息给服务端,服务端返回的信息需加密,如传递用户名和密码等资料,需要进行加密,服务端验证信息后,返回令牌token需要进行加密,客户端解密后保存。

4.4 JAVA中使用RSA加密和解密

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
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import org.apache.commons.codec.binary.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;



/**
* Created by jianweilin on 2017/10/11.
*/
public class RSAUtils {
private static final String PUBLIC_KEY = "RSAPublicKey";
private static final String PRIVATE_KEY = "RSAPrivateKey";
private static final String KEY_ALGORITHM = "RSA";
private static final String SIGNATHURE_ALGORITHM = "MD5withRSA";

public static Map<String,Object> initKey() throws NoSuchAlgorithmException {
// 1. 生成公钥和私钥对,基于RSA算法生成对象
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);

// 2. 初始化密钥对生成器,密钥大小为1024位
keyPairGenerator.initialize(1024);

// 3. 生成一个密钥对,保存在keyPair中
KeyPair keyPair = keyPairGenerator.generateKeyPair();

// 4. 得到私钥和公钥
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();

Map<String,Object> keyMap = new HashMap<>();
keyMap.put(PUBLIC_KEY,publicKey);
keyMap.put(PRIVATE_KEY,privateKey);
return keyMap;
}

/**
* 获取私钥
*/
public static String getPrivateKey(Map<String,Object> keyMap){
Key key = (Key) keyMap.get(PRIVATE_KEY);
return Base64.encodeBase64String(key.getEncoded());
}

/**
* 获取公钥
*/
public static String getPublicKey(Map<String,Object> keyMap) {
Key key = (Key) keyMap.get(PUBLIC_KEY);
return Base64.encodeBase64String(key.getEncoded());
}

/**
* 用公钥对数据加密
*/
public static byte[] encryptByPublicKey(byte[] data,String key) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
// 对公钥解密
byte[] keyBytes = Base64.decodeBase64(key);

// 取得公钥
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key publicKey = keyFactory.generatePublic(x509EncodedKeySpec);

// 对数据加密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE,publicKey);
return cipher.doFinal(data);
}

/**
* 用私钥对数据解密
*/
public static byte[] decrptByPrivateKey(byte[] data, String key) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
// 对私钥解密
byte[] keyBytes = Base64.decodeBase64(key);

// 取得私钥
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);

// 对数据解密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE,privateKey);

return cipher.doFinal(data);
}

public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
String content = "Hello World";

// 1. 初始化公钥 & 私钥
Map<String,Object> pairKey = initKey();

// 2. 获取公钥
String publicKey = getPublicKey(pairKey);

// 3. 用公钥加密内容
byte[] encodeContent = encryptByPublicKey(content.getBytes("UTF-8"),publicKey);

// 4. 获取私钥
String privateKey = getPrivateKey(pairKey);

// 5. 用私钥解密内容
byte[] decodeContent = decrptByPrivateKey(encodeContent,privateKey);

System.out.println("原文:" + content);
System.out.print("密文:");
System.out.println(new String(encodeContent,"UTF-8"));
System.out.println("明文:" + new String(decodeContent,"UTF-8"));
System.out.println("加密~解密历时: " + (System.currentTimeMillis() - start)/1000 + "秒");
}
}

4.5 AES加密 & 解密

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
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

/**
* Created by jianweilin on 2017/10/11.
*/
public class AESUtils {

/**
* 加密
*/
public static String encrypt(String sSrc, String sKey) throws Exception {
if (sKey == null) {
System.out.print("Key为空null");
return null;
}
// 判断Key是否为16位
if (sKey.length() != 16) {
System.out.print("Key长度不是16位");
return null;
}
byte[] raw = sKey.getBytes();
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");//"算法/模式/补码方式"
IvParameterSpec iv = new IvParameterSpec("0102030405060708".getBytes());//使用CBC模式,需要一个向量iv,可增加加密算法的强度
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(sSrc.getBytes());

return new BASE64Encoder().encode(encrypted);//此处使用BASE64做转码功能,同时能起到2次加密的作用。
}

/**
* 解密
*/
public static String decrypt(String sSrc, String sKey) throws Exception {
try {
// 判断Key是否正确
if (sKey == null) {
System.out.print("Key为空null");
return null;
}
// 判断Key是否为16位
if (sKey.length() != 16) {
System.out.print("Key长度不是16位");
return null;
}
byte[] raw = sKey.getBytes("ASCII");
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv = new IvParameterSpec("0102030405060708"
.getBytes());
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] encrypted1 = new BASE64Decoder().decodeBuffer(sSrc);//先用base64解密
try {
byte[] original = cipher.doFinal(encrypted1);
String originalString = new String(original);
return originalString;
} catch (Exception e) {
System.out.println(e.toString());
return null;
}
} catch (Exception ex) {
System.out.println(ex.toString());
return null;
}
}

public static void main(String[] args) throws Exception {
String content = "HelloWorld123456";
String password = "1234567123456712";

// AES加密
String encodeContent = encrypt(content,password);

// AES解密
String decodeContent = decrypt(encodeContent,password);

System.out.println("原文:" + content);
System.out.println("密文:" + encodeContent);
System.out.println("明文:" + decodeContent);

}
}

5. Base64编码

5.1 概述

Base64内容传送编码是一种以任意8位字节序列组合的描述形式,这种形式不易被人直接识别。Base64是一种常见的编码规范,其作用是将二进制序列转换为人类可读的ASCLL字符序列,常用在需用通过文本协议(如HTTP和SMTP)来传输二进制数据下。Base64并不是加密解密算法,尽管我们有时也听到过使用Base64来加密解密的说法,但这里所有的加密和解密实际是编码和解码的过程,其变换是非常简单的,仅仅能够编码信息被直接识别。

5.2 原理

Base64算法主要是将给定的字符以字符编码(如ASCLL码,UTF-8码)对应的十进制数为基准,做编码操作。

1、将给定的字符串以字符为单位,转换为对应的字符编码。

2、将获得字符编码转换为二进制

3、对二进制做分组转换,每3个字节为一组,转换为每4个6位二进制位为1组(不足6位时,低位补0)这是一个分组变化的过程,3个8位二进制和4个6位二进制的长度都是24位

4、对获得的4-6二进制码补位,向6位二进制码添加2位高位0,组成4个8位二进制码。

5、对获得4-8二进制码转换为十进制码。

6、将获得的十进制码转换为Base64字符表中的对应字符。

5.3 JAVA实现Base64编码和解码

推荐使用commons-codec开源包,请在maven工程中添加依赖:

1
2
3
4
5
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>

实战:Base64编码 & 解码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.apache.commons.codec.binary.Base64;

import java.io.UnsupportedEncodingException;

/**
* Created by jianweilin on 2017/10/11.
*/
public class Base64Utils {
private static final String QUERY_URL = "http://localhost:8080/hello?content=sayHello";

public static void main(String[] args) throws UnsupportedEncodingException {
byte[] encodeContent = Base64.encodeBase64URLSafe(QUERY_URL.getBytes("UTF-8"));
byte[] decodeContent = Base64.decodeBase64(encodeContent);

System.out.println("原文:" + QUERY_URL);
System.out.println("编码:" + new String(encodeContent,"UTF-8"));
System.out.println("解码:" + new String(decodeContent,"UTF-8"));
}
}

参考资料

  1. JAVA中常用加密算法简述
  2. Java与加密解密