原文鏈接:https://www.dubby.cn/detail.html?id=9122
1. 常見編碼
為什么要介紹編碼呢?因?yàn)樵贘ava中,加密/解密都是對(duì)byte的操作,一段文本的byte[]經(jīng)過加密后的bye[]可能是一段很隨機(jī)的字節(jié)數(shù)組,如果不經(jīng)過編碼直接轉(zhuǎn)換成string,那么很有可能是不可見字符,而不可見字符不管是保存還是傳輸都會(huì)很麻煩,所以一般來說,加密后都會(huì)再編碼成可見字符保存/傳輸。
1.1 Hex
一個(gè)byte有8位,而Hex編碼就是把一個(gè)byte拆成2個(gè)byte,一個(gè)取前4位,另一個(gè)取后4位。
public class HexUtil {
private final static char[] digits = {
'0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'a', 'b',
'c', 'd', 'e', 'f'
};
public static String toHex(byte[] src) {
StringBuilder sb = new StringBuilder(src.length << 1);
for (byte aSrc : src) {
sb.append(digits[(aSrc & 0xF0) >>> 4]);
sb.append(digits[(aSrc & 0x0F)]);
}
return sb.toString();
}
public static byte[] toBytes(String string) throws Exception {
int len = string.length();
final byte[] out = new byte[len >> 1];
for (int i = 0, j = 0; j < len; i++) {
int f = toDigit(string.charAt(j), j) << 4;
j++;
f = f | toDigit(string.charAt(j), j);
j++;
out[i] = (byte) (f & 0xFF);
}
return out;
}
private static int toDigit(final char ch, final int index) throws Exception {
final int digit = Character.digit(ch, 16);
if (digit == -1) {
throw new Exception("Illegal hexadecimal character " + ch + " at index " + index);
}
return digit;
}
}
其中toHex
和toBytes
這兩個(gè)方法就是byte[]和String互相轉(zhuǎn)換的方法。
單元測(cè)試:
public class HexUtilTest {
@Test
public void toHex() {
byte[] bytes = {(byte) 0, (byte) 1, (byte) 2, (byte) 3, (byte) 255, (byte) 254};
System.out.println(HexUtil.toHex(bytes));
}
@Test
public void toBytes() throws Exception {
byte[] bytes = {(byte) 0, (byte) 1, (byte) 2, (byte) 3};
String s = HexUtil.toHex(bytes);
byte[] result = HexUtil.toBytes(s);
Assert.assertArrayEquals(bytes, result);
}
}
當(dāng)然,你也可以用Apache的
commons-codec
包的Hex直接來編碼/解碼。
1.2 Base64
一個(gè)byte有8位,3個(gè)字節(jié)編碼為4個(gè)字符,這樣這4個(gè)byte事實(shí)上只有6位是有效的,也就是最多能有64種可能,那么我們只需要列出64個(gè)可見字符來代表這64種情況就可以。而Base64標(biāo)準(zhǔn)給出的64個(gè)字符是:
import java.util.Base64;
/**
* 使用JDK1.8提供的Base64來編解碼
* @see java.util.Base64
*/
public class Base64Util {
private static final Base64.Encoder EncoderWithoutPadding = Base64.getEncoder().withoutPadding();
private static final Base64.Encoder URLSafeEncoderWithoutPadding = Base64.getUrlEncoder().withoutPadding();
/**
* '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', '+', '/'
*/
public static byte[] encode(byte[] src) {
return Base64.getEncoder().encode(src);
}
/**
* 不會(huì)用=來填充
*/
public static byte[] encodeWithoutPadding(byte[] src) {
return EncoderWithoutPadding.encode(src);
}
public static byte[] decode(byte[] src) {
return Base64.getDecoder().decode(src);
}
/**
* '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', '-', '_'
*/
public static byte[] encodeURLSafe(byte[] src) {
return Base64.getUrlEncoder().encode(src);
}
/**
* 不會(huì)用=來填充
*/
public static byte[] encodeURLSafeWithoutPadding(byte[] src) {
return URLSafeEncoderWithoutPadding.encode(src);
}
public static byte[] decodeURLSafe(byte[] src) {
return Base64.getUrlDecoder().decode(src);
}
}
單元測(cè)試:
public class Base64UtilTest {
private static final String originStr = "Hello, world.你好,世界。" + System.currentTimeMillis();
@Test
public void encodeDecode() {
System.out.println("encodeDecode");
byte[] srcBytes = originStr.getBytes(Charset.forName("UTF-8"));
byte[] encodeBytes = Base64Util.encode(srcBytes);
String encodeStr = new String(encodeBytes, Charset.forName("UTF-8"));
System.out.println(encodeStr);
byte[] decodeBytes = Base64Util.decode(encodeBytes);
String decodeStr = new String(decodeBytes, Charset.forName("UTF-8"));
System.out.println(decodeStr);
Assert.assertEquals(originStr, decodeStr);
}
@Test
public void encodeDecodeWithoutPadding() {
System.out.println("encodeDecodeWithoutPadding");
byte[] srcBytes = originStr.getBytes(Charset.forName("UTF-8"));
byte[] encodeBytes = Base64Util.encodeWithoutPadding(srcBytes);
String encodeStr = new String(encodeBytes, Charset.forName("UTF-8"));
System.out.println(encodeStr);
byte[] decodeBytes = Base64Util.decode(encodeBytes);
String decodeStr = new String(decodeBytes, Charset.forName("UTF-8"));
System.out.println(decodeStr);
Assert.assertEquals(originStr, decodeStr);
}
@Test
public void encodeDecodeURLSafe() {
System.out.println("encodeDecodeURLSafe");
byte[] srcBytes = originStr.getBytes(Charset.forName("UTF-8"));
byte[] encodeBytes = Base64Util.encodeURLSafe(srcBytes);
String encodeStr = new String(encodeBytes, Charset.forName("UTF-8"));
System.out.println(encodeStr);
byte[] decodeBytes = Base64Util.decodeURLSafe(encodeBytes);
String decodeStr = new String(decodeBytes, Charset.forName("UTF-8"));
System.out.println(decodeStr);
Assert.assertEquals(originStr, decodeStr);
}
@Test
public void encodeDecodeURLSafeWithoutPadding() {
System.out.println("encodeDecodeURLSafeWithoutPadding");
byte[] srcBytes = originStr.getBytes(Charset.forName("UTF-8"));
byte[] encodeBytes = Base64Util.encodeURLSafeWithoutPadding(srcBytes);
String encodeStr = new String(encodeBytes, Charset.forName("UTF-8"));
System.out.println(encodeStr);
byte[] decodeBytes = Base64Util.decodeURLSafe(encodeBytes);
String decodeStr = new String(decodeBytes, Charset.forName("UTF-8"));
System.out.println(decodeStr);
Assert.assertEquals(originStr, decodeStr);
}
}
1.3 Base32
和Base64很類似,一個(gè)byte有8位,5個(gè)字節(jié)編碼為8個(gè)字符(Base64是3個(gè)字符轉(zhuǎn)換成4個(gè)),這樣這8個(gè)byte事實(shí)上只有5位是有效的,也就是最多能有32種可能,那么我們只需要列出32個(gè)可見字符來代表這32種情況就可以。而Base32標(biāo)準(zhǔn)給出的32個(gè)字符是:
這種編碼方式一般不會(huì)用,因?yàn)樗虰ase64沒有什么區(qū)別,但是編碼后字符的長度卻比Base64編碼結(jié)果長約20%。所以如果沒有特殊需求,一般都會(huì)使用Base64。
2. 消息摘要
消息摘要算法一般用來驗(yàn)證數(shù)據(jù)完整性,所以網(wǎng)上提供文件下載的資方,一般也會(huì)提供一個(gè)摘要值來讓我們驗(yàn)證下載后的文件和真實(shí)的文件是否一致,這樣有效避免文件被篡改。想一想你下載一個(gè)abc.exe文件被植入木馬,你卻無法辨別,是多么的可怕,所以建議對(duì)于關(guān)鍵文件都要做一下數(shù)據(jù)一致性校驗(yàn)。
下圖就是MySQL官網(wǎng)下載頁面同時(shí)提供了文件的MD5摘要:
下面給出幾種常見消息摘要算法的實(shí)現(xiàn),這幾種都可以直接使用JDK實(shí)現(xiàn),當(dāng)然你也可以使用Apache的commons-codec
包來實(shí)現(xiàn),這個(gè)包其實(shí)也是封裝的JDK的API。
2.1 MD5
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MD5 {
public static byte[] digest(byte[] data) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("MD5");
digest.update(data);
return digest.digest();
}
}
單元測(cè)試:
public class MD5Test {
@Test
public void digest() throws NoSuchAlgorithmException {
String data = "Hello, world.MD5消息摘要";
byte[] dataBytes = data.getBytes(Charset.forName("UTF-8"));
byte[] digestBytes = MD5.digest(dataBytes);
System.out.println(HexUtil.toHex(digestBytes));
System.out.println(Hex.encodeHexString(digestBytes));
}
@Test
public void commonsCodecDigest() {
String data = "Hello, world.MD5消息摘要";
byte[] dataBytes = data.getBytes(Charset.forName("UTF-8"));
byte[] digestBytes = DigestUtils.md5(dataBytes);
System.out.println(HexUtil.toHex(digestBytes));
System.out.println(Hex.encodeHexString(digestBytes));
}
}
2.2 SHA
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class SHA {
public static byte[] sha1(byte[] data) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("SHA");
digest.update(data);
return digest.digest();
}
public static byte[] sha256(byte[] data) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
digest.update(data);
return digest.digest();
}
public static byte[] sha512(byte[] data) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("SHA-512");
digest.update(data);
return digest.digest();
}
}
單元測(cè)試:
import cn.dubby.encrypt.encoding.HexUtil;
import cn.dubby.encrypt.signature.MD5;
import cn.dubby.encrypt.signature.SHA;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.junit.Test;
import java.nio.charset.Charset;
import java.security.NoSuchAlgorithmException;
public class SHATest {
@Test
public void sha() throws NoSuchAlgorithmException {
String data = "Hello, world.MD5消息摘要";
byte[] dataBytes = data.getBytes(Charset.forName("UTF-8"));
byte[] digestBytes = SHA.sha1(dataBytes);
System.out.println(HexUtil.toHex(digestBytes));
System.out.println(Hex.encodeHexString(digestBytes));
}
@Test
public void sha256() throws NoSuchAlgorithmException {
String data = "Hello, world.MD5消息摘要";
byte[] dataBytes = data.getBytes(Charset.forName("UTF-8"));
byte[] digestBytes = SHA.sha256(dataBytes);
System.out.println(HexUtil.toHex(digestBytes));
System.out.println(Hex.encodeHexString(digestBytes));
}
@Test
public void sha512() throws NoSuchAlgorithmException {
String data = "Hello, world.MD5消息摘要";
byte[] dataBytes = data.getBytes(Charset.forName("UTF-8"));
byte[] digestBytes = SHA.sha512(dataBytes);
System.out.println(HexUtil.toHex(digestBytes));
System.out.println(Hex.encodeHexString(digestBytes));
}
@Test
public void commonsCodecSHA() {
String data = "Hello, world.MD5消息摘要";
byte[] dataBytes = data.getBytes(Charset.forName("UTF-8"));
System.out.println(DigestUtils.sha1Hex(dataBytes));
System.out.println(DigestUtils.sha256Hex(dataBytes));
System.out.println(DigestUtils.sha512Hex(dataBytes));
}
}
2.3 MAC
/**
* JDK實(shí)現(xiàn)了HmacMD5,HmacSHA1,HmacSHA256,HmacSHA384,HmacMD2,HmacMD4,HmacSHA224
*/
public class MAC {
public static byte[] hmacMD5(byte[] key, byte[] data) throws NoSuchAlgorithmException, InvalidKeyException {
SecretKey secretKey = new SecretKeySpec(key, "HmacMD5");
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
mac.init(secretKey);
return mac.doFinal(data);
}
public static byte[] hmacSHA256(byte[] key, byte[] data) throws NoSuchAlgorithmException, InvalidKeyException {
SecretKey secretKey = new SecretKeySpec(key, "HmacSHA256");
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
mac.init(secretKey);
return mac.doFinal(data);
}
public static byte[] hmacSHA384(byte[] key, byte[] data) throws NoSuchAlgorithmException, InvalidKeyException {
SecretKey secretKey = new SecretKeySpec(key, "HmacSHA384");
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
mac.init(secretKey);
return mac.doFinal(data);
}
}
單元測(cè)試:
import cn.dubby.encrypt.encoding.HexUtil;
import cn.dubby.encrypt.signature.MAC;
import org.junit.Test;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
public class MACTest {
private static final String data = "Hello, world.";
@Test
public void hmacMD5() throws NoSuchAlgorithmException, InvalidKeyException {
KeyGenerator keyGenerator = KeyGenerator.getInstance("HmacMD5");
SecretKey secretKey = keyGenerator.generateKey();
byte[] keyByte = secretKey.getEncoded();
byte[] result = MAC.hmacMD5(keyByte, data.getBytes(Charset.forName("UTF-8")));
System.out.println(HexUtil.toHex(result));
result = MAC.hmacMD5(keyByte, data.getBytes(Charset.forName("UTF-8")));
System.out.println(HexUtil.toHex(result));
}
@Test
public void hmacSHA256() throws NoSuchAlgorithmException, InvalidKeyException {
KeyGenerator keyGenerator = KeyGenerator.getInstance("HmacSHA256");
SecretKey secretKey = keyGenerator.generateKey();
byte[] keyByte = secretKey.getEncoded();
byte[] result = MAC.hmacSHA256(keyByte, data.getBytes(Charset.forName("UTF-8")));
System.out.println(HexUtil.toHex(result));
result = MAC.hmacSHA256(keyByte, data.getBytes(Charset.forName("UTF-8")));
System.out.println(HexUtil.toHex(result));
}
@Test
public void hmacSHA384() throws NoSuchAlgorithmException, InvalidKeyException {
KeyGenerator keyGenerator = KeyGenerator.getInstance("HmacSHA384");
SecretKey secretKey = keyGenerator.generateKey();
byte[] keyByte = secretKey.getEncoded();
byte[] result = MAC.hmacSHA384(keyByte, data.getBytes(Charset.forName("UTF-8")));
System.out.println(HexUtil.toHex(result));
result = MAC.hmacSHA384(keyByte, data.getBytes(Charset.forName("UTF-8")));
System.out.println(HexUtil.toHex(result));
}
}