1. 程式人生 > >Java 加解密技術系列之 DES

Java 加解密技術系列之 DES



前幾篇文章講的都是單向加密演算法,其中涉及到了 BASE64、MD5、SHA、HMAC 等幾個比較常見的加解密演算法。這篇文章,以及後面幾篇,打算介紹幾個對稱加密演算法,比如:DES、3DES(TripleDES)、AES 等。那麼,這篇文章主要是對 DES 大概講一下。

背景

在討論 DES 之前,首先了解一下什麼是對稱加密演算法吧。對於對稱加密演算法,他應用的時間比較早,技術相對來說比較成熟,在對稱加密演算法中,資料發信方將明文(原始資料)和加密金鑰一起經過特殊加密演算法處理後,使其變成複雜的加密密文傳送出去。收信方收到密文後,若想解讀原文,則需要使用加密用過的金鑰及相同演算法的逆演算法對密文進行解密,才能使其恢復成可讀明文。

在對稱加密演算法中,使用的金鑰只有一個,發收信雙方都使用這個金鑰對資料進行加密和解密,這就要求解密方事先必須知道加密金鑰。對稱加密演算法的特點是演算法公開、計算量小。不足之處是,交易雙方都使用同樣鑰匙,安全性得不到保證。

概念

那麼,什麼是 DES?他是怎麼來的?相信很多人都很感興趣,因為以前在開發的時候,對進度的要求比較嚴,很多時候根本就沒有時間來了解這些東西。因此,今天專門來研究研究這個東西。 DES,全稱為“Data Encryption Standard”,中文名為“資料加密標準”,是一種使用金鑰加密的塊演算法。DES 演算法為密碼體制中的對稱密碼體制,又被稱為美國資料加密標準,是 1972 年美國 IBM 公司研製的對稱密碼體制加密演算法。 明文按 64 位進行分組,金鑰長 64 位,金鑰事實上是 56 位參與 DES 運算(第8、16、24、32、40、48、56、64 位是校驗位, 使得每個金鑰都有奇數個 1)分組後的明文組和 56 位的金鑰按位替代或交換的方法形成密文組的加密方法。

基本原理

入口引數有三個:key、data、mode。key 為加密解密使用的金鑰,data 為加密 解密的資料,mode 為其工作模式。當模式為加密模式時,明文按照 64 位進行分組,形成明文組,key 用於對資料加密,當模式為解密模式時,key 用於對資料解密。實際運用中,金鑰只用到了 64 位中的 56 位,這樣才具有高的安全性。

主要流程

DES 演算法把 64 位的明文輸入塊變為 64 位的密文輸出塊,它所使用的金鑰也是 64 位,其演算法主要分為兩步:
  • 初始置換
其功能是把輸入的 64 位資料塊按位重新組合,並把輸出分為 L0、R0 兩部分,每部分各長 32 位,其置換規則為將輸入的第 58 位換到第一位,第 50 位換到第 2 位 …… 依此類推,最後一位是原來的第 7 位。L0、R0 則是換位輸出後的兩部分,L0 是輸出的左 32 位,R0 是右  32 位,例:設定換前的輸入值為 D1 D2 D3 …… D64,則經過初始置換後的結果為:L0 = D58 D50 …… D8;R0 = D57 D49 …… D7。
  • 逆置換
經過 16 次迭代運算後,得到 L16、R16,將此作為輸入,進行逆置換,逆置換正好是初始置換的逆運算,由此即得到密文輸出。 整個演算法 的主流程圖如下:

分組模式

  • ECB模式
ECB,中文名“電子密碼本模式”,是最古老、最簡單的模式,將加密的資料分成若干組,每組的大小跟加密金鑰長度相同。然後每組都用相同的金鑰加密,比如 DES 演算法,如果最後一個分組長度不夠 64 位,要補齊 64 位。如圖所示:
  • CBC模式
CBC,中文名“加密塊鏈模式”,與 ECB 模式最大的不同是加入了初始向量。他的特點是,每次加密的密文長度為 64位 ( 8 個位元組),當相同的明文使用相同的金鑰和初始向量的時候 CBC 模式總是產生相同的密文。
  • CFB模式
