天天看點

關于加密與解密、簽名與驗簽前言沒有硝煙的戰場——淺談密碼技術參考

前言

面對 MD5、SHA、DES、AES、RSA 等等這些名詞你是否有很多問号?這些名詞都是什麼?還有什麼公鑰加密、私鑰解密、私鑰簽名、公鑰驗簽。這些都什麼鬼?

或許在你日常工作沒有聽說過這些名詞,但是一旦你要設計一個對外通路的接口,或者安全性要求高的系統,那麼必然會接觸到這些名詞。是以加解密、加簽驗簽對于一個合格的程式員來說是必須要掌握的一個概念。

那麼加解密相關的密碼學真的離我們很遙遠嗎?

其實生活中有很多常見的場景其實都用到了密碼學的相關知識,我們不要把它想得太難,例如在《睡在我上鋪的兄弟》這一段中作弊繞密碼中,小癟三代表 A,小赤佬代表 B,唉呀媽呀代表 C,坑爹呀是 D,這一段繞密碼其實也是密碼學的一種。有興趣的小夥伴可以看一下這一片段繞密碼片段。是以其實密碼學與我們生活息息相關,接下來我們就一文徹底搞懂這些概念。

沒有硝煙的戰場——淺談密碼技術

沒有根基也許可以建一座小屋,但絕對不能造一座堅固的大廈。

密碼這個詞有很多種的解釋,在現代社會如果不接觸程式設計的話,那麼普遍的認為是我們設定的登入密碼、或者是去銀行取錢時輸入的數字。都是我們在注冊時實作給提供服務的一方存儲一組數字,以後我們登入的時候就用這組數字相當于就證明了我們的身份。這個數字通常來說就是叫做密碼。

而我們需要了解的不是上面說的密碼,而是一種 “密碼術”,就是對于要傳遞的資訊按照某種規則進行轉換,進而隐藏資訊的内容。這種方法可以使機密資訊得以在公開的管道傳遞而不洩密。使用這種方法,要經過加密過程。在加密過程中我們需要知道下面的這些概念:

  • 原文:或者叫明文,就是被隐藏的文字
  • 加密法:指隐藏原文的法則
  • 密文:或者叫僞文,指對原文按照加密法處理過後生成的可公開傳遞的文字
  • 密鑰:在加密法中起決定性的因素,可能是數字、詞彙,也可能是一些字母,或者這些東西的組合

加密的結果生成了密文,要想讓接受者能夠讀懂這些密文,那麼就要把加密法以及密鑰告訴接受者,否者接受者無法對密文解密,也就無法讀懂原文。

從曆史的角度來看,密碼學大概可以分為古典密碼學和近現代密碼學兩個階段。兩者以現代資訊技術的誕生為分界點,現在所讨論的密碼學多指的是後者,建立在資訊論和數學成果基礎之上的。

古典密碼學

古典密碼學源自于數千年前,最早在公元前 1900 年左右的古埃及,就出現了通過使用特殊字元和簡單替換式密碼來保護資訊。美索不達米亞平原上曾經出土一個公元前 1500 年左右的泥闆,其上記錄了加密描述的陶瓷器上釉的工藝配方。古希臘時期(公元前 800 ﹣前 146 年)還發明了通過實體手段來隐藏資訊的 “隐寫術”,例如使用牛奶書寫、用蠟覆寫文字等。後來在古羅馬時期還出現了基于替換加密的凱撒密碼,據稱凱撒曾用此方法與其部下通信而得以命名。這些手段多數是采用簡單的機械工具來保護秘密,在今天看來毫無疑問是十分簡陋,很容易猜出來的。嚴格來看,可能都很難稱為密碼科學。

凱撒密碼是當偏移量是 3 的時候,所有的字母都 A 都将被替換成 D,B 變成 E,以此類推。

關于加密與解密、簽名與驗簽前言沒有硝煙的戰場——淺談密碼技術參考

近代密碼學

