關於加解密、加簽驗籤的那些事 | 得物技術

語言: CN / TW / HK

1前言

面對MD5、SHA、DES、AES、RSA等等這些名詞你是否有很多問號?這些名詞都是什麼?還有什麼公鑰加密、私鑰解密、私鑰加簽、公鑰驗籤。這些都什麼鬼?或許在你日常工作沒有聽說過這些名詞,但是一旦你要設計一個對外訪問的介面,或者安全性要求高的系統,那麼必然會接觸到這些名詞。所以加解密、加簽驗籤對於一個合格的程式設計師來說是必須要掌握的一個概念。接下來我們就一文徹底搞懂這些概念。

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

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

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

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

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

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

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

2.1 古典密碼學

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

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

2.2 近代密碼學

近代密碼學的研究來自於第一、二次世界大戰中對於軍事通訊進行保護和猜出來的需求。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(密碼學的新方向)》,探討了無需傳輸金鑰的保密通訊和簽名認證體系問題,正式開創了現代公鑰密碼學體系的研究。在公鑰密碼發現以前,如果需要保密通訊,通訊雙方事先要對加解密的演算法以及要使用的金鑰進行事先協商,包括送雞毛信,實際上是在傳送金鑰。但自從有了公鑰密碼,需要進行祕密通訊的雙方不再需要進行事前的金鑰協商了。公鑰密碼在理論上是不保密的,在實際上是保密的。也就是說,公鑰密碼是可以猜出來的,但需要極長的時間,等到猜出來了,這個祕密也沒有保密的必要了。

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

3程式實現

3.1 對稱加密演算法

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

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

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

  • 加密:原文+祕鑰 = 密文
  • 解密:密文-祕鑰 = 原文 可以看到兩次過程使用的都是一個祕鑰。用圖簡單表示如下:

64011.png

3.2實戰演練

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

3.2.1 DES

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

接下來用Java實現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);
    }
}


3.2.2 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),初始化時需指定某種操作模式

截圖20230309 11.47.01.png

祕鑰的可以由我們自己定義,也可以是由AES自己生成,當自己定義是需要是要注意: (1)根據 AES 規範,可以是 16 位元組、24 位元組和32 位元組長,分別對應 128 位、192 位和 256 位; (2)為便於傳輸,一般對加密後的資料進行 base64 編碼:

/*
         * 此處使用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;
        }
    }


3.3 非對稱加密演算法

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

3.3.1 加密和解密

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

3.3.2 加簽和驗籤

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

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

3.3.3 實戰演練

我們在開發過程中經常使用的非對稱加密演算法之一就是RSA演算法。接下來我們使用Java實現RSA演算法。 生成金鑰 首先是生成key的部分,生成key有好多種做法,這裡我介紹三種 (1)命令列:可以使用openssl進行生成公鑰和私鑰

openssl genrsa -out key.pem 1024
            -out 指定生成檔案,此檔案包含公鑰和私鑰兩部分,所以即可以加密,也可以解密
1024 生成金鑰的長度


(2)使用網站:生成金鑰的網站 (3)使用程式碼:可以指定生成金鑰的長度,最低是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個位元組標示資料。所以結構如下

最後進行RSA加密。所以我們填寫的XXXWithRSA,這個XXX代表的就是使用什麼摘要演算法進行加簽,至於摘要演算法是什麼,隨後會有詳細的說明。

呼叫實驗一下

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


輸出為

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


4摘要演算法

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

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

4.1 特點

無論輸入的訊息有多長,計算出來的訊息摘要的長度總是固定的。一般地,只要輸入的訊息不同,對其進行摘要以後產生的摘要訊息也必不相同;但相同的輸入必會產生相同的輸出。只能進行正向的資訊摘要,而無法從摘要中恢復出任何的訊息,甚至根本就找不到任何與原資訊相關的資訊(不可逆性)。 好的摘要演算法,沒有人能從中找到“碰撞”或者說極度難找到,雖然“碰撞”是肯定存在的(碰撞即不同的內容產生相同的摘要)。

4.2 應用

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

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位機器做了優化。

4.3 實戰演練

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

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


5參考文章

https://zhuanlan.zhihu.com/p/20064358 https://time.geekbang.org/column/article/224701 https://my.oschina.net/OutOfMemory/blog/3131916 https://www.zz-news.com/com/zhongshanfengyu/news/itemid-674743.html https://www.hbhncj.com/article-53-3443-1.html https://www.zhihu.com/question/33645891