天天看點

Java 加解密技術系列之 HMAC序背景正文代碼實作結束語

上一篇文章中簡單的介紹了第二種單向加密算法 — — SHA,同時也給出了 SHA-1 的 Java 代碼。有這方面需求的童鞋可以去參考一下。今天這篇文章将要介紹第三種單向加密算法 — — HMAC,其實,這種加密算法并不是那麼常用,最起碼,在我寫系列部落格之前,我是沒有聽說過它的。當然,這并不是說 HMAC 不出名,肯定是我孤落寡聞了。

背景

之是以在單向加密算法中介紹 HMAC 這種“不常見的”算法,一是因為“沒見過”,二是因為畢竟是同屬于單向加密算法中的一種,而且還是基于密鑰的雜湊演算法的認證協定。是以,還是決定簡單的介紹一下。

正文

HMAC,全稱為“Hash Message Authentication Code”,中文名“散列消息驗證碼”,主要是利用雜湊演算法,以一個密鑰和一個消息為輸入,生成一個消息摘要作為輸出。一般的,消息驗證碼用于驗證傳輸于兩個共  同享有一個密鑰的機關之間的消息。HMAC 可以與任何疊代散列函數捆綁使用。MD5 和 SHA-1 就是這種散列函數。 HMAC 還可以使用一個用于計算和确認消息鑒别值的密鑰。

HMAC,散列消息驗證碼,是基于密鑰的 Hash 算法的認證協定。它的實作原理是,用公開函數和密鑰産生一個固定長度的值作為認證辨別,用這個辨別鑒别消息的完整性。使用一個密鑰生成一個固定大小的小資料塊,即 MAC,并将其加入到消息中,然後傳輸。接收方利用與發送方共享的密鑰進行鑒别認證等。 

這種結構的主要作用是:

  • 不用修改就可以使用适合的散列函數,而且散列函數在軟體方面表現的很好, 并且源碼是公開和通用的。
  • 可以保持散列函數原有的性能而不緻使其退化。
  • 可以使得基于合理的關于底層散列函數假設的消息鑒别機制的加密強度分析 便于了解。
  • 當發現或需要運算速度更快或更安全的散列函數時,可以很容易的實作底層 散列函數的替換。

定義 HMAC 需要一個加密用散列函數(表示為 H)和一個密鑰 K。我們假設 H 是  一個将資料塊用一個基本的疊代壓縮函數來加密的散列函數。我們用 B 來表示資料塊 的字長。(以上提到的散列函數的分割資料塊字長 B = 64),用 L 來表示散列函數的 輸出資料字長(MD5中 L = 16 , SHA-1 中 L = 20)。鑒别密鑰的長度可以是小于等于數 據塊字長的任何正整數值。應用程式中使用的密鑰長度若是比 B 大,則首先用使用散列 函數 H 作用于它,然後用 H 輸出的 L 長度字元串作為在 HMAC 中實際使用的密鑰。 一般情況下,推薦的最小密鑰 K 長度是 L 個字長。(與 H 的輸出資料長度相等)。

我們将定義兩個固定且不同的字元串 ipad,opad: (‘i’,‘o’表示内部與外部) 

  • ipad = the byte 0x36 repeated B times
  • opad = the byte 0x5C repeated B times

計算‘text’的 HMAC:

  • H (K XOR opad, H (K XOR ipad, text))

計算步驟

  • 在密鑰 K 後面添加 0 來建立一個子長為 B 的字元串。(例如,如果 K 的字長是 20 位元組,B=60 位元組,則 K 後會加入 44 個零位元組0x00)
  • 将上一步生成的 B 字長的字元串與 ipad 做異或運算
  • 将資料流 text 填充至第二步的結果字元串中
  • 用 H 作用于第三步生成的資料流
  • 将第一步生成的 B 字長字元串與 opad 做異或運算
  • 再将第四步的結果填充進第五步的結果中
  • 用 H 作用于第六步生成的資料流,輸出最終結果

密鑰

用于 HMAC 的密鑰可以是任意長度(比 B 長的密鑰将首先被 H 處理)。但當密鑰  長度小于 L 時,會降低函數的安全強度。長度大于  L 的密鑰也是可以的,但額外的長度并不能顯著的提高函數的安全強度。