近代密碼學的研究來自于第一、二次世界大戰中對于軍事通信進行保護和猜出來的需求。1901 年 12 月,意大利的工程師 Guglielmo Marconi(奎裡亞摩 • 馬可尼)成功完成了跨越大西洋的無線電通信的實驗,在全球範圍内引發轟動,推動了無線電通信時代的到來。無線電大大提高了遠端通信的能力,但是它有一個天然的缺陷——很難限制接收方,這就意味着你所傳的資訊有可能被攔截,是以就催生了加密技術的發展。

對于無線電資訊進行加密和解密也直接促進了近現代密碼學和計算機技術的出現。反過來這些科技進步也影響了時代的發展。一戰時期德國外交部長 Arthur Zimmermann(阿瑟 • 齊默爾曼)拉攏墨西哥構成抗美軍事同盟的電報(1917 年 1 月 16 日)被英國情報機構—40 号辦公室破譯,直接導緻了美國的參戰;二戰時期德國使用的恩尼格瑪(Enigma)密碼機(當時最先進的加密裝置)被盟軍成功破譯(1939 年到 1941 年),導緻大西洋戰役德國失敗。據稱,二戰時期光英國從事密碼學研究的人員就達到 7000 人,而他們的成果使二戰結束的時間至少提前了一到兩年時間。

接下來就是可以稱之為是密碼學發展史上裡程碑的事件了。1945 年 9 月 1 日,Claude Elwood Shannon(克勞德 • 艾爾伍德 • 香農)完成了劃時代的内部報告《A Mathematical Theory of Cryptography(密碼術的一個數學理論)》,1949 年 10 月,該報告以《Communication Theory of Secrecy Systems(保密系統的通信理論)》為題在 Bell System Technical Journal(貝爾系統技術期刊)上正式發表。這篇論文首次将密碼學和資訊論聯系到一起,為對稱密碼技術提供了數學基礎。這也标志着近現代密碼學的正式建立。這也是密碼學發展史上的第一座裡程碑性事件。

密碼學發展史上的第二個裡程碑性事件是 DES 的出現。DES 全稱為 Data Encryption Standard,即資料加密标準,是一種使用密鑰加密的分組密碼算法,1977 年被美國聯邦政府的國家标準局确定為聯邦資料處理标準(FIPS),并授權在非密級政府通信中使用,随後該算法在國際上廣泛流傳開來。

密碼學發展史上的第三個裡程碑性事件就是我們區塊鍊中廣泛應用的公鑰密碼,也就是非對稱密碼算法的出現。1976 年 11 月,Whitfield Diffie 和 Martin E.Hellman 在 IEEE Transactions on Information Theory 上發表了論文《New Directions in Cryptography(密碼學的新方向)》,探讨了無需傳輸密鑰的保密通信和簽名認證體系問題,正式開創了現代公鑰密碼學體系的研究。在公鑰密碼發現以前,如果需要保密通信,通信雙方事先要對加解密的算法以及要使用的密鑰進行事先協商,包括送雞毛信,實際上是在傳送密鑰。但自從有了公鑰密碼,需要進行秘密通信的雙方不再需要進行事前的密鑰協商了。公鑰密碼在理論上是不保密的,在實際上是保密的。也就是說,公鑰密碼是可以猜出來的,但需要極長的時間,等到猜出來了,這個秘密也沒有保密的必要了。

上面我們說到了關于近現代的密碼學相關的東西,基本上總結下來我們現在常用的就兩個,一個是對稱加密算法,一個是非對稱加密算法。那麼接下來我們就以介紹這兩個概念為主線引出開題中我們提到的概念。

程式實作

對稱加密算法

對稱加密指的就是加密和解密使用同一個秘鑰,是以叫做對稱加密。對稱加密隻有一個秘鑰,作為私鑰。具體的算法有:DES、3DES、TDEA、Blowfish,RC5,IDEA。但是我們常見的有:DES、AES 等等。

