缘起

网络中为了保障数据安全性,会对数据做很多增加安全性的操作,比如加密,签名(摘要)等。而加密又分为对称加密和非对称加密,每一种加密方式又有不同加密算法来实现,常见的有比如MD5,DES,AES等,本文主要对常用的加密算法做一个入门。

加密算法分类

Java加密与解密艺术_二进制

单向散列算法

单向散列算法又叫签名摘要算法或摘要算法,是从一段明文中提取一段摘要出来,明文中任何一个字符的变动都会引起摘要的变动,从而实现对原文的真实性做校验

比如有一个压缩包A求出来的摘要为c0bb4f54f1d8b14caf6fe1069e5f93ad,那么用户在下载这个压缩包到本地的时候就可以再次求一下摘要,然后与作者提供的摘要做比对,如果不一样,说明这个压缩包被人修改过,可能被人放入了病毒之类。

常见的单向散列算法有MD系列,Sha系列,Mac算法。

MD5:md5算法是典型的消息摘要算法,其前身有MD2、MD3和MD4算法,它由MD4、MD3和MD2算法改进而来。

SHA系列:也是由MD4演化而来,包括 SHA-1,SHA-224,SHA-256,SHA-384,和 SHA-512 等几种算法。其中,SHA-1,SHA-224 和 SHA-256 适用于长度不超过 2^64 二进制位的消息。SHA-384 和 SHA-512 适用于长度不超过 2^128 二进制位的消息。与MD5主要不同之处在于摘要长度,SHA算法的摘要长度更长,安全性更高。

HMAC:类似MD5和SHA,也是给定一个明文,通过HMAC得到这段明文的Hash值,但是与MD和SHA不同的是,HMAC运算的时候需要一个密钥的参与,通常称之为“盐”,其原理为 HMAC 进程将密钥(盐)与消息数据混合,使用哈希函数对混合结果进行哈希计算,将所得哈希值与该密钥混合,然后再次应用哈希函数。 输出的哈希值长度为 160 位。此外,HMAC还能自己生成密钥而非人为构造密钥。调用的时候通常是​​hamc("明文","盐")​​​这种方式。MD5和SHA系列在日常的使用过程中为了安全通常也会加盐,只是这个盐是直接拼在明文的后面,比如:​​MD5("明文"+"盐")​​.HMAC可以用来做报文签名,也就是防篡改,发送方在发送报文的时候在报文后面拼接上通过HMAC算法计算出的Hash值,接收方接收到的时候对这个Hash值进行验证,由于密钥(盐)是发送方和接收方提前约定好的,所以中间层即使获取了你的明文,他篡改了报文之后由于没有盐,他就无法得到运算之后的结果。这样接收端方可以验证这段报文的真实性。Java实现Hamc如下

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

public class HMACSHA1 {

private static final String MAC_NAME = "HmacSHA1";
private static final String ENCODING = "UTF-8";

/*
* 展示了一个生成指定算法密钥的过程 初始化HMAC密钥
* @return
* @throws Exception
*
public static String initMacKey() throws Exception {
//得到一个 指定算法密钥的密钥生成器
KeyGenerator KeyGenerator keyGenerator =KeyGenerator.getInstance(MAC_NAME);
//生成一个密钥
SecretKey secretKey =keyGenerator.generateKey();
return null;
}
*/

/**
* 使用 HMAC-SHA1 签名方法对对encryptText进行签名
* @param encryptText 被签名的字符串
* @param encryptKey 密钥
* @return
* @throws Exception
*/
public static byte[] HmacSHA1Encrypt(String encryptText, String encryptKey) throws Exception
{
byte[] data=encryptKey.getBytes(ENCODING);
//根据给定的字节数组构造一个密钥,第二参数指定一个密钥算法的名称
SecretKey secretKey = new SecretKeySpec(data, MAC_NAME);
//生成一个指定 Mac 算法 的 Mac 对象
Mac mac = Mac.getInstance(MAC_NAME);
//用给定密钥初始化 Mac 对象
mac.init(secretKey);

byte[] text = encryptText.getBytes(ENCODING);
//完成 Mac 操作
return mac.doFinal(text);
}
}

对称加密算法

DES:使用最广泛的算法,DES 的密钥的位数太短,只有56 比特,加密强度不够,而如果要提高加密强度(例如增加密钥长度),则系统开销呈指数增长。

3DES:解决DES密钥太短的问题,密钥长度112-168位,增加了迭代次数,速度较慢,效率不高。使用方式同DES。

AES:解决3DES加密效率低,速度慢的问题,成为DES算法的替代者,使用方式同DES。AES是下一代的加密算法标准,速度快,安全级别高。对内存的需求非常低。AES的密钥长度比DES大, AES同时还有AES128,AES192,AES256这些,后面的数字分别代表密钥的比特长度。AES所允许的密钥长度有128(bit),192(bit),256(bit)三个长度,对应的明文就是128/8=16字节,24字节和32字节。但是由于美国出口限制,导致目前只能用16个字节的密钥,用24和32字节的密钥会报错​​java.security.InvalidKeyException: Illegal key size or default parameters​

不管是DES,3DES还是AES的又有五种加密模式:CBC、ECB、CTR、OCF、CFB,每一种加密模式其实现机理又不一样,加密出来的密文也不尽相同。

非对称加密算法

RSA:该算法有一个公钥和一个私钥。用公钥加密只有这个私钥能解开,用私钥加密也只有公钥能解开。在日常使用中通常私钥是只有服务端有,而公钥是多个客户端有。这样服务端发送的报文每一个客户端都可以解开(此处的应用场景通常用于签名,因为私钥只有服务端有,所以客户端能够解密出来这一段密文,就说明报文是服务端发送的而不是第三方发送的)。而客户端发送的报文也只有服务端能够解开,客户端互相之间是解不开的。故日常用私钥签名,公钥加密。