CFB,中文名“加密反饋模式”,加密反饋模式克服了需要等待 8 個位元組才能加密的缺點,它採用了分組密碼作為流密碼的金鑰流生成器。他的特點是,每次加密的 Pi 和 Ci 不大於 64 位;加密演算法和解密演算法相同,不能適用於公鑰演算法;使用相同的金鑰和初始向量的時候,相同明文使用 CFB 模式加密輸出相同的密文;可以使用不同的初始化變數使相同的明文產生不同的密文,防止字典攻擊;加密強度依賴於金鑰長度;加密塊長度過小時,會增加迴圈的數量,導致開銷增加;加密塊長度應時 8 位的整數倍(即位元組為單位);一旦某位資料出錯,會影響目前和其後 8 個塊的資料。
  • OFB模式
OFB,中文名“輸出反饋模式”,與 CFB 模式不同之處在於, 加密位移暫存器與密文無關了,僅與加密 key 和加密演算法有關,做法是不再把密文輸入到加密移位暫存器,而是把輸出的分組密文(Oi)輸入到一位暫存器。因為密文沒有參與鏈操作,所以使得 OFB 模式更容易受到攻擊;不會進行錯誤傳播,某位密文發生錯誤,只會影響該位對應的明文,而不會影響別的位;不是自同步的,如果加密和解密兩個操作失去同步,那麼系統需要重新初始化;每次重新同步時,應使用不同的初始向量。可以避免產生相同的位元流,避免“已知明文”攻擊。
  • CTR模式
CTR,中文名“計數模式”,是對一系列輸入資料塊(稱為計數)進行加密,產生一系列的輸出塊,輸出塊與明文異或得到密文。對於最後的資料塊,可能是長 u 位的區域性資料塊,這 u 位就將用於異或操作,而剩下的 b-u 位將被丟棄(b表示塊的長度)。

程式碼實現

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

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

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;

/**
 * Created by xiang.li on 2015/2/28.
 * DES 加解密工具類
 *
 * <pre>
 * 支援 DES、DESede(TripleDES,就是3DES)、AES、Blowfish、RC2、RC4(ARCFOUR)
 * DES                  key size must be equal to 56
 * DESede(TripleDES)    key size must be equal to 112 or 168
 * AES                  key size must be equal to 128, 192 or 256,but 192 and 256 bits may not be available
 * Blowfish             key size must be multiple of 8, and can only range from 32 to 448 (inclusive)
 * RC2                  key size must be between 40 and 1024 bits
 * RC4(ARCFOUR)         key size must be between 40 and 1024 bits
 * 具體內容 需要關注 JDK Document http://.../docs/technotes/guides/security/SunProviders.html
 * </pre>
 */
public class DES {
    /**
     * 定義加密方式
     */
    private final static String KEY_DES = "DES";
    private final static String KEY_AES = "AES";    // 測試

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

    /**
     * 初始化金鑰
     * @return
     */
    public static String init() {
        return init(null);
    }

    /**
     * 初始化金鑰
     * @param seed 初始化引數
     * @return
     */
    public static String init(String seed) {
        SecureRandom secure = null;
        String str = "";
        try {
            if (null != secure) {
                // 帶引數的初始化
                secure = new SecureRandom(decryptBase64(seed));
            } else {
                // 不帶引數的初始化
                secure = new SecureRandom();
            }

            KeyGenerator generator = KeyGenerator.getInstance(KEY_DES);
            generator.init(secure);

            SecretKey key = generator.generateKey();
            str = encryptBase64(key.getEncoded());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return str;
    }

    /**
     * 轉換金鑰
     * @param key 金鑰的位元組陣列
     * @return
     */
    private static Key byteToKey(byte[] key) {
        SecretKey secretKey = null;
        try {
            DESKeySpec dks = new DESKeySpec(key);
            SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_DES);
            secretKey = factory.generateSecret(dks);

            // 當使用其他對稱加密演算法時,如AES、Blowfish等演算法時,用下述程式碼替換上述三行程式碼
//            secretKey = new SecretKeySpec(key, KEY_DES);
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        }
        return secretKey;
    }