那麼對稱加密的優點是什麼呢?算法公開、計算量小、加密速度快、加密效率高。缺點就是秘鑰的管理和分發是非常困難的,不夠安全。在資料傳送前,發送方和接收方必須商定好秘鑰,然後雙方都必須要儲存好秘鑰,如果一方的秘鑰被洩露了,那麼加密的資訊也就不安全了。另外,每對使用者每次使用對稱加密算法時,都需要使用其他人不知道的唯一秘鑰,這會使得收、發雙方所擁有的的鑰匙數量巨大,秘鑰管理也會成為雙方的負擔。

加密的過程我們可以了解為如下:

  • 加密:原文 + 秘鑰 = 密文
  • 解密:密文 - 秘鑰 = 原文

可以看到兩次過程使用的都是一個秘鑰。用圖簡單表示如下:

關于加密與解密、簽名與驗簽前言沒有硝煙的戰場——淺談密碼技術參考

實戰演練

既然我們知道關于對稱加密算法的相關知識,那麼我們日常用 Java 如何實作對稱加密的加密和解密動作呢?常見的對稱加密算法有:DES、AES 等。

DES

DES 加密算法是一種分組密碼,以 64 位為分組對資料加密,它的密鑰長度是 56 位,加密解密用同一算法。DES 加密算法是對密鑰進行保密,而公開算法,包括加密和解密算法。這樣,隻有掌握了和發送方相同密鑰的人才能解讀由 DES 加密算法加密的密文資料。是以,破譯 DES 加密算法實際上就是搜尋密鑰的編碼。對于 56 位長度的密鑰來說,如果用窮舉法來進行搜尋的話,其運算次數為 2 的 56 次方。

接下來用 Java 實作 DES 加密

private final static String DES = "DES";
    
    public static void main(String[] args) throws Exception {
        String data = "123 456";
        String key = "wang!@#$";
        System.err.println(encrypt(data, key));
        System.err.println(decrypt(encrypt(data, key), key));
    }
    /**
     * Description 根據鍵值進行加密
     * @param data
     * @param key  加密鍵byte數組
     * @return
     * @throws Exception
     */
    public static String encrypt(String data, String key) throws Exception {
        byte[] bt = encrypt(data.getBytes(), key.getBytes());
        String strs = new BASE64Encoder().encode(bt);
        return strs;
    }
    /**
     * Description 根據鍵值進行解密
     * @param data
     * @param key  加密鍵byte數組
     * @return
     * @throws IOException
     * @throws Exception
     */
    public static String decrypt(String data, String key) throws IOException,
            Exception {
        if (data == null)
            return null;
        BASE64Decoder decoder = new BASE64Decoder();
        byte[] buf = decoder.decodeBuffer(data);
        byte[] bt = decrypt(buf,key.getBytes());
        return new String(bt);
    }
    /**
     * Description 根據鍵值進行加密
     * @param data
     * @param key  加密鍵byte數組
     * @return
     * @throws Exception
     */
    private static byte[] encrypt(byte[] data, byte[] key) throws Exception {
        // 生成一個可信任的随機數源
        SecureRandom sr = new SecureRandom();
        // 從原始密鑰資料建立DESKeySpec對象
        DESKeySpec dks = new DESKeySpec(key);
        // 建立一個密鑰工廠,然後用它把DESKeySpec轉換成SecretKey對象
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES);
        SecretKey securekey = keyFactory.generateSecret(dks);
        // Cipher對象實際完成加密操作
        Cipher cipher = Cipher.getInstance(DES);
        // 用密鑰初始化Cipher對象
        cipher.init(Cipher.ENCRYPT_MODE, securekey, sr);
        return cipher.doFinal(data);
    }
    /**
     * Description 根據鍵值進行解密
     * @param data
     * @param key  加密鍵byte數組
     * @return
     * @throws Exception
     */
    private static byte[] decrypt(byte[] data, byte[] key) throws Exception {
        // 生成一個可信任的随機數源
        SecureRandom sr = new SecureRandom();
        // 從原始密鑰資料建立DESKeySpec對象
        DESKeySpec dks = new DESKeySpec(key);
        // 建立一個密鑰工廠,然後用它把DESKeySpec轉換成SecretKey對象
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES);
        SecretKey securekey = keyFactory.generateSecret(dks);
        // Cipher對象實際完成解密操作
        Cipher cipher = Cipher.getInstance(DES);
        // 用密鑰初始化Cipher對象
        cipher.init(Cipher.DECRYPT_MODE, securekey, sr);
        return cipher.doFinal(data);
    }
           

輸出以後可以看到資料被加密了

5fiw/XhRJ0E=
123 456
           

在 Java 中用 DES 加密有一個特殊的地方

  1. 秘鑰設定的長度必須大于等于 8
  2. 秘鑰設定的長度如果大于 8 的話,那麼隻會取前 8 個位元組作為秘鑰

為什麼呢,我們可以看到在初始化

DESKeySpec

類的時候有下面一段,其中 var1 是我們傳的秘鑰。可以看到他進行了截取。隻截取前八個位元組。

public DESKeySpec(byte[] var1, int var2) throws InvalidKeyException {
    
    if (var1.length - var2 < 8) {
        throw new InvalidKeyException("Wrong key size");
    } else {
        this.key = new byte[8];
        System.arraycopy(var1, var2, this.key, 0, 8);
    }
}
           
AES
           

AES 加密算法是密碼學中的進階加密标準,該加密算法采用對稱分組密碼體制,密鑰長度的最少支援為 128、192、256,分組長度 128 位,算法應易于各種硬體和軟體實作。這種加密算法是美國聯邦政府采用的區塊加密标準,AES 标準用來替代原先的 DES,已經被多方分析且廣為全世界所使用。

JCE,Java Cryptography Extension,在早期 JDK 版本中,由于受美國的密碼出口條例限制,Java 中涉及加解密功能的 API 被限制出口,是以 Java 中安全元件被分成了兩部分: 不含加密功能的 JCA(Java Cryptography Architecture )和含加密功能的 JCE(Java Cryptography Extension)。

JCE 的 API 都在 javax.crypto 包下,核心功能包括:加解密、密鑰生成(對稱)、MAC 生成、密鑰協商。

加解密功能由 Cipher 元件提供,其也是 JCE 中最核心的元件。

在設定

Cipher

類的時候有幾個注意點:

  1. Cipher 在使用時需以參數方式指定 transformation
  2. transformation 的格式為 algorithm/mode/padding,其中 algorithm 為必輸項,如: AES/DES/CBC/PKCS5Padding,具體有哪些可看下表
  3. 預設的 mode 為 ECB,預設的 padding 為 PKCS5Padding
  4. 在 block 算法與流加密模式組合時, 需在 mode 後面指定每次處理的 bit 數, 如 DES/CFB8/NoPadding, 如未指定則使用預設值, SunJCE 預設值為 64bits
  5. Cipher 有 4 種操作模式: ENCRYPT_MODE(加密), DECRYPT_MODE(解密), WRAP_MODE(導出 Key), UNWRAP_MODE(導入 Key),初始化時需指定某種操作模式

 秘鑰的可以由我們自己定義,也可以是由 AES 自己生成,當自己定義是需要是要注意:

  1. 根據 AES 規範,可以是 16 位元組、24 位元組和 32 位元組長,分别對應 128 位、192 位和 256 位;
  2. 為便于傳輸,一般對加密後的資料進行 base64 編碼:
public static void main(String[] args) throws Exception {
        /*
         * 此處使用AES-128-ECB加密模式,key需要為16位。
         */
        String cKey = "1234567890123456";
        // 需要加密的字串
        String cSrc = "buxuewushu";
        System.out.println(cSrc);
        // 加密
        String enString = Encrypt(cSrc, cKey);
        System.out.println("加密後的字串是:" + enString);
        // 解密
        String DeString = Decrypt(enString, cKey);
        System.out.println("解密後的字串是:" + DeString);
    }
    // 加密
    public static String Encrypt(String sSrc, String sKey) throws Exception {
        if (sKey == null) {
            System.out.print("Key為空null");
            return null;
        }
        // 判斷Key是否為16位
        if (sKey.length() != 16) {
            System.out.print("Key長度不是16位");
            return null;
        }
        byte[] raw = sKey.getBytes("utf-8");
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");//"算法/模式/補碼方式"
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
        byte[] encrypted = cipher.doFinal(sSrc.getBytes("utf-8"));
        return new Base64().encodeToString(encrypted); //此處使用BASE64做轉碼功能,同時能起到2次加密的作用。
    }
 
    // 解密
    public static String Decrypt(String sSrc, String sKey) throws Exception {
        try {
            // 判斷Key是否正确
            if (sKey == null) {
                System.out.print("Key為空null");
                return null;
            }
            // 判斷Key是否為16位
            if (sKey.length() != 16) {
                System.out.print("Key長度不是16位");
                return null;
            }
            byte[] raw = sKey.getBytes("utf-8");
            SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec);
            byte[] encrypted1 = new Base64().decode(sSrc);//先用base64解密
            try {
                byte[] original = cipher.doFinal(encrypted1);
                String originalString = new String(original,"utf-8");
                return originalString;
            } catch (Exception e) {
                System.out.println(e.toString());
                return null;
            }
        } catch (Exception ex) {
            System.out.println(ex.toString());
            return null;
        }
    }
           

非對稱加密算法

非對稱加密算法中加密和解密用的不是同一個秘鑰,是以叫作非對稱加密算法。在非對稱加密算法每個使用者都有兩把鑰匙,一把公鑰一把私鑰。公鑰是對外釋出的,所有人都看的到所有人的公鑰,私鑰是自己儲存,每個人都隻知道自己的私鑰而不知道别人的。而也正是在非對稱加密算法中有加密和解密、簽名和驗簽的概念。接下來我們解釋一下這幾個概念是什麼意思。

加密和解密

用該使用者的公鑰加密後隻能該使用者的私鑰才能解密。這種情況下,公鑰是用來加密資訊的,確定隻有特定的人(用誰的公鑰就是誰)才能解密該資訊。是以這種我們稱之為加密和解密。

下面我拿 A 銀行和小明來舉例子吧。假設這 2 者之間是用不對稱的加密算法來保證資訊傳輸的安全性(不被第三人知道資訊的含義及篡改資訊)。大緻流程如下:首先小明發了一條資訊給 A 銀行 “我要存 500 元”。這條資訊小明會根據 A 銀行的對外釋出的公鑰把這條資訊加密了,加密之後,變成“XXXXXXX” 發給 A 銀行。中間被第三者截獲,由于沒有 A 銀行的私鑰無法解密,不能知道資訊的含義,也無法按正确的方式篡改。是以拿這條加密資訊是沒辦法的。最後被 A 銀行接受,A 銀行用自己的私鑰去解密這條資訊,解密成功,讀取内容,執行操作。然後得知消息是小明發來的,便去拿小明的公鑰,把 “操作成功(或失敗)” 這條資訊用小明的公鑰加密,發給小明。同理最後小明用自己的私鑰解開,得知知乎發來的資訊内容。其他人截獲因為沒有小明的私鑰是以也沒有用。

簽名和驗簽

還有第二種情況,公鑰是用來解密資訊的,確定讓别人知道這條資訊是真的由我釋出的,是完整正确的。接收者由此可知這條資訊确實來自于擁有私鑰的某人,這被稱作數字簽名,公鑰的形式就是數字證書。是以這種我們稱之為加簽和驗簽。