编码算法

严格来说编码算法并不属于加密算法,其原理只是对原文进行编码使之变成了一串肉眼无法辨别的编码,但如果通过计算机,能够很容易的再解码为明文,没有任何安全性可言。

BASE64:由于某些系统中只能使用ASCII字符。Base64就是用来将非ASCII字符的数据转换成ASCII字符的一种方法。由于ASCII码表是不包含中文的,所以Base64理论上应该不能作用于中文,但实际测试过程中确实是可以,这是什么原因我也不是很清楚,猜测只是把中文转成了字符而已。​​BASE64实现原理​

URIEncode:由于url中对一些字符有限制,比如“+”,“/”等,而传统base64编码后会出现这些字符,这些字符会影响URL的使用,因此出现了UrlBase64编码。

JDK中对加密算法的支持

jdk中对加密算法有不错的支持,我们来看看具体有哪些。

jdk加密相关jar包以及package:


  1. rt.jar:java.security.*包,定义了消息摘要算法的实现(不包括mac算法)以及加解密算法的使用接口
  2. jce.jar:java.crypto.*包,DES,3DES以及AES中使用较多,RSA中会用到Cipher类。其中包含了DES,3DES以及AES的实现;比较常用的类Cipher,KeyGenerator,SecretKey,Key,SecretKeyFactory
  3. rt.jar:java.security.cert.*包,提供用于解析和管理证书的类和接口,如Certificate以及CertificateFactory
  4. Bouncy Castle:加密组件,安全提供者,javajce的补充(如java6不支持MD4,SSH-224,但bc支持),以及一些工具类,如Base64编码(UrlBase64类)以及十六进制编码(Hex类)。

此外还有很多开源组织也提供了很多不错的工具类,比如apache的commons-codec,apache的commons是一个工具包,而commons-codec就是这个工具包中用于加解密的。

下面给出一个在java中使用各种加密方式的代码

package com.bxoon.security;

import org.junit.jupiter.api.Test;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import java.security.*;

/**
* MD5加密
*/
public class MD5Util {

/**
* 功能简述: 测试MD5单向加密.
* @throws Exception
*/
@Test
public void test01() throws Exception {
String plainText = "Hello , world !";
MessageDigest md5 = MessageDigest.getInstance("md5");
byte[] cipherData = md5.digest(plainText.getBytes());
StringBuilder builder = new StringBuilder();
for(byte cipher : cipherData) {
String toHexStr = Integer.toHexString(cipher & 0xff);
builder.append(toHexStr.length() == 1 ? "0" + toHexStr : toHexStr);
}
System.out.println(builder.toString());
}

/**
* 功能简述: 使用BASE64进行双向加密/解密.
* @throws Exception
*/
@Test
public void test02() throws Exception {
BASE64Encoder encoder = new BASE64Encoder();
BASE64Decoder decoder = new BASE64Decoder();
String plainText = "Hello , world !";
String cipherText = encoder.encode(plainText.getBytes());
System.out.println("cipherText : " + cipherText);
//cipherText : SGVsbG8gLCB3b3JsZCAh
System.out.println("plainText : " +
new String(decoder.decodeBuffer(cipherText)));
}

/**
* 功能简述: 使用DES对称加密/解密.
* @throws Exception
*/
@Test
public void test03() throws Exception {
String plainText = "Hello , world !";
String key = "12345678"; //要求key至少长度为8个字符

SecureRandom random = new SecureRandom();
DESKeySpec keySpec = new DESKeySpec(key.getBytes());
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("des");
SecretKey secretKey = keyFactory.generateSecret(keySpec);

Cipher cipher = Cipher.getInstance("des");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, random);
byte[] cipherData = cipher.doFinal(plainText.getBytes());
System.out.println("cipherText : " + new BASE64Encoder().encode(cipherData));
//PtRYi3sp7TOR69UrKEIicA==

cipher.init(Cipher.DECRYPT_MODE, secretKey, random);
byte[] plainData = cipher.doFinal(cipherData);
System.out.println("plainText : " + new String(plainData));
//Hello , world !
}

/**
* 功能简述: 使用RSA非对称加密/解密.
* @throws Exception
*/
@Test
public void test04() throws Exception {
String plainText = "Hello , world !";

KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("rsa");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.generateKeyPair();

PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
SecureRandom random = new SecureRandom();

Cipher cipher = Cipher.getInstance("rsa");
cipher.init(Cipher.ENCRYPT_MODE, privateKey, random);
byte[] cipherData = cipher.doFinal(plainText.getBytes());
System.out.println("cipherText : " + new BASE64Encoder().encode(cipherData));

cipher.init(Cipher.DECRYPT_MODE, publicKey, random);
byte[] plainData = cipher.doFinal(cipherData);
System.out.println("plainText : " + new String(plainData));
//Hello , world !

Signature signature = Signature.getInstance("MD5withRSA");
signature.initSign(privateKey);
signature.update(cipherData);
byte[] signData = signature.sign();
System.out.println("signature : " + new BASE64Encoder().encode(signData));
//ADfoeKQn6eEHgLF8ETMXan3TfFO03R5u+cQEWtAQ2lRblLZw1DpzTlJJt1RXjU451I84v3297LhR
//co64p6Sq3kVt84wnRsQw5mucZnY+jRZNdXpcbwh2qsh8287NM2hxWqp4OOCf/+vKKXZ3pbJMNT/4
///t9ewo+KYCWKOgvu5QQ=

signature.initVerify(publicKey);
signature.update(cipherData);
boolean status = signature.verify(signData);
System.out.println("status : " + status);
//true
}

}