常用加密解密(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));
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容