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

原文鏈接: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;
    }

}

其中toHextoBytes這兩個(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è)字符是:

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

    /**
     * 不會(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è)字符是:

image

這種編碼方式一般不會(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摘要:

image

下面給出幾種常見消息摘要算法的實(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));
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,283評(píng)論 6 530
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 97,947評(píng)論 3 413
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,094評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,485評(píng)論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,268評(píng)論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,817評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,906評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,039評(píng)論 0 285
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,551評(píng)論 1 331
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,502評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,662評(píng)論 1 366
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,188評(píng)論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,907評(píng)論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,304評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,563評(píng)論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,255評(píng)論 3 389
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,637評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容

  • 這里先簡單介紹單向散列函數(shù)、消息摘要和哈希碰撞的的概念 單向散列函數(shù): 將任意長度的信息轉(zhuǎn)換為較短的固定長度的值,...
    坤_7a1e閱讀 3,509評(píng)論 0 0
  • 1. ASCII 編碼 ASCII(American Standard Code for Information ...
    s酸菜閱讀 8,701評(píng)論 0 8
  • 了解CTF的朋友們,肯定對(duì)一種題型不陌生——Misc,Misc里面有各種各樣的編碼,眼花繚亂的文本通過層層解碼后得...
    Ackerzy閱讀 11,804評(píng)論 1 30
  • 字符是用戶可以讀寫的最小單位。計(jì)算機(jī)所能支持的字符組成的集合,就叫做字符集。字符集通常以二維表的形式存在。二維表的...
    劉惜有閱讀 8,148評(píng)論 2 14
  • 《如何閱讀一本書》的筆記-第31頁 ]章節(jié)名:閱讀的層次 頁碼:第31頁 Q1:這個(gè)章節(jié)介紹了幾個(gè)人物的觀點(diǎn)? Q...
    欣然閱讀閱讀 274評(píng)論 0 0