密鑰必須随機選取(或使用強大的基于随機種子的僞随機生成方法),并且要周期 性的更新。目前的攻擊沒有指出一個有效的更換密鑰的頻率,因為那些攻擊實際上并 不可行。然而,周期性更新密鑰是一個對付函數和密鑰所存在的潛在缺陷的基本 的安全措施,并可以降低洩漏密鑰帶來的危害。

代碼實作

<span style="font-family:Comic Sans MS;font-size:12px;">package com.sica.hmac;

import com.google.common.base.Strings;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.NoSuchAlgorithmException;

/**
 * Created by xiang.li on 2015/2/27.
 */
public class HMAC {
    /**
     * 定義加密方式
     * MAC算法可選以下多種算法
     * <pre>
     * HmacMD5
     * HmacSHA1
     * HmacSHA256
     * HmacSHA384
     * HmacSHA512
     * </pre>
     */
    private final static String KEY_MAC = "HmacMD5";

    /**
     * 全局數組
     */
    private final static String[] hexDigits = { "0", "1", "2", "3", "4", "5",
            "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };

    /**
     * 構造函數
     */
    public HMAC() {

    }

    /**
     * BASE64 加密
     * @param key 需要加密的位元組數組
     * @return 字元串
     * @throws Exception
     */
    public static String encryptBase64(byte[] key) throws Exception {
        return (new BASE64Encoder()).encodeBuffer(key);
    }

    /**
     * BASE64 解密
     * @param key 需要解密的字元串
     * @return 位元組數組
     * @throws Exception
     */
    public static byte[] decryptBase64(String key) throws Exception {
        return (new BASE64Decoder()).decodeBuffer(key);
    }

    /**
     * 初始化HMAC密鑰
     * @return
     */
    public static String init() {
        SecretKey key;
        String str = "";
        try {
            KeyGenerator generator = KeyGenerator.getInstance(KEY_MAC);
            key = generator.generateKey();
            str = encryptBase64(key.getEncoded());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return str;
    }

    /**
     * HMAC加密
     * @param data 需要加密的位元組數組
     * @param key 密鑰
     * @return 位元組數組
     */
    public static byte[] encryptHMAC(byte[] data, String key) {
        SecretKey secretKey;
        byte[] bytes = null;
        try {
            secretKey = new SecretKeySpec(decryptBase64(key), KEY_MAC);
            Mac mac = Mac.getInstance(secretKey.getAlgorithm());
            mac.init(secretKey);
            bytes = mac.doFinal(data);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bytes;
    }

    /**
     * HMAC加密
     * @param data 需要加密的字元串
     * @param key 密鑰
     * @return 字元串
     */
    public static String encryptHMAC(String data, String key) {
        if (Strings.isNullOrEmpty(data)) {
            return null;
        }
        byte[] bytes = encryptHMAC(data.getBytes(), key);
        return byteArrayToHexString(bytes);
    }


    /**
     * 将一個位元組轉化成十六進制形式的字元串
     * @param b 位元組數組
     * @return 字元串
     */
    private static String byteToHexString(byte b) {
        int ret = b;
        //System.out.println("ret = " + ret);
        if (ret < 0) {
            ret += 256;
        }
        int m = ret / 16;
        int n = ret % 16;
        return hexDigits[m] + hexDigits[n];
    }

    /**
     * 轉換位元組數組為十六進制字元串
     * @param bytes 位元組數組
     * @return 十六進制字元串
     */
    private static String byteArrayToHexString(byte[] bytes) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < bytes.length; i++) {
            sb.append(byteToHexString(bytes[i]));
        }
        return sb.toString();
    }

    /**
     * 測試方法
     * @param args
     */
    public static void main(String[] args) throws Exception {
        String key = HMAC.init();
        System.out.println("Mac密鑰:\n" + key);
        String word = "123";
        System.out.println(encryptHMAC(word, key));
    }
}</span>
           

結束語

看完這篇文章之後,HMAC 你已經了解了很多了,以後再遇到這個名詞,當然你也可以說出個一二三來。不過,在應用中,或許一般情況下用不到,如果考慮安全方面的因素,我想,這種不可逆的加密算法還是不錯的,因為你需要額外提供一組密鑰,而這組密鑰對于外人來說是不知道的,是以,安全性相比較來說還是很可靠的。