繼續拿小明和銀行 A 舉例子。銀行 A 釋出了一個銀行用戶端的更新檔供所有使用者更新,那為了確定人家下載下傳的是正确完整的用戶端,銀行 A 會為這個程式打上一個數字簽名(就是用銀行 A 的私鑰對這個程式加密然後釋出),你需要在你的電腦裡裝上銀行 A 的數字證書(就是銀行對外釋出的公鑰),然後下載下傳好這個程式,數字證書會去解密這個程式的數字簽名,解密成功,更新檔得以使用。同時你能知道這個更新檔确實是來自這個銀行 A,是由他釋出的,而不是其他人釋出的。

實戰演練

我們在開發過程中經常使用的非對稱加密算法就是 RSA 算法。接下來我們使用 Java 實作 RSA 算法。

生成密鑰

首先是生成 key 的部分,生成 key 有好多種做法,這裡我介紹三種

  1. 指令行:可以使用 openssl 進行生成公鑰和私鑰
-- 生成公鑰和私鑰
openssl genrsa -out key.pem 1024
-out 指定生成檔案,此檔案包含公鑰和私鑰兩部分,是以即可以加密,也可以解密
1024 生成密鑰的長度
           
  1. 使用網站:生成密鑰的網站
  2. 使用代碼:可以指定生成密鑰的長度,最低是 512
public static KeyPair buildKeyPair() throws NoSuchAlgorithmException {
    final int keySize = 2048;
    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA_ALGORITHM);
    keyPairGenerator.initialize(keySize);
    return keyPairGenerator.genKeyPair();
}
           

加密

有了密鑰,就可以進行加密的操作了,接下來就介紹關于 RSA 的加密操作,非常簡單隻要傳進來公鑰和需要加密的資料即可。

// 加密
    public static byte[] encrypt(PublicKey publicKey, String message) throws Exception {
        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        return cipher.doFinal(message.getBytes(UTF8));
    }
           

解密

// 解密
public static byte[] decrypt(PrivateKey privateKey, byte [] encrypted) throws Exception {
    Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
    cipher.init(Cipher.DECRYPT_MODE, privateKey);
    return cipher.doFinal(encrypted);
}
           

簽名

/**
 * 使用RSA簽名
 */
private static String signWithRSA(String content, PrivateKey privateKey) throws Exception {
    Signature signature = Signature.getInstance("SHA1WithRSA");
    signature.initSign(privateKey);
    signature.update(content.getBytes("utf-8"));
    byte[] signed = signature.sign();
    return base64Encode(signed);
}
           

驗簽

/**
 * 使用RSA驗簽
 */
private static boolean checkSignWithRSA(String content, PublicKey publicKey,String sign) throws Exception {
    Signature signature = Signature.getInstance("SHA1WithRSA");
    signature.initVerify(publicKey);
    signature.update(content.getBytes("utf-8"));
    return signature.verify(base64Decode(sign));
}
           

在加簽驗簽的時候需要傳入一個數字簽名标準,我們這裡填的是

SHA1WithRSA

,它的意思是用 SHA 算法進行簽名,用 RSA 算法進行加密。

算法說明:在對進行 SHA1 算法進行摘要計算後,要求對計算出的摘要進行處理,而不是直接進行 RSA 算法進行加密。要求把 SHA1 摘要的資料進行壓縮到 20 個位元組。在前面插入 15 個位元組标示資料。是以結構如下

30(資料類型結構)21(總長度)30(資料類型)09(長度)06 05 2B 0E 03 02 1A 0500【資料具體類型不清楚-請專家指正】 04 (資料類型) 14 (長度) + SHA1簽名資料
           

最後進行 RSA 加密。是以我們填寫的

XXXWithRSA

,這個 XXX 代表的就是使用什麼摘要算法進行加簽,至于摘要算法是什麼,随後會有詳細的說明。

調用實驗一下

public static void main(String[] args) throws Exception {
    KeyPair keyPair = buildKeyPair();
    byte[] encryptData = encrypt(keyPair.getPublic(), "不學無數");
    System.out.println(String.format("加密後的資料:%s",base64Encode(encryptData)));
    System.out.println(String.format("解密後的資料:%s",new String(decrypt(keyPair.getPrivate(),encryptData),UTF8)));
    String context = "加簽的字元串";
    String sign = signWithRSA(context, keyPair.getPrivate());
    System.out.println(String.format("生成的簽名:%s",sign));
    Boolean checkSignWithRSA = checkSignWithRSA(context, keyPair.getPublic(), sign);
    System.out.println(String.format("校驗的結果:%s",checkSignWithRSA.toString()));
}
           

輸出為

加密後的資料:Bi8b4eqEp+rNRhDaij8vVlNwKuICbPJfFmyzmEXKuAgEgzMPb8hAmYiGN+rbUKWeZYJKJd0fiOXv
6YrYqd7fdast/m443qQreRLxdQFScwvCvj9g1YnPzbU2Q/jIwqAPopTyPHNNngBmFki+R/6V4DYt
HA5gniaUMYzynHdD+/W+x8ZYmwiuuS63+7wXqL36aLKe0H50wELOpSn45Gvni8u+5zPIoHV7PBiz
trCnQvne5LxFKDprrS3td1/76qyupFd+Ul3hsd+gjbAyN2MlXcAFMrGVaRkopWwc9hP1BsPvS52q
/8jOVdbeyU9BziVhViz1V0TtGW8bfbEnIStc3Q==
解密後的資料:不學無數
生成的簽名:wvUXtr2UI0tUXmyMTTUBft8oc1dhvtXSBrFFetI5ZoxMm91TbXRWD31Pgqkg72ADxx9TEOAM3Bm1
kyzfBCZZpoq6Y9SM4+jdJ4sMTVtw0wACPglnPDAGs8sG7nnLhXWNQ1Y4pl4ziY6uLxF1TzQLFTxu
NAS7nyljbG69wrb9R3Sv5t8r1I54rYCVGSVFmTrGf+dSCjxABZv6mH8nygVif7zN1vU1+nSDKcON
Vtrpv0xCQHVBqnHPA6OiDm5GzBQxjD5aQt8mfgv8JJrB52TEa4JPYoC5Zw4JHlL++OvPwMpJgnuG
yg5vnWhxE2ncTzM+/pZ+CnXF2Dqv/JMQOfX6tA==
校驗的結果:true
           

摘要算法

資料摘要算法是密碼學算法中非常重要的一個分支,它通過對所有資料提取指紋資訊以實作資料簽名、資料完整性校驗等功能,由于其不可逆性,有時候會被用做敏感資訊的加密。資料摘要算法也被稱為哈希(Hash)算法或雜湊演算法。

消息摘要算法的主要特征是加密過程不需要密鑰,并且經過加密的資料無法被解密,隻有輸入相同的明文資料經過相同的消息摘要算法才能得到相同的密文。(摘要可以比方為指紋,消息摘要算法就是要得到檔案的唯一職位)

特點

無論輸入的消息有多長,計算出來的消息摘要的長度總是固定的。一般地,隻要輸入的消息不同,對其進行摘要以後産生的摘要消息也必不相同;但相同的輸入必會産生相同的輸出。隻能進行正向的資訊摘要,而無法從摘要中恢複出任何的消息,甚至根本就找不到任何與原資訊相關的資訊(不可逆性)。

好的摘要算法,沒有人能從中找到 “碰撞” 或者說極度難找到,雖然 “碰撞” 是肯定存在的(碰撞即不同的内容産生相同的摘要)。

應用