    /**
     * DES 解密
     * @param data 需要解密的字串
     * @param key 金鑰
     * @return
     */
    public static String decryptDES(String data, String key) {
        // 驗證傳入的字串
        if (Strings.isNullOrEmpty(data)) {
            return "";
        }
        // 呼叫解密方法完成解密
        byte[] bytes = decryptDES(hexString2Bytes(data), key);
        // 將得到的位元組陣列變成字串返回
        return new String(bytes);
    }

    /**
     * DES 解密
     * @param data 需要解密的位元組陣列
     * @param key 金鑰
     * @return
     */
    public static byte[] decryptDES(byte[] data, String key) {
        byte[] bytes = null;
        try {
            Key k = byteToKey(decryptBase64(key));
            Cipher cipher = Cipher.getInstance(KEY_DES);
            cipher.init(Cipher.DECRYPT_MODE, k);
            bytes = cipher.doFinal(data);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bytes;
    }

    /**
     * DES 加密
     * @param data 需要加密的字串
     * @param key 金鑰
     * @return
     */
    public static String encryptDES(String data, String key) {
        // 驗證傳入的字串
        if (Strings.isNullOrEmpty(data)) {
            return "";
        }
        // 呼叫加密方法完成加密
        byte[] bytes = encryptDES(data.getBytes(), key);
        // 將得到的位元組陣列變成字串返回
        return byteArrayToHexString(bytes);
    }

    /**
     * DES 加密
     * @param data 需要加密的位元組陣列
     * @param key 金鑰
     * @return
     */
    public static byte[] encryptDES(byte[] data, String key) {
        byte[] bytes = null;
        try {
            Key k = byteToKey(decryptBase64(key));
            Cipher cipher = Cipher.getInstance(KEY_DES);
            cipher.init(Cipher.ENCRYPT_MODE, k);
            bytes = cipher.doFinal(data);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bytes;
    }


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

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

    /**
     * 將一個位元組轉化成十六進位制形式的字串
     * @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 hexstr 十六進位制字串
     * @return
     */
    public static byte[] hexString2Bytes(String hexstr) {
        byte[] b = new byte[hexstr.length() / 2];
        int j = 0;
        for (int i = 0; i < b.length; i++) {
            char c0 = hexstr.charAt(j++);
            char c1 = hexstr.charAt(j++);
            b[i] = (byte) ((parse(c0) << 4) | parse(c1));
        }
        return b;
    }

    /**
     * 轉換字元型別資料為整型資料
     * @param c 字元
     * @return
     */
    private static int parse(char c) {
        if (c >= 'a')
            return (c - 'a' + 10) & 0x0f;
        if (c >= 'A')
            return (c - 'A' + 10) & 0x0f;
        return (c - '0') & 0x0f;
    }

    /**
     * 測試方法
     * @param args
     */
    public static void main(String[] args) {
        String key = DES.init();
        System.out.println("DES金鑰:\n" + key);

        String word = "123";
        

        String encWord = encryptDES(word, key);

        System.out.println(word + "\n加密後:\n" + encWord);
        System.out.println(word + "\n解密後:\n" + decryptDES(encWord, key));
    }
}</span><span style="font-size: 14px;">
</span></span>

結束語

到這裡,這篇文章也就差不多要結束了,希望以上的內容對各位看官有稍許的幫助,哪怕一點也好。其實,在日常的開發中,如果不是進度控制的特別嚴格,對於這些原理性的東西,我們還是需要知道的,對於那些細節的東西,可以不用死記硬背,有網的話,隨用隨查就可以了。但這個前提是,原理性的東西必須要懂,知道了原理,就會有解決思路,有了思路,解決問題是遲早的事,細節嘛,不用那麼糾結,做的時候考慮到就行了,畢竟時間是有限的。