常用加密解密(1)——消息摘要

原文链接:https://www.dubby.cn/detail.html?id=9122

1. 常见编码

为什么要介绍编码呢?因为在Java中,加密/解密都是对byte的操作,一段文本的byte[]经过加密后的bye[]可能是一段很随机的字节数组,如果不经过编码直接转换成string,那么很有可能是不可见字符,而不可见字符不管是保存还是传输都会很麻烦,所以一般来说,加密后都会再编码成可见字符保存/传输。

1.1 Hex

一个byte有8位,而Hex编码就是把一个byte拆成2个byte,一个取前4位,另一个取后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;
    }

}

其中toHextoBytes这两个方法就是byte[]和String互相转换的方法。

单元测试:

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);
    }
}

当然,你也可以用Apache的commons-codec包的Hex直接来编码/解码。

1.2 Base64

一个byte有8位,3个字节编码为4个字符,这样这4个byte事实上只有6位是有效的,也就是最多能有64种可能,那么我们只需要列出64个可见字符来代表这64种情况就可以。而Base64标准给出的64个字符是:

image
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);
    }

    /**
     * 不会用=来填充
     */
    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);
    }

    /**
     * 不会用=来填充
     */
    public static byte[] encodeURLSafeWithoutPadding(byte[] src) {
        return URLSafeEncoderWithoutPadding.encode(src);
    }

    public static byte[] decodeURLSafe(byte[] src) {
        return Base64.getUrlDecoder().decode(src);
    }

}

单元测试:

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很类似,一个byte有8位,5个字节编码为8个字符(Base64是3个字符转换成4个),这样这8个byte事实上只有5位是有效的,也就是最多能有32种可能,那么我们只需要列出32个可见字符来代表这32种情况就可以。而Base32标准给出的32个字符是:

image

这种编码方式一般不会用,因为他和Base64没有什么区别,但是编码后字符的长度却比Base64编码结果长约20%。所以如果没有特殊需求,一般都会使用Base64。

2. 消息摘要

消息摘要算法一般用来验证数据完整性,所以网上提供文件下载的资方,一般也会提供一个摘要值来让我们验证下载后的文件和真实的文件是否一致,这样有效避免文件被篡改。想一想你下载一个abc.exe文件被植入木马,你却无法辨别,是多么的可怕,所以建议对于关键文件都要做一下数据一致性校验。

下图就是MySQL官网下载页面同时提供了文件的MD5摘要:

image

下面给出几种常见消息摘要算法的实现,这几种都可以直接使用JDK实现,当然你也可以使用Apache的commons-codec包来实现,这个包其实也是封装的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();
    }
}

单元测试:

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

}

单元测试:

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实现了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);
    }
}

单元测试:

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));
    }
}

你可能感兴趣的:(常用加密解密(1)——消息摘要)