一般地,把對一個資訊的摘要稱為該消息的指紋或數字簽名。數字簽名是保證資訊的完整性和不可否認性的方法。資料的完整性是指信宿接收到的消息一定是信源發送的資訊,而中間絕無任何更改;資訊的不可否認性是指信源不能否認曾經發送過的資訊。其實,通過數字簽名還能實作對信源的身份識别(認證),即确定 “信源” 是否是信宿意定的通信夥伴。 數字簽名應該具有唯一性,即不同的消息的簽名是不一樣的;同時還應具有不可僞造性,即不可能找到另一個消息,使其簽名與已有的消息的簽名一樣;還應具有不可逆性,即無法根據簽名還原被簽名的消息的任何資訊。這些特征恰恰都是消息摘要算法的特征,是以消息摘要算法适合作為數字簽名算法。

有哪些具體的消息摘要算法?

  • CRC8、CRC16、CRC32:CRC(Cyclic Redundancy Check,循環備援校驗)算法出現時間較長,應用也十分廣泛,尤其是通訊領域,現在應用最多的就是 CRC32 算法,它産生一個 4 位元組(32 位)的校驗值,一般是以 8 位十六進制數,如 FA 12 CD 45 等。CRC 算法的優點在于簡便、速度快,嚴格的來說,CRC 更應該被稱為資料校驗算法,但其功能與資料摘要算法類似,是以也作為測試的可選算法。
  • MD2 、MD4、MD5:這是應用非常廣泛的一個算法家族,尤其是 MD5(Message-Digest Algorithm 5,消息摘要算法版本 5),它由 MD2、MD3、MD4 發展而來,由 Ron Rivest(RSA 公司)在 1992 年提出,目前被廣泛應用于資料完整性校驗、資料(消息)摘要、資料加密等。MD2、MD4、MD5 都産生 16 位元組(128 位)的校驗值,一般用 32 位十六進制數表示。MD2 的算法較慢但相對安全,MD4 速度很快,但安全性下降,MD5 比 MD4 更安全、速度更快。
  • SHA1、SHA256、SHA384、SHA512:SHA(Secure Hash Algorithm)是由美國專門制定密碼算法的标準機構——美國國家标準技術研究院(NIST)制定的,SHA 系列算法的摘要長度分别為:SHA 為 20 位元組(160 位)、SHA256 為 32 位元組(256 位)、 SHA384 為 48 位元組(384 位)、SHA512 為 64 位元組(512 位),由于它産生的資料摘要的長度更長,是以更難以發生碰撞,是以也更為安全,它是未來資料摘要算法的發展方向。由于 SHA 系列算法的資料摘要長度較長,是以其運算速度與 MD5 相比,也相對較慢。
  • RIPEMD、PANAMA、TIGER、ADLER32 等: RIPEMD 是 Hans Dobbertin 等 3 人在對 MD4,MD5 缺陷分析基礎上,于 1996 年提出來的,有 4 個标準 128、160、256 和 320,其對應輸出長度分别為 16 位元組、20 位元組、32 位元組和 40 位元組。TIGER 由 Ross 在 1995 年提出。Tiger 号稱是最快的 Hash 算法,專門為 64 位機器做了優化。

實戰演練

在單獨的使用摘要算法時我們通常使用的 MD5 算法,是以我們這裡就單獨說明使用 Java 實作 MD5 算法。

public static String getMD5Str(String str) throws Exception {
    try {
        // 生成一個MD5加密計算摘要
        MessageDigest md = MessageDigest.getInstance("MD5");
        // 計算md5函數
        md.update(str.getBytes());
        // digest()最後确定傳回md5 hash值,傳回值為8為字元串。因為md5 hash值是16位的hex值,實際上就是8位的字元
        // BigInteger函數則将8位的字元串轉換成16位hex值,用字元串來表示;得到字元串形式的hash值
        return new BigInteger(1, md .digest()).toString(16);
    } catch (Exception e) {
        throw new Exception("MD5加密出現錯誤,"+e.toString());
    }
}
           

參考

  • zhuanlan.zhihu.com/p/20064358
  • time.geekbang.org/column/arti…
  • my.oschina.net/OutOfMemory…
  • www.zz-news.com/com/zhongsh…
  • www.hbhncj.com/article-53-…
  • www.zhihu.com/question/33…

繼續閱讀