黑莲技术资源论坛

作者: 123456790
查看: 103|回复: 0

Java教程︰一文詳解信息安全的密碼學

Java教程︰一文詳解信息安全的密碼學

[复制链接]
123456790 | 显示全部楼层 发表于: 2022-8-5 14:46:00
123456790 发表于: 2022-8-5 14:46:00 | 显示全部楼层 |阅读模式
查看: 103|回复: 0
一、前言

一個信息系統缺少不了信息安全模塊,今天就帶著大家全面了解並學習一下信息安全中的密碼學知識,本文將會通過案例展示讓你了解抽象的密碼學知識,閱讀本文你將會有如下收獲︰

  • 熟悉現代密碼學體系包含的主流密碼技術
  • 掌握Base64和Hex編碼技術的特性與使用案例
  • 掌握對稱密碼和非對稱密碼的特性與使用案例
  • 掌握混合密碼系統和隨機數的特征與使用案例
二、關于密碼

提到密碼,你的第一印象是什麼?我們平時登錄微信、郵箱都需要輸入用戶名和密碼,或者用手機支付也需要輸入支付密碼,大部分人想到的可能就是這些情形中涉及到的密碼。然而本文即將討論的密碼與此密碼完全是不同的概念。實際上無論是微信還是支付寶或者其他系統要求輸入的密碼都只是一種身份驗證的憑證,也就正確的密碼是可以證明你是這個賬號的主人的證據。這種密碼準確來講叫做口令比較合適,對應英文中的password、pin。
本文中提到的密碼是什麼呢?實際上,密碼(cryptography)是一個極其龐大復雜的信息處理體系,涉及到信息的機密性、完整性、認證、不可否認性等眾多方面,由此衍生出的很多保護我們信息安全的技術,這些技術我們一般統稱為密碼技術。密碼技術是密碼學的核心。
數學與密碼技術的關系︰數學是密碼技術的基礎,復雜的密碼技術往往都會涉及到復雜的數學公式。
密碼學︰密碼學是網絡安全、信息安全、區塊鏈等領域的基礎,常見的對稱密碼、公鑰密鑰、散列函數等,都屬于密碼學範疇。密碼學中的密碼技術比如“密碼”可以讓竊听者無法解讀竊取的信息,“單項散列函數”可以檢測出消息是否被篡改,“數字簽名”可以確定消息是否來源于合法的發送者。
三、信息安全

1. 概念

信息安全是指信息網絡的硬件、軟件及其系統中的數據受到保護,不受偶然的或者惡意的原因而遭到破壞 、 更改 、泄露、否認等,系統連續可靠正常地運行,信息服務不中斷。 信息安全安全是建立在以密碼技術為基礎的計算機安全領域,輔以通信技術、計算機技術與網絡技術等方面的內容。
2. 與密碼學的關系


  • 密碼學是保障信息安全的核心技術 ,但不是提供信息安全的唯一方式 。
  • 信息安全是密碼學研究與發展的目的 。
  • 信息安全的理論基礎是密碼學,信息安全的問題根本解決往往依靠密碼學理論 。
3. 密碼學與信息安全常識


  • 不要使用保密的密碼算法
  • 使用低強度的密碼比不進行加密更危險
  • 任何密碼總有一天會被破解
  • 密碼只是信息安全的一部分
四、現代密碼學體系

信息安全及密碼學技術,是整個信息技術的基石。在西方語文中,密碼學一詞源于希臘語,krypto意思是隱藏,graphene是書寫的意思。密碼學的發展總共經歷了四個階段︰遠古密碼、古典密碼、近代密碼和現代密碼,這四個階段的發展歷史詳細介紹可參見文章最後的附錄部分,接下來本文內容主要介紹的是現代密碼學所涉及到的密碼知識。
1. 信息安全威脅與密碼技術



該圖完整的展示了信息安全面臨的威脅與解決方案中會用到的密碼技術,本文側重于介紹有關于保證數據機密性的密碼技術。
2. 密碼算法及重要概念

將明文通過處理變換為密文的規則稱為加密算法,將密文恢復成明文的規則稱為解密算法,加密解密一起稱為密碼算法。



  • 明文 (Plaintext)︰ 信息的原始數據
  • 密文(Ciphertext)︰明文經過編碼變換所生成的數據
  • 加密(Encryption)︰對明文進行編碼變換生成密文的過程
  • 解密(Decryption)︰將密文恢復成明文的過程
  • 密鑰︰密碼算法需要用到密鑰的,密鑰就相當于保險庫大門的鑰匙,重要性不言而喻,所以切記不要泄露密碼的密鑰。 密鑰 (Key)︰控制明文與密文之間相互變換的,分為加密密鑰和解密密鑰。
3. ASCII編碼

ASCII碼 是現今最通用的單字節編碼系統,並等同于國際標準ISO/IEC 646 。在這個頁面,你可以找到8位的256個字符、ASCII碼表和Windows-1252 (code page 1252,它是國際標準ISO 8859-1的一個擴展字符集) 標準保持一致;
ASCII碼American Standard Code for Information Interchange 的縮寫,而不是ASCⅡ(羅馬數字2),有很多人在這個地方產生誤解;
ASCII碼 規範于1967年第一次發布,最後一次更新是在1986年,它包含了33個控制字符(具有某些特殊功能但是無法顯示的字符)和95個可顯示字符;
ASCII碼大致可以分作三部分組成。

  • 第一部分是︰ASCII非打印控制字符
  • 第二部分是︰ASCII打印字符
  • 第三部分是︰擴展ASCII打印字符




3.1 第一部分︰ASCII非打印控制字符表

ASCII表上的數字0€€31分配給了控制字符,用于控制像打印機等一些外圍設備。例如,12代表換頁/新頁功能。此命令指示打印機跳到下一頁的開頭。(參詳ASCII碼表中0-31)
3.2 第二部分︰ASCII打印字符

數字 32€€126 分配給了能在鍵盤上找到的字符,當您查看或打印文檔時就會出現。數字127代表 DELETE 命令。(參詳ASCII碼表中32-127)
3.3 第三部分︰擴展ASCII打印字符

擴展的ASCII字符滿足了對更多字符的需求。擴展的ASCII包含ASCII中已有的128個字符(數字0€€32顯示在下圖中),又增加了128個字符,總共是256個。即使有了這些更多的字符,許多語言還是包含無法壓縮到256個字符中的符號。因此,出現了一些ASCII的變體來囊括地區性字符和符號。例如,許多軟件程序把ASCII表(又稱作ISO8859-1)用于北美、西歐、澳大利亞和非洲的語言。
4. 字符串的ASCII碼與二進制位

public class ASCIITest {    @Test    public void test01() {        String str = "heima";        byte[] bytes = str.getBytes();        for (byte b : bytes) {            //打印ascii碼            System.out.println(b);            //獲取二進制位            String s = Integer.toBinaryString(b);            System.out.println(s);        }    }            @Test    public void test02() throws UnsupportedEncodingException {        String str = "黑馬"; //中文UTF-8編碼一個漢字佔3個字節,中文GBK編碼一個漢字佔2個字節。        byte[] bytes = str.getBytes("UTF-8");        System.out.println("字節個數︰"+ bytes.length);        char[] chars = str.toCharArray();        for (char c : chars) {            //打印字符            System.out.println(c);            //字符類型會自動提升為int類型,獲取二進制值(10進制轉二進制)            String s = Integer.toBinaryString(c);            System.out.println(s);        }    }}   5. Hex編碼與Base64編碼

我們知道在計算機中的字節共有256個組合,對應就是ascii碼,而ascii碼的128~255之間的值是不可見字符。而在網絡上交換數據時,比如說從A地傳到B地,往往要經過多個路由設備,由于不同的設備對字符的處理方式有一些不同,這樣那些不可見字符就有可能被處理錯誤,這是不利于傳輸的。所以我們需要將字節轉為正確的課可見字符,需要對字節數組進行進一步編碼,常用編碼格式用Hex編碼和Base64編碼。
5.1 Hex編碼

1字節=8位2進制,比如小寫字母a,ASCII表對應十進制為97,二進制表示為01100001
1字節=2位16進制,比如小寫字母a,ASCII表對應十進制為97, 十六進制表示為61



  • 因為一個字節中存在8個 bit可以表示256個字符,而非擴展 ASCII 碼只能表示0-127種字符,為了能完整地表示一個字節,可以將二進制數據轉換為十六進制數據的方式來實現。所以 Hex 編碼也被稱作為 Base16 編碼,相比于原先8位表示一個字節,Hex 編碼能夠只用2位表示一個字節。Hex 編碼最常用于二進制文件查看時展示的編碼,如010Editor 就可以支持查看二進制文件。
  • 使用16個可見字符來表示一個二進制數組,編碼後數據大小將x2
  • 1個字符需要用2個可見字符來表示
5.1.1 代碼示例

引入依賴
<dependencies>        <dependency>            <groupId>commons-codec</groupId>            <artifactId>commons-codec</artifactId>            <version>1.14</version>        </dependency>        <dependency>            <groupId>commons-io</groupId>            <artifactId>commons-io</artifactId>            <version>2.6</version>        </dependency>        <dependency>            <groupId>junit</groupId>            <artifactId>junit</artifactId>            <version>4.5</version>            <scope>test</scope>        </dependency>  </dependencies>單元測試︰
public class HexDemoTest {    @Test    public void test01() {        String data = "itcast" ;        byte[] bytes = data.getBytes() ;        //測試hex        String encoded = Hex.encodeHexString(bytes) ;        System.out.println(encoded);    }}   5.2 base64編碼


  • Base64編碼要求把3個8位字節(3乘8=24)轉化為4個6位的字節(4乘6=24),之後在6位的前面補兩個0,形成8位一個字節的形式。 如果剩下的字符不足3個字節,則用0填充,輸出字符使用'=',因此編碼後輸出的文本末尾可能會出現1或2個'='。為了保證所輸出的編碼位可讀字符,Base64制定了一個編碼表,以便進行統一轉換。編碼表的大小為2^6=64,這也是Base64名稱的由來。標準base64只有64個字符(大寫A到Z、小寫a到z、數字0到9、“+”和“/”)以及用作後綴等號;
  • Base64是網絡上最常見的用于傳輸8Bit字節碼的可讀性編碼算法之一
  • 以每 3 個 字符(1Byte=8bit)為一組,然後針對每組,首先獲取每個字符的 ASCII 編碼(字符'a'=97=01100001),然後將 ASCII 編碼轉換成 8 bit 的二進制,得到一組 3 * 8=24 bit 的字節。然後再將這 24 bit 劃分為 4 個 6 bit 的字節,並在每個 6 bit 的字節前面都填兩個高位 0,得到 4 個 8 bit 的字節,然後將這 4 個 8 bit 的字節轉換成十進制,對照 BASE64 編碼表 (下表),得到對應編碼後的字符。
  • 使用64個可見字符來表示一個二進制數組,編碼後數據大小變成原來的4/3
  • 3個字符用4個可見字符來表示


5.2.1 原理示例1-足夠三字節



  • 第一步︰"jay"、“a”、"n"對應的ASCII碼值分別為106,97,121,對應的二進制值是01101010、01100001、01111001。如圖第二三行所示,由此組成一個24位的二進制字符串。
  • 第二步︰如圖第四行,將24位每6位二進制位一組分成四組。
  • 第三步︰在上面每一組前面補兩個0(紅色背景),擴展成32個二進制位,此時變為四個字節︰00011010、00100110、00000101、00111001。分別對應的值(Base64編碼索引)為︰26、38、5、57。
  • 第四步︰用上面的值在Base64編碼表中進行查找,分別對應︰a、m、F、5。因此“jay”Base64編碼之後就變為︰amF5。
5.2.2 代碼示例1-足夠三字節

單元測試︰
public class Base64DemoTest {    @Test    public void test01() {        //jay正好三個字節,編碼後輸出的結果沒有=        System.out.println(Base64.encodeBase64String("jay".getBytes()));    }}5.2.3 原理示例2-不夠三字節

如果字節不足三個怎麼辦,分組的時候不組8位的都補0,計算沒結果的=號代替


5.2.4 代碼示例2-不夠三字節

單元測試︰
public class Base64Demo {    @Test    public void test02() {        //ja不夠三個字節,編碼後一定會有=        System.out.println(Base64.encodeBase64String("ja".getBytes()));    }}5.3 代碼示例-編碼與解碼

單元測試︰
/** * hex編碼與base64編碼測試 */public class HexAndBase64Test {    @Test    public void test() throws DecoderException {        String data = "黑馬程序員" ;        byte[] bytes = data.getBytes() ;        //測試hex        String encryStr = Hex.encodeHexString(bytes) ;        String decryStr = new String(Hex.decodeHex(encryStr.toCharArray())) ;        System.out.println("Hex編碼解碼︰"+ encryStr  + " | " + decryStr) ;        //測試base64        encryStr = Base64.encodeBase64String(bytes) ;        decryStr = new String(Base64.decodeBase64(encryStr.getBytes()) );        System.out.println("Base64編碼解碼︰"+ encryStr  + " | " + decryStr) ;    }}上面我們已經看到了Base64就是用6位(2的6次冪就是64)表示字符,因此成為Base64。同理,Base32就是用5位,Base16就是用4位。
對比︰ hex編碼速度快,體積大;base64編碼速度慢,體積小
6. 密碼分類

6.1 對稱密碼

加密密鑰和解密密鑰相同,又稱傳統密碼體制、共享密鑰密碼體制、秘密密鑰體制或單密鑰體制。從密鑰使用方式上分為分組密碼和序列密碼 ,這點後文會有介紹。
對稱加密算法的優點︰算法公開、計算量小、加密速度快、加密效率高。
對稱加密算法的缺點︰交易雙方都使用同樣鑰匙,安全性得不到保證。此外,每對用戶每次使用對稱加密算法時,都需要使用其他人不知道的惟一鑰匙,這會使得發收信雙方所擁有的鑰匙數量呈幾何級數增長,密鑰管理成為用戶的負擔。對稱加密算法在分布式網絡系統上使用較為困難,主要是因為密鑰管理困難,使用成本較高。
對稱加密通常使用的是相對較小的密鑰,一般小于256 bit。因為密鑰越大,加密越強,但加密與解密的過程越慢。如果你只用1 bit來做這個密鑰,那黑客們可以先試著用0來解密,不行的話就再用1解;但如果你的密鑰有1 MB大,黑客們可能永遠也無法破解,但加密和解密的過程要花費很長的時間。密鑰的大小既要照顧到安全性,也要照顧到效率,是一個trade-off。
常用對稱加密算法

  • DES(Data Encryption Standard)︰數據加密標準,速度較快,適用于加密大量數據的場合。
  • 3DES(Triple DES)︰是基于DES,對一塊數據用三個不同的密鑰進行三次加密,強度更高。
  • AES(Advanced Encryption Standard)︰高級加密標準,是下一代的加密算法標準,速度快,安全級別高,支持128、192、256、512位密鑰的加密。
算法特征

  • 加密方和解密方使用同一個密鑰,一旦密鑰文件泄漏, 就會導致數據暴露
  • 加密解密的速度比較快,適合數據比較長時的使用,可以加密大文件
  • 密鑰傳輸的過程不安全,且容易被破解,密鑰管理也比較麻煩。
  • 加密後編碼表找不到對應字符, 出現亂碼
  • 一般結合Base64使用
6.1.1 DES


  • DES是1997年美國聯邦信息處理標準中所采用的一種對稱密碼算法,一直以來被美國以及其他國家的政府和銀行等廣泛采用。隨著計算機的快速發展,DES已經被暴力破解,1997年用時96天破譯密鑰,1998年41天破譯密鑰,到了1999年只用22小時15分鐘就可以破譯。
  • DES技術是一種將64比特的明文加密成64比特的密文的對稱密碼算法,因此理論上來講,他的密鑰長度也是64位,但因為在DES的密鑰中每隔7比特,就會設置一個用于錯誤檢查的比特,所以實際上DES的密鑰的長度只有56比特。
  • DES是以64比特的明文(比特序列)為一個單位進行加密,這64比特的單位成為分組,一般來說,以分組為單位進行處理的密碼算法稱為分組密碼。
  • DES每次每次只能加密64比特的數據,如果要加密的明文比較長,就需要對DES加密進行迭代(反復),而迭代的具體方案就稱為模式。
Java中有關對稱和非對稱加密的核心類︰javax.crypto.Cipher
代碼示例
/** * DES加密算法測試 */public class DesTest {    /**     * 測試DES加密     */    @Test    public void testEncrypt() throws Exception {        //明文        String text = "黑馬程序員";        //密鑰,長度必須為8個字節(字符)        byte[] secretKeyBytes = "12345678".getBytes();        //secretKeyBytes  = generateSecretKey("DES", 56);        // Cipher︰獲取密碼對象,參數按"算法/模式/填充模式"        Cipher cipher = Cipher.getInstance("DES");        // 參數1︰密鑰,key的字節數組,參數2︰加密算法        SecretKeySpec sks = new SecretKeySpec(secretKeyBytes, "DES");        //加密對象初始化數據,參數1︰模式,有加密模式和解密模式,參數2︰密鑰規則        cipher.init(Cipher.ENCRYPT_MODE,sks);        //執行加密,得到加密結果        byte[] bytes = cipher.doFinal(text.getBytes());        //輸出字節,因為ascii碼有負數,解析不出來,所以亂碼        //將byte數組轉成ASCII編碼,必須確保byte數組的值在ASCII的可視字符範圍,否則會出現亂碼,        //因為ASCII的取值範圍比byte小,byte的取值範圍是-128到127        for (byte b : bytes) {            System.out.println(b);        }        // 打印密文        System.out.println(new String(bytes));        //將byte數組轉成Base64編碼。        String result = Base64.encodeBase64String(bytes);        System.out.println("加密後的值︰" + result);    }    /**     * 測試DES解密     */    @Test    public void testDecrypt() throws Exception {        //密文        String crpyt = "+rBmhkThnKQf8IJTM/qmMA==";        //密鑰,長度必須為8個字節(字符)        byte[] secretKeyBytes = "12345678".getBytes();        //獲取Cipher對象        Cipher cipher = Cipher.getInstance("DES");        // 指定密鑰規則        SecretKeySpec sks = new SecretKeySpec(secretKeyBytes, "DES");        cipher.init(Cipher.DECRYPT_MODE, sks);        //解密,上面使用的base64編碼,下面直接用密文        byte[] bytes = cipher.doFinal(Base64.decodeBase64(crpyt));        //  因為是明文,所以直接返回        String text = new String(bytes);        System.out.println("解密後的值︰"+ text) ;    }    /**     * 生成密鑰     * @param algorithm 算法     * @param len  密鑰長度     */    public static byte[] generateSecretKey(String algorithm, int len) throws NoSuchAlgorithmException {        KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm);//密鑰生成器        keyGenerator.init(len);//密鑰長度        SecretKey secretKey = keyGenerator.generateKey();//生成密鑰        return secretKey.getEncoded(); //密鑰字節數組轉字符串    }}6.1.2 3DES


  • 三重DES,是為了加強DES的強度,將DES重復3次所得到的一種密碼算法。明文需經過3次DES處理才能得到最後密文,由于DES密鑰實際長度為56比特,因此3DES的密鑰密鑰實際長度就是56*3=168比特。通過增加迭代次數提高安全性,常應用在銀行等金融機構。
  • DES密鑰長度是8字節(64比特),3DES密鑰長度是24字節(192比特)
  • 三重DES不是進行三次DES加密(加密-加密-加密),而是加密-解密-加密的過程。
  • 加密過程︰用第一支密鑰對原文進行加密,再使用第二支密鑰對第一步操作後的信息進行解密,最後使用第三支密鑰對第二步操作後的信息進行加密得到最終密文。
  • 解密過程︰用第三支密鑰對密文進行解密,再采用第二支密鑰進行加密,最後采用第一支密鑰解密得到原文。
  • 三重DES中所有密鑰都相同時,三重DES等同于普通DES,因為前兩步加密解密後得到的是原來的明文。
  • EDE︰表示加密(Encryption)-> 解密(Decryption)->加密(Encryption)這個流程。
  • 缺點︰處理速度較慢、密鑰計算時間較長、加密效率不高。
/** * 3DES加密算法測試 */public class Des3Test {    /**     * 測試3DES加密     */    @Test    public void testEncrypt() throws Exception {        //明文        String text = "黑馬程序員";        //密鑰,長度必須24個字節(字符)        byte[] secretKeyBytes = "123456781234567812345678".getBytes();        //可指定密鑰實際長度是168        //secretKeyBytes  = generateSecretKey("DESede", 168);        // Cipher︰獲取密碼對象,參數按"算法/模式/填充模式"        Cipher cipher = Cipher.getInstance("DESede");        // 參數1︰密鑰,key的字節數組,參數2︰加密算法        SecretKeySpec sks = new SecretKeySpec(secretKeyBytes, "DESede");        //加密對象初始化數據,參數1︰模式,有加密模式和解密模式,參數2︰密鑰規則        cipher.init(Cipher.ENCRYPT_MODE,sks);        //執行加密,得到加密結果        byte[] bytes = cipher.doFinal(text.getBytes());        //將byte數組轉成Base64編碼。        String result = Base64.encodeBase64String(bytes);        System.out.println("加密後的值︰" + result);    }    /**     * 測試3DES解密     */    @Test    public void testDecrypt() throws Exception {        //密文        String crpyt = "+rBmhkThnKQf8IJTM/qmMA==";        //密鑰,長度必須24個字節(字符)        byte[] secretKeyBytes = "123456781234567812345678".getBytes();        //獲取Cipher對象        Cipher cipher = Cipher.getInstance("DESede");        // 指定密鑰規則        SecretKeySpec sks = new SecretKeySpec(secretKeyBytes, "DESede");        cipher.init(Cipher.DECRYPT_MODE, sks);        //解密,上面使用的base64編碼,下面直接用密文        byte[] bytes = cipher.doFinal(Base64.decodeBase64(crpyt));        //  因為是明文,所以直接返回        String text = new String(bytes);        System.out.println("解密後的值︰"+ text) ;    }    /**     * 生成密鑰     * @param algorithm 算法     * @param len  密鑰長度     */    public static byte[] generateSecretKey(String algorithm, int len) throws NoSuchAlgorithmException {        KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm);//密鑰生成器        keyGenerator.init(len);//密鑰長度        SecretKey secretKey = keyGenerator.generateKey();//生成密鑰        return secretKey.getEncoded(); //密鑰字節數組轉字符串    }}6.1.3 AES


  • AES(Advanced Encryption Standard)是取代其前任標準(DES)而稱為新標準的一種對稱算法。
  • AES分組長度為128比特,密鑰長度有128、192、256比特三種,AES-128、AES192和AES-256。
  • 至今還沒有有效破解AES的方式
還是之前的代碼,替換密鑰值和加密算法即可
/** * AES加密算法測試 */public class AesTest {    /**     * 測試AES加密     */    @Test    public void testEncrypt() throws Exception {        //明文        String text = "黑馬程序員";        //密鑰,長度必須為16個字節(字符)        byte[] secretKeyBytes = "1234567812345678".getBytes();        //密鑰實際長度128比特        //secretKeyBytes  = generateSecretKey("AES", 128);        // Cipher︰獲取密碼對象,參數按"算法/模式/填充模式"        Cipher cipher = Cipher.getInstance("AES");        // 參數1︰密鑰,key的字節數組,參數2︰加密算法        SecretKeySpec sks = new SecretKeySpec(secretKeyBytes, "AES");        //加密對象初始化數據,參數1︰模式,有加密模式和解密模式,參數2︰密鑰規則        cipher.init(Cipher.ENCRYPT_MODE,sks);        //執行加密,得到加密結果        byte[] bytes = cipher.doFinal(text.getBytes());        //將byte數組轉成Base64編碼。        String result = Base64.encodeBase64String(bytes);        System.out.println("加密後的值︰" + result);    }    /**     * 測試AES解密     */    @Test    public void testDecrypt() throws Exception {        //密文        String crpyt = "j9qMqmunoPEtMRpNYPWfCw==";        //密鑰,長度必須為16個字節(字符)        byte[] secretKeyBytes = "1234567812345678".getBytes();        //獲取Cipher對象        Cipher cipher = Cipher.getInstance("AES");        //指定密鑰規則        SecretKeySpec sks = new SecretKeySpec(secretKeyBytes, "AES");        cipher.init(Cipher.DECRYPT_MODE, sks);        //解密,上面使用的base64編碼,下面直接用密文        byte[] bytes = cipher.doFinal(Base64.decodeBase64(crpyt));        //因為是明文,所以直接返回        String text = new String(bytes);        System.out.println("解密後的值︰"+ text) ;    }    /**     * 生成密鑰     * @param algorithm 算法     * @param len  密鑰長度     */    public static byte[] generateSecretKey(String algorithm, int len) throws NoSuchAlgorithmException {        KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm);//密鑰生成器        keyGenerator.init(len);//密鑰長度        SecretKey secretKey = keyGenerator.generateKey();//生成密鑰        return secretKey.getEncoded(); //密鑰字節數組轉字符串    }}6.1.4 破解AES的難度

據統計,完全破解要花費時長為2104億年,消耗電量1.1201 * 10^22 kWh,電費1.368 * 10^13 億美元
6.1.5 選用哪一種?

DES已被破解不要再使用,3DES在部分金融機構內還有在使用將來會被AES取代,推薦使用AES。
6.2 分組密碼

6.2.1 概念

按對明文的處理方式,密碼算法可以分為分組密碼( Blok cipher)和流密碼(Stream cipher)。
6.2.2 分組密碼︰也叫塊加密(block cyphers),每次只能處理特定長度的一塊數據的密碼算法,“一塊”稱為分組,一個分組的比特數就是分組長度。一次加密明文中的一個塊,將明文按一定的位長分組,明文組經過加密運算得到密文組,密文組經過解密運算(加密運算的逆運算),還原成明文組。比如︰DES和3DES的分組長度都是64比特,一次性只能加密64比特的明文並生成64比特的密文。
6.2.3 序列密碼︰也叫流加密(stream cyphers),對數據流進行連續處理的密碼算法,是指利用少量的密鑰(制亂元素)通過某種復雜的運算(密碼算法)產生大量的偽隨機位流,用于對明文位流的加密。解密是指用同樣的密鑰和密碼算法及與加密相同的偽隨機位流,用以還原明文位流。流密碼中一般以1比特、8比特或32比特等為單位進行加解密。
6.2.4 對比︰分組密碼處理一個分組就結束,無需通過內部狀態記錄加密進度;流密碼是對一串數據流進行連續處理,需要保持內部狀態。
前文所提到的DES、3DES、AES等大部分對稱加密算法都屬于分組密碼。流密碼的典型例子有一次性密碼本。
本文內容主要講解的是分組密碼。
6.2.2 加密模式

分組算法只能加密固定長度的分組,但有時加密的明文長度會超過分組密碼的分組長度,此時就需要對分組密碼進行迭代,以便將一段很長的明文全部加密。迭代的方法就稱為分組密碼的加密模式(model)。
加密模式的種類︰常見的有ECB模式(電子密碼本模式)、CBC模式(密碼分組鏈接模式)、CTR模式(計數器模式)等,本課中重點說明ECB模式和CBC模式。
6.2.3 明文分組與密文分組

明文分組︰ 分組密碼算法中作為加密對象的明文。明文分組的長度與分組密碼算法的分組長度相等。
密文分組︰分組加密算法中對明文分組加密之後產生的密文。


6.2.4 ECB與CBC

1) ECB

ECB加密模式︰需要加密的明文按照塊密碼的塊大小被分為數個塊(組),並對每個塊進行獨立加密。明文分組與密文分組是一一對應的關系,每個明文分組都各自獨立地進行加密和解密。如果明文中存在多個相同的明文分組,那麼明文分組將被轉換為相同的密文分組,這樣的話可以通過觀察密文來推測出明文的組合,從而可能破譯密碼,所以ECB模式存在一定風險。


ECB的一個非常大的弱點就是可以在不破譯密文的情況下操作明文,比如銀行轉賬例子中︰付款人、收款人、轉賬金額分別可以對應三個明文分組,分別加密對應三個密文分組,如果將收款人密文分組和付款人密文分組順序調換,那麼整個銀行轉行的操作就是完全相反的。也就是說在ECB模式中,只要對任意密文分組進行替換,對應的明文分組也會被替,如果直接將密文分組刪除,那麼明文分組也會被刪除,如果對密文分組負責,那麼明文分組也會被復制。
由于ECB模式的巨大漏洞,作為程序員,切記千萬不能使用ECB模式進行加解密。
2) CBC

CBC加密模式︰密文分組像鏈條一樣拼接在一起,具體過程是首先將明文分組與前一個密文分組進行XOR運行,然後在進行加密。


對比ECB模式和CBC模式其中一個分組的加密過程,發現他們的區別在于ECB模式只進行了加密,而CBC模式在加密之前進行了一次XOR運算。
CBC模式在加密一個明文分組時,由于不存在前一個密文分組,所以需要提前準備好一個長度為一個分組的比特序列來當做前一個密文分組,這個比特序列稱為初始化向量(initialization vector),縮寫是IV。每次加密時都會隨機產生一個不同的比特序列當做初始化向量。
CBC模式就算是明文分組中的某兩個組比如分組1和明文分組2值相等,由于有XOR運算。密文分組1和2的值也不一定相等。所以CBC模式不存在ECB模式中的缺陷。
由于CBC模式的安全性,CBC被應用在在互聯網安全通信協議IPsec中,在此協議中主要使用了3DES-CBC以及AES-CBC,CBC模式還被應用在Kerberos verson 5的認證系統中。
6.2.5 填充模式

大多數密碼算法都是塊密碼算法,需要將明文消息切成固定大小的塊,一塊一塊地進行加密。例如DES就需要將消息切割成一個個64位的塊。如果消息長度不是64的整數倍,最後一個消息塊就不夠64位,這時就要對最後一個消息塊進行填充。填充方式有很多種,如果加密時以某種方式填充,解密時就得使用這種填充方式並去除填充內容。
NoPadding
不填充,在DES加密算法下, 要求原文長度必須是8byte的整數倍,在AES加密算法下, 要求原文長度必須是16byte的整數倍
PKCS5Padding
數據塊的大小為8位, 不夠就補足
6.2.6 Java的應用

有關加密模式與填充模式,在Java中的應用,有如下幾點需要注意

  • javax.crypto.Cipher類是Java中提供加密和解密功能的核心類,Cipher對象創建的方式有如下兩種︰
  • Cipher c = Cipher.getInstance("算法/模式/填充");或Cipher c = Cipher.getInstance("算法")


  • 默認情況下, 加密模式和填充模式為 : ECB/PKCS5Padding
  • 如果使用CBC模式, 在初始化Cipher對象時, 需要增加參數, 初始化向量IV : IvParameterSpec iv = new IvParameterSpec(key.getBytes());
  • Cipher創建的第一種方式中,參數一般稱為轉換名,轉換名由"算法/模式/填充"構成。Java平台的每個實現都需要支持以下標準Cipher轉換︰
  • - `AES/CBC/NoPadding` (128) - `AES/CBC/PKCS5Padding` (128) - `AES/ECB/NoPadding` (128) - `AES/ECB/PKCS5Padding` (128) - `DES/CBC/NoPadding` (56) - `DES/CBC/PKCS5Padding(56)` - `DES/ECB/NoPadding(56)` - `DES/ECB/PKCS5Padding` (56) - `DESede/CBC/NoPadding` (168) - `DESede/CBC/PKCS5Padding` (168) - `DESede/ECB/NoPadding` (168) - `DESede/ECB/PKCS5Padding` (168) - `RSA/ECB/PKCS1Padding` ( `1024,2048` ) - `RSA/ECB/OAEPWithSHA-1AndMGF1Padding` ( `1024,2048` ) - `RSA/ECB/OAEPWithSHA-256AndMGF1Padding` ( `1024,2048` )
6.2.7 改進DES示例

將DesTest類中,加密測試用例里只改動一行,改動獲取Cipher的方式改為︰
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");運行加密測試用例多次,發現每次加密結果都不一樣,是因為初始向量IV由于加密時我們沒有指定,所以會每次加密的時候系統隨機生成IV,將每次加密後的值都拿去解密,發現全部解密失敗,因為解密時需要IV值卻不知道IV值是什麼。所以程序需要進一步改進為加密時指定IV值,以方便解密時使用同樣的IV值來解密。
再次改進加密和解密都是用相同的IV值︰
/** * DES加密算法測試 */public class DesTest {    /**     * 測試DES加密     */    @Test    public void testEncrypt() throws Exception {        //明文        String text = "黑馬程序員";        //密鑰,長度必須為8個字節(字符)        byte[] secretKeyBytes = "12345678".getBytes();        //secretKeyBytes  = generateSecretKey("DES", 56);        // Cipher︰獲取密碼對象,參數按"算法/模式/填充模式"        Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");        // 參數1︰密鑰,key的字節數組,參數2︰加密算法        SecretKeySpec sks = new SecretKeySpec(secretKeyBytes, "DES");        //加密對象初始化數據,參數1︰模式,有加密模式和解密模式,參數2︰密鑰規則,參數3︰IV初始向量值        IvParameterSpec iv = new IvParameterSpec("22446688".getBytes());        cipher.init(Cipher.ENCRYPT_MODE, sks, iv);        //執行加密,得到加密結果        byte[] bytes = cipher.doFinal(text.getBytes());        //將byte數組轉成Base64編碼。        String result = Base64.encodeBase64String(bytes);        System.out.println("加密後的值︰" + result);    }    /**     * 測試DES解密     */    @Test    public void testDecrypt() throws Exception {        //密文        String crpyt = "FeHL4fKM/N1RSYOKJJ6ZZQ==";        //密鑰,長度必須為8個字節(字符)        byte[] secretKeyBytes = "12345678".getBytes();        //獲取Cipher對象        Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");        // 指定密鑰規則        SecretKeySpec sks = new SecretKeySpec(secretKeyBytes, "DES");        //加密對象初始化數據,參數1︰模式,有加密模式和解密模式,參數2︰密鑰規則,參數3︰IV初始向量值        IvParameterSpec iv = new IvParameterSpec("22446688".getBytes());        cipher.init(Cipher.DECRYPT_MODE, sks, iv);        //解密,上面使用的base64編碼,下面直接用密文        byte[] bytes = cipher.doFinal(Base64.decodeBase64(crpyt));        //  因為是明文,所以直接返回        String text = new String(bytes);        System.out.println("解密後的值︰"+ text) ;    }    /**     * 生成密鑰     * @param algorithm 算法     * @param len  密鑰長度     */    public static byte[] generateSecretKey(String algorithm, int len) throws NoSuchAlgorithmException {        KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm);//密鑰生成器        keyGenerator.init(len);//密鑰長度        SecretKey secretKey = keyGenerator.generateKey();//生成密鑰        return secretKey.getEncoded(); //密鑰字節數組轉字符串    }}3DS、AES的改進思路類似︰第一步修改轉換為DESede/CBC/PKCS5Padding、AES/CBC/PKCS5Padding,第二步在加密和解密的時候設置初始化向量為同一值。
3DES最終代碼︰
/** * 3DES加密算法測試 */public class Des3Test {    /**     * 測試3DES加密     */    @Test    public void testEncrypt() throws Exception {        //明文        String text = "黑馬程序員";        //密鑰,長度必須24個字節(字符)        byte[] secretKeyBytes = "123456781234567812345678".getBytes();        //可指定密鑰長度是168        //secretKeyBytes  = generateSecretKey("DESede", 168);        // Cipher︰獲取密碼對象,參數按"算法/模式/填充模式"        Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");        //參數1︰密鑰,key的字節數組,參數2︰加密算法        SecretKeySpec sks = new SecretKeySpec(secretKeyBytes, "DESede");        //加密對象初始化數據,參數1︰模式,有加密模式和解密模式,參數2︰密鑰規則,參數3︰IV初始向量值        IvParameterSpec iv = new IvParameterSpec("22446688".getBytes());        cipher.init(Cipher.ENCRYPT_MODE, sks, iv);        //執行加密,得到加密結果        byte[] bytes = cipher.doFinal(text.getBytes());        //將byte數組轉成Base64編碼。        String result = Base64.encodeBase64String(bytes);        System.out.println("加密後的值︰" + result);    }    /**     * 測試3DES解密     */    @Test    public void testDecrypt() throws Exception {        //密文        String crpyt = "FeHL4fKM/N1RSYOKJJ6ZZQ==";        //密鑰,長度必須24個字節(字符)        byte[] secretKeyBytes = "123456781234567812345678".getBytes();        // 獲取Cipher對象        Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");        // 指定密鑰規則        SecretKeySpec sks = new SecretKeySpec(secretKeyBytes, "DESede");        //加密對象初始化數據,參數1︰模式,有加密模式和解密模式,參數2︰密鑰規則,參數3︰IV初始向量值        IvParameterSpec iv = new IvParameterSpec("22446688".getBytes());        cipher.init(Cipher.DECRYPT_MODE, sks, iv);        // 解密,上面使用的base64編碼,下面直接用密文        byte[] bytes = cipher.doFinal(Base64.decodeBase64(crpyt));        //  因為是明文,所以直接返回        String text = new String(bytes);        System.out.println("解密後的值︰"+ text) ;    }    /**     * 生成密鑰     * @param algorithm 算法     * @param len  密鑰長度     */    public static byte[] generateSecretKey(String algorithm, int len) throws NoSuchAlgorithmException {        KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm);//密鑰生成器        keyGenerator.init(len);//密鑰長度        SecretKey secretKey = keyGenerator.generateKey();//生成密鑰        return secretKey.getEncoded(); //密鑰字節數組轉字符串    }}AES最終代碼
/** * AES加密算法測試 */public class AesTest {    /**     * 測試AES加密     */    @Test    public void testEncrypt() throws Exception {        //明文        String text = "黑馬程序員";        //密鑰,長度必須為16個字節(字符)        byte[] secretKeyBytes = "1234567812345678".getBytes();        //secretKeyBytes  = generateSecretKey("AES", 128);        // Cipher︰獲取密碼對象,參數按"算法/模式/填充模式"        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");        // 參數1︰密鑰,key的字節數組,參數2︰加密算法        SecretKeySpec sks = new SecretKeySpec(secretKeyBytes, "AES");        //加密對象初始化數據,參數1︰模式,有加密模式和解密模式,參數2︰密鑰規則,參數3︰IV初始向量值        IvParameterSpec iv = new IvParameterSpec("1122334455667788".getBytes());        cipher.init(Cipher.ENCRYPT_MODE, sks, iv);        //執行加密,得到加密結果        byte[] bytes = cipher.doFinal(text.getBytes());        //將byte數組轉成Base64編碼。        String result = Base64.encodeBase64String(bytes);        System.out.println("加密後的值︰" + result);    }    /**     * 測試AES解密     */    @Test    public void testDecrypt() throws Exception {        //密文        String crpyt = "VG+sz7FleJ5QMMe+elTkpg==";        //密鑰,長度必須為16個字節(字符)        byte[] secretKeyBytes = "1234567812345678".getBytes();        //獲取Cipher對象        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");        //指定密鑰規則        SecretKeySpec sks = new SecretKeySpec(secretKeyBytes, "AES");        //加密對象初始化數據,參數1︰模式,有加密模式和解密模式,參數2︰密鑰規則,參數3︰IV初始向量值        IvParameterSpec iv = new IvParameterSpec("1122334455667788".getBytes());        cipher.init(Cipher.DECRYPT_MODE, sks, iv);        //解密,上面使用的base64編碼,下面直接用密文        byte[] bytes = cipher.doFinal(Base64.decodeBase64(crpyt));        //  因為是明文,所以直接返回        String text = new String(bytes);        System.out.println("解密後的值︰"+ text) ;    }    /**     * 生成密鑰     * @param algorithm 算法     * @param len  密鑰長度     */    public static byte[] generateSecretKey(String algorithm, int len) throws NoSuchAlgorithmException {        KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm);//密鑰生成器        keyGenerator.init(len);//密鑰長度        SecretKey secretKey = keyGenerator.generateKey();//生成密鑰        return secretKey.getEncoded(); //密鑰字節數組轉字符串    }}6.3 非對稱密碼

加密密鑰和解密密鑰不相同,並且從一個很難(實際上不可能實現)推出另一個,又稱公鑰密碼體制(public-key cryptography system) 。公鑰密碼體制用一個密鑰進行加密( (驗證 ),而用另一個進行解密( 簽名)。其中一個密鑰可以公開,成為公開密鑰(pulic key),簡稱公鑰;另一個密鑰需要秘密保存,稱為私有密鑰(private key),簡稱私鑰 。

  • 公鑰和私鑰是一對
  • 如果用公開密鑰對數據進行加密,只有用對應的私有密鑰才能解密
  • 如果用私有密鑰對數據進行加密,只有用對應的公開密鑰才能解密
非對稱加密算法的優點︰安全性更高,公鑰是公開的,密鑰是自己保存的,不需要將私鑰給別人。
非對稱加密算法的缺點︰加密和解密花費時間長、速度慢,只適合對少量數據進行加密。
非對稱加密算法︰ RSA、Elgamal、背包算法、Rabin、D-H、ECC(橢圓曲線加密算法),常見的有RSA、ECC。
現代計算機和互聯網安全體系,很大程度上都依賴于公鑰密碼,但公鑰密鑰也存在風險,比如中間人攻擊。
6.3.1 中間人攻擊

中間人攻擊指的是主動攻擊者混入發送者和接收者中間,對發送者偽裝成接收者,對接收者偽裝成發送者的攻擊方式。如果要防御中間人攻擊,我們需要一種手段來確認接收者收到的公鑰是否真的屬于發送者,這種手段稱為認證,可以利用公鑰的證書來實現。


6.3.2 生成密鑰對

public class RSATest {        /**     * 測試生成密鑰對 公鑰和私鑰     */    @Test    public void testCreatePubKeyPriKey() throws NoSuchAlgorithmException {        //創建密鑰對生成器對象        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");        //生成密鑰對        KeyPair keyPair = keyPairGenerator.generateKeyPair();        //生成私鑰        PrivateKey privateKey = keyPair.getPrivate();        //生成公鑰        PublicKey publicKey = keyPair.getPublic();        //獲取私鑰字節數組        byte[] privateKeyEncoded = privateKey.getEncoded();        //獲取公鑰字節數組        byte[] publicKeyEncoded = publicKey.getEncoded();        //進行base64編碼        String privateKeyString = Base64.encodeBase64String(privateKeyEncoded);        String publicKeyString = Base64.encodeBase64String(publicKeyEncoded);        //輸出私鑰        System.out.println(privateKeyString);        //輸出公鑰        System.out.println(publicKeyString);    } }6.3.3 私鑰加密公鑰解密

    private  String privateKeyString = "私鑰的值";    private  String publicKeyString = "公鑰的值";    private  String algorithm = "RSA";    /**     * 測試私鑰加密     * @throws Exception     */    @Test    public void testPrikKeyEncrypt() throws Exception {        String text = "傳智播客";        PrivateKey privateKey = getPrivateKey();        //創建加密對象        Cipher cipher = Cipher.getInstance(algorithm);        //初始化加密,參數1︰加密模式,參數2︰使用私鑰進行加密        cipher.init(Cipher.ENCRYPT_MODE,privateKey);        //對明文進行加密        byte[] bytes = cipher.doFinal(text.getBytes());        System.out.println(Base64.encodeBase64String(bytes));    }    /**     * 測試公鑰解密     */    @Test    public void testPubKeyDecrypt() throws Exception {        String encrypt = "加密的值";        PublicKey publicKey = getPublicKey();        //創建加密對象        Cipher cipher = Cipher.getInstance(algorithm);        //初始化加密,參數1︰解密模式,參數2︰使用公鑰進行解密        cipher.init(Cipher.DECRYPT_MODE,publicKey);        //對密文進行解密        byte[] bytes = cipher.doFinal(Base64.decodeBase64(encrypt));        System.out.println(new String(bytes));    }    /**     * 獲取私鑰對象     */    private PrivateKey getPrivateKey() throws Exception{        //獲取密鑰工廠        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);        //構建密鑰規範 進行Base64解碼        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyString));        //生成私鑰        return keyFactory.generatePrivate(spec);    }    /**     * 獲取公鑰對象     */    private PublicKey getPublicKey() throws Exception{        //獲取密鑰工廠        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);        //構建密鑰規範 進行Base64解碼        X509EncodedKeySpec spec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyString));        //生成公鑰        return keyFactory.generatePublic(spec);    }6.3.4 公鑰加密私鑰解密

        /**     * 測試公鑰加密     * @throws Exception     */    @Test    public void testPubKeyEncrypt() throws Exception {        String text = "傳智播客";        PublicKey publicKey = getPublicKey();        //創建加密對象        Cipher cipher = Cipher.getInstance(algorithm);        //初始化加密,參數1︰加密模式,參數2︰使用公鑰進行加密        cipher.init(Cipher.ENCRYPT_MODE,publicKey);        //對明文進行加密        byte[] bytes = cipher.doFinal(text.getBytes());        System.out.println(Base64.encodeBase64String(bytes));    }    /**     * 測試私鑰解密     */    @Test    public void testPriKeyDecrypt() throws Exception {        String encrypt = "加密的值";        //公鑰進行解密        PrivateKey publicKey = getPrivateKey();        //初始化加密,參數1︰解密模式,參數2︰使用私鑰進行解密        Cipher cipher = Cipher.getInstance(algorithm);        cipher.init(Cipher.DECRYPT_MODE,publicKey);        //對密文進行解密        byte[] bytes = cipher.doFinal(Base64.decodeBase64(encrypt));        System.out.println(new String(bytes));    }6.3.5 代碼重構測試加解密

    /**     * 測試加密解密     * @throws Exception     */    @Test    public void testEncryptDecrypt() throws Exception {        PrivateKey privateKey = getPrivateKey();        PublicKey publicKey = getPublicKey();        //1.1私鑰加密        String  priEnrypt = encrypt(privateKey, "傳智播客");        //1.1公鑰解密        decrypt(publicKey,priEnrypt);        //2.1公鑰加密        String  pubEnrypt = encrypt(publicKey, "傳智播客");        //2.1私鑰解密        decrypt(privateKey,pubEnrypt);    }    /**     * 加密數據     */    private String encrypt(Key key, String input) throws Exception{        //創建加密對象        Cipher cipher = Cipher.getInstance(algorithm);        //初始化加密,參數1︰加密模式,參數2︰使用key進行加密        cipher.init(Cipher.ENCRYPT_MODE, key);        //私鑰加密        byte[] bytes = cipher.doFinal(input.getBytes());        //對密文進行Base64編碼        System.out.println("加密結果︰"+ Base64.encodeBase64String(bytes));        return Base64.encodeBase64String(bytes);    }    /**     * 解密數據     */    private String decrypt(Key key, String encrypted) throws Exception{        //創建加密對象        Cipher cipher = Cipher.getInstance(algorithm);        //初始化加密,參數1︰解密模式,參數2︰使用key進行解密        cipher.init(Cipher.DECRYPT_MODE,key);        //由于密文進行了Base64編碼, 在這里需要進行解碼        byte[] decode = Base64.decodeBase64(encrypted);        //對密文進行解密,不需要使用base64,因為原文不會亂碼        byte[] bytes = cipher.doFinal(decode);        System.out.println("解密結果︰"+ new String(bytes));        return new String(bytes);    }6.3.6 密鑰對保存及讀取


  • 保存公鑰和私鑰到文件
private String pubKeyPath = "test.pub";    private String priKeyPath = "test.pri";    /**     * 測試生成密鑰對 公鑰和私鑰     */    @Test    public void testCreatePubKeyPriKey() throws NoSuchAlgorithmException, IOException {        //創建密鑰對生成器對象        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");        //生成密鑰對        KeyPair keyPair = keyPairGenerator.generateKeyPair();        //生成私鑰        PrivateKey privateKey = keyPair.getPrivate();        //生成公鑰        PublicKey publicKey = keyPair.getPublic();        //獲取私鑰字節數組        byte[] privateKeyEncoded = privateKey.getEncoded();        //獲取公鑰字節數組        byte[] publicKeyEncoded = publicKey.getEncoded();        //進行base64編碼        String privateKeyString = Base64.encodeBase64String(privateKeyEncoded);        String publicKeyString = Base64.encodeBase64String(publicKeyEncoded);        //輸出私鑰        System.out.println(privateKeyString);        //輸出公鑰        System.out.println(publicKeyString);        // 保存文件        FileUtils.writeStringToFile(new File(pubKeyPath), publicKeyString, Charset.forName("UTF-8"));        FileUtils.writeStringToFile(new File(priKeyPath), privateKeyString, Charset.forName("UTF-8"));    }

  • 從文件讀取公鑰和私鑰
/**     * 獲取私鑰對象     */    private PrivateKey getPrivateKey() throws Exception{        String privateKeyString = FileUtils.readFileToString(new File(priKeyPath), Charset.defaultCharset());        //獲取密鑰工廠        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);        //構建密鑰規範 進行Base64解碼        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyString));        //生成私鑰        return keyFactory.generatePrivate(spec);    }    /**     * 獲取公鑰對象     */    private PublicKey getPublicKey() throws Exception{        String publicKeyString  = FileUtils.readFileToString(new File(pubKeyPath), Charset.defaultCharset());        //獲取密鑰工廠        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);        //構建密鑰規範 進行Base64解碼        X509EncodedKeySpec spec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyString));        //生成公鑰        return keyFactory.generatePublic(spec);    }6.4 隨機數

6.4.1 概念

對于密碼技術來說,算法的強度再高,只要攻擊者知道了密鑰,那麼安全就形同虛設。我們需要使用一種技術來隨機成成密鑰,使得攻擊者無法看穿,那麼這項技術就是隨機數技術。
隨機數技術的應用場景︰

  • 生成密鑰︰用于對稱密碼和消息認證碼。
  • 生成密鑰對︰用于公鑰密碼和數字簽名
  • 生成初始向量(IV)︰用于分組密碼的CBC、CFB和OFB模式。
  • 生成nonce︰用于防御重放共計及分組密碼的CTR模式
  • 生成鹽︰用于基于口令的密碼(PBE)
隨機數技術分類︰

  • 隨機性︰完全雜亂的數列
  • 不可預測性︰不能從過去的數列推測出下一個出現的數
  • 不可重現性︰除非將數列本身保存下來,佛則不能重現相同的數列
由上到下越來越嚴格,密碼技術使用的隨機數僅僅靠隨機性是不夠的,至少還要具備不可預測性才行。具備不可預測性的隨機數,一定具備隨機性。具備不可重現性的隨機數,一定具隨機性和不可預測性。


隨機性
不可預測性
不可重現性
說明
弱隨機數
具備




只具備隨機屬性,不可用與密碼技術
強偽隨機數
具備
具備


具備不可預測性,可用于密碼技術
真隨機數
具備
具備
具備
具備不可重現性,可用于密碼技術

嚴格程度,從低到高︰隨機性->不可預測性->不可重現性
6.4.2 偽隨機數生成器

隨機數可以通過硬件來生成,也可以通過軟件生成。
生成隨機數的硬件設備稱為隨機數生成器,生成隨機數的軟件稱為偽隨機數生成器。
1) 具體的偽隨機數生成器

  • 線性同余法
  • 不具備不可預測性,不可用于密碼技術,比如Java中的java.util.Random。核心是將當前隨機數乘以A再加上C,然後除以M得到的余數作為下一個偽隨機數。
  • 單項散列函數法
  • 具備不可預測性,可用于密碼技術。核心在于單項散列函數的單向性。
  • 密碼法
  • 具備不可預測性,可用于密碼技術。何在在于密碼的機密性。
  • ASNI X9.17
  • 具備不可預測性,可用于密碼技術。
2)偽隨機數生成器的攻擊
由于偽隨機數生成器承擔了生成密鑰的重任,會經常被當做攻擊對象。如果攻擊者知道了偽隨機數的種子,那麼他就能夠知道這個偽隨機數生成器所生成的全部偽隨機數數列。我們需要使用具備不可重現性的真隨機數作為種子。
實現方式︰事先準備好隨機池,隨機池內容要保護好,當密碼軟件需要偽隨機數的種子時,可以從隨機池中取出來需要的長度的隨機比特序列來使用。
隨機池︰

  • 比如linux中的/dev/random和/dev/urandom文件就是一個根據硬件設備驅動存儲真隨機數的隨機池
  • 比如Windows操作系統的CryptGenRandom接口
6.4.3 Java隨機數示例

Java編程中,能保證不可預測的隨機算法類是SecureRandom,可用于密碼技術
/** * 隨機數生成測試 */public class RandomTest {    @Test    public void testRandom() throws NoSuchAlgorithmException {        //linux環境下會讀取/dev/urandom非阻塞生成隨機數        SecureRandom random = new SecureRandom();        for (int i = 0; i < 100; i++) {            System.out.println(random.nextLong());        }    }}6.5 混合密碼系統

6.5.1 概念


  • 對稱密碼問題︰缺陷在于對于密鑰的管理上,以及在非安全信道中通訊時,密鑰交換的安全性不能保障,而公鑰密碼則解決了這個問題(也稱密鑰配送問題)
  • 公鑰密碼問題1︰處理速度遠遠低于對稱密碼
  • 公鑰密碼問題2︰會出現中間人攻擊
所以在實際的網絡環境中,會將兩者混合使用,將對稱密碼和非對稱密碼結合起來的方式就是混合密碼。混合密碼系統可以解決公鑰密碼速度慢的問題,中間人共計需要用到認證技術。
6.5.2 混合密碼系統組成機制


  • 用對稱密碼加密消息
  • 通過偽隨機生成器生成對稱密碼加密中使用的會話密鑰
  • 用公鑰密碼加密會話密鑰
  • 從混合密碼系統外部賦予公鑰密碼加密時使用的密鑰
混合密碼系統運用了偽隨機數生成器、對稱密碼、公鑰密碼這三種密碼技術,充分利用了對稱密碼和公鑰密碼的優勢。比如密碼軟件PGP,SSL/TLS技術都運用了混合密碼系統。
1)加密過程



  • 加密會話密鑰
  • 會話密鑰指的是為本次通信生成的臨時密鑰,一般通過偽隨機數生成器產生,產生會話密鑰的同時會傳給右側作為對稱密碼的密鑰使用。接著通過公鑰對會話密鑰進行加密,公鑰密碼加密所使用的密鑰是接收者的公鑰
  • 使用對稱密碼進行加密,密鑰為會話密鑰。如果內容很長,對稱密鑰依然能快速完成加密
  • 根據第一步和第二部的加密結果,按順序拼接成混合密碼系統的密文
2)解密過程



  • 分離密文︰根據發送者和接收者事先約定好的密文結構,將密文分離成兩部分
  • 解密會話密鑰︰用公鑰密碼解密會話密鑰,解密密鑰是接收者的私鑰。解密後的會話密鑰將被用作解密消息的密鑰
  • 解密消息︰使用解密後的會話密鑰利用對稱密碼進行解密

6.5.3 注意事項


  • 會話密鑰通過偽隨機數生成器生成,所以要選擇高強度的偽隨機數生成器
  • 對稱密碼被用來加密消息,所以要選擇高強度對稱密碼算法確保密鑰足夠的長度,並且選擇分組密碼模式。
  • 公鑰密碼被用來加密會話密鑰,所以要選擇高強度的公鑰密碼算法確保密鑰足夠的長度
  • 公鑰密碼的強度最好高于對稱密碼的強度,對稱密碼的會話密鑰如果被破譯只影響此次通信,公鑰密碼如果被破譯,會導致從過去到未來(用相同公鑰加密的)所有的通信內容都被破譯
6.5.4 代碼示例

AES工具類︰
/** * AES加解密工具類 */public class AESUtil {    /**     * AES加密     * @param text 待加密的明文     * @param secretKey 密鑰     * @param iv 初始向量     */    public static String encrypt(byte[] text,byte[] secretKey,byte[] iv) throws Exception {        // Cipher︰獲取密碼對象,參數按"算法/模式/填充模式"        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");        // 參數1︰密鑰,key的字節數組,參數2︰加密算法        SecretKeySpec sks = new SecretKeySpec(secretKey, "AES");        //加密對象初始化數據,參數1︰模式,有加密模式和解密模式,參數2︰密鑰規則,參數3︰IV初始向量值        IvParameterSpec ivParam = new IvParameterSpec(iv);        cipher.init(Cipher.ENCRYPT_MODE, sks, ivParam);        //執行加密,得到加密結果        byte[] bytes = cipher.doFinal(text);        //將byte數組轉成Base64編碼。        String result = Base64.encodeBase64String(bytes);        return result;    }    /**     * AES加密     * @param text 待加密的明文     * @param secretKey 密鑰     */    public static String encrypt(byte[] text,byte[] secretKey) throws Exception {        // Cipher︰獲取密碼對象,參數按"算法/模式/填充模式"        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");        // 參數1︰密鑰,key的字節數組,參數2︰加密算法        SecretKeySpec sks = new SecretKeySpec(secretKey, "AES");        //加密對象初始化數據,參數1︰模式,有加密模式和解密模式,參數2︰密鑰規則        cipher.init(Cipher.ENCRYPT_MODE, sks);        //執行加密,得到加密結果        byte[] bytes = cipher.doFinal(text);        //將byte數組轉成Base64編碼。        String result = Base64.encodeBase64String(bytes);        return result;    }    /**     * AES解密     * @param encrypted 待解密的密文     * @param secretKey 密鑰     * @param iv 初始向量值     */    public static String decrypt(String encrypted,byte[] secretKey, byte[] iv) throws Exception {        //秘鑰,長度必須為16個字節(字符)        //獲取Cipher對象        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");        //指定密鑰規則        SecretKeySpec sks = new SecretKeySpec(secretKey, "AES");        //加密對象初始化數據,參數1︰模式,有加密模式和解密模式,參數2︰密鑰規則,參數3︰IV初始向量值        IvParameterSpec ivParam = new IvParameterSpec(iv);        cipher.init(Cipher.DECRYPT_MODE, sks, ivParam);        //解密,上面使用的base64編碼,下面直接用密文        byte[] bytes = cipher.doFinal(Base64.decodeBase64(encrypted));        //  因為是明文,所以直接返回        String text = new String(bytes);        return text;    }    /**     * AES解密     * @param encrypted 待解密的密文     * @param secretKey 密鑰     */    public static String decrypt(byte[] encrypted,byte[] secretKey) throws Exception {        //秘鑰,長度必須為16個字節(字符)        //獲取Cipher對象        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");        //指定密鑰規則        SecretKeySpec sks = new SecretKeySpec(secretKey, "AES");        //加密對象初始化數據,參數1︰模式,有加密模式和解密模式,參數2︰密鑰規則        cipher.init(Cipher.DECRYPT_MODE, sks);        //解密,上面使用的base64編碼,下面直接用密文        byte[] bytes = cipher.doFinal(encrypted);        //  因為是明文,所以直接返回        String text = new String(bytes);        return text;    }    /**     * 生成密鑰     * @param algorithm 算法     * @param len  密鑰長度     */    public static byte[] generateSecretKey(String algorithm, int len) throws NoSuchAlgorithmException {        KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm);//密鑰生成器        keyGenerator.init(len);//密鑰長度        SecretKey secretKey = keyGenerator.generateKey();//生成密鑰        return secretKey.getEncoded(); //密鑰字節數組轉字符串    }}RSA 工具類:
/** * RSA加解密工具類 */public class RSAUtil {    private static String algorithm = "RSA";    /**     * 加密數據     * @param key 公鑰或者私鑰     * @param text 待加密的明文     */    public static String encrypt(Key key, byte[] text) throws Exception{        //創建加密對象        Cipher cipher = Cipher.getInstance(algorithm);        //初始化加密,參數1︰加密模式,參數2︰使用key進行加密        cipher.init(Cipher.ENCRYPT_MODE, key);        //私鑰加密        byte[] bytes = cipher.doFinal(text);        //對密文進行Base64編碼        return Base64.encodeBase64String(bytes);    }    /**     * 解密數據     * @param key 公鑰或者私鑰     * @param encrypted 待解密的密文     */    public static byte[] decrypt(Key key, String encrypted) throws Exception{        //創建加密對象        Cipher cipher = Cipher.getInstance(algorithm);        //初始化加密,參數1︰解密模式,參數2︰使用key進行解密        cipher.init(Cipher.DECRYPT_MODE,key);        //由于密文進行了Base64編碼, 在這里需要進行解碼        byte[] decode = Base64.decodeBase64(encrypted);        //對密文進行解密,不需要使用base64,因為原文不會亂碼        byte[] bytes = cipher.doFinal(decode);        return bytes;    }    private static String pubKeyPath = "test.pub";    private static String priKeyPath = "test.pri";    /**     * 生成密鑰對公鑰和私鑰     */    public static void createPubKeyPriKey() throws NoSuchAlgorithmException, IOException {        //創建密鑰對生成器對象        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");        //生成密鑰對        KeyPair keyPair = keyPairGenerator.generateKeyPair();        //生成私鑰        PrivateKey privateKey = keyPair.getPrivate();        //生成公鑰        PublicKey publicKey = keyPair.getPublic();        //獲取私鑰字節數組        byte[] privateKeyEncoded = privateKey.getEncoded();        //獲取公鑰字節數組        byte[] publicKeyEncoded = publicKey.getEncoded();        //進行base64編碼        String privateKeyString = Base64.encodeBase64String(privateKeyEncoded);        String publicKeyString = Base64.encodeBase64String(publicKeyEncoded);        //輸出私鑰        System.out.println(privateKeyString);        //輸出公鑰        System.out.println(publicKeyString);        // 保存文件        FileUtils.writeStringToFile(new File(pubKeyPath), publicKeyString, Charset.forName("UTF-8"));        FileUtils.writeStringToFile(new File(priKeyPath), privateKeyString, Charset.forName("UTF-8"));    }    /**     * 獲取私鑰對象     */    public static PrivateKey getPrivateKey() throws Exception{        String privateKeyString = FileUtils.readFileToString(new File(priKeyPath), Charset.defaultCharset());        //獲取密鑰工廠        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);        //構建密鑰規範 進行Base64解碼        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyString));        //生成私鑰        return keyFactory.generatePrivate(spec);    }    /**     * 獲取公鑰對象     */    public static PublicKey getPublicKey() throws Exception{        String publicKeyString  = FileUtils.readFileToString(new File(pubKeyPath), Charset.defaultCharset());        //獲取密鑰工廠        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);        //構建密鑰規範 進行Base64解碼        X509EncodedKeySpec spec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyString));        //生成公鑰        return keyFactory.generatePublic(spec);    }    public static void main(String[] args) throws Exception {        PrivateKey privateKey = RSAUtil.getPrivateKey();        PublicKey publicKey = RSAUtil.getPublicKey();        String  pubEnrypt = encrypt(publicKey, "傳智播客".getBytes());        System.out.println(pubEnrypt);        //2.1私鑰解密        byte[] decrypt = decrypt(privateKey, pubEnrypt);        System.out.println(new String(decrypt));    }}混合密碼測試類︰
/** * 混合密碼測試 */public class MixCryptTest {    @Test    public void testMxiCrypt() throws Exception {        //準備好公鑰與私鑰,生成到文件中        RSAUtil.createPubKeyPriKey(); //其中公鑰給客戶端使用,私鑰服務端保留,下方代碼表示客戶端給服務端發送混合加密消息        // *********** 客戶端發送消息開始 *********** //        //準備消息明文        byte[] text = "大家好,我來自傳智播客大家好,我來自傳智播客".getBytes();        //偽隨機數生成器生成會話密鑰        byte[] sessionSecretKey = AESUtil.generateSecretKey("AES", 128);        //公鑰密碼算法用公鑰加密會話密鑰        String secretKeyEncrypted = RSAUtil.encrypt(RSAUtil.getPublicKey(), sessionSecretKey);        System.out.println("[客戶端]會話密鑰加密結果︰" + secretKeyEncrypted);        //為對稱加密算法準備加密時候初始向量值        byte[] iv = AESUtil.generateSecretKey("AES", 128);        //對稱密碼算法用會話密鑰加密消息        String textEncrypt = AESUtil.encrypt(text, sessionSecretKey, iv);        System.out.println("[客戶端]消息明文加密結果︰" + textEncrypt);        //拼接兩個加密後的值        byte[] b1 = Base64.decodeBase64(secretKeyEncrypted);        int len1 = b1.length;        byte[] b2 = Base64.decodeBase64(textEncrypt);        int len2 = b2.length;        //拼接兩個加密後的值        String splitFlag = "==itcast==";        byte[] flag = splitFlag.getBytes("UTF-8");        int lenFlag = flag.length;        byte[] bFinal = new byte[len1+lenFlag+len2];        System.arraycopy(b1, 0, bFinal, 0, len1);        System.arraycopy(b2, 0, bFinal, len1, lenFlag);        System.arraycopy(b2, 0, bFinal, len1+lenFlag, len2);        String finalStr = Base64.encodeBase64String(bFinal);        System.out.println("[客戶端]拼接後的加密結果︰" + finalStr);        // *********** 客戶端發送消息結束 *********** //        // *********** 服務端解密消息開始 *********** //        //開始分離數據        byte[] finalBytes = Base64.decodeBase64(finalStr);        byte[] sskEncryptedBytes = new byte[len1];        byte[] textEncryptedBytes = new byte[len2];        System.arraycopy(finalBytes, 0, sskEncryptedBytes, 0, len1);        System.arraycopy(finalBytes, len1+lenFlag, textEncryptedBytes, 0, len2);        String sskEncrypt = Base64.encodeBase64String(sskEncryptedBytes);        String textEncrypted = Base64.encodeBase64String(textEncryptedBytes);        //公鑰密碼用私鑰解密會話密鑰的密文得到會話密鑰明文        byte[] ssKBytes = RSAUtil.decrypt(RSAUtil.getPrivateKey(), sskEncrypt);        //對稱密碼用會話密鑰的明文解密消息        String textPlain = AESUtil.decrypt(textEncrypted, ssKBytes, iv);        System.out.println("[服務端]密文解密結果︰" + textPlain);        // *********** 服務端解密消息結束 *********** //    }}五、結束語

在今日文章中,我們體系化的了解到了密碼的含義,介紹了現代密碼學涉及到的編碼技術和密碼技術,其中最重要的就是對稱密碼技術和公鑰密碼技術,為了保證密碼的安全我們還了解到了分組密碼的加密模式和填充模式、及隨機數和混合密碼系統。通過今日的學習,相信大家內心中都已經揭開了密碼的神秘面紗。當然,有關于密碼相關的內容絕不僅僅只是今天這麼多,還有非常多密碼組合技術,比如下方這些密碼技術等著我們進一步探究︰

  • 數字簽名︰單項散列函數和公鑰密碼的組合
  • 證書︰公鑰和數字簽名的組合
  • 消息認證碼︰單項散列函數和密碼組組合,或者通過對稱密碼來生成
六、附錄.密碼發展歷史

說明︰該部分內容收集整理自網絡
1. 第一階段︰遠古密碼

時間範圍︰遠古到公元前400年左右
密碼特征︰
這個時期所謂“密碼”本質上是古人根據物理工具特性所作出的改造和偽裝,當中並沒有涉及到啟發式的數學思維或方法,我們稱之為遠古密碼。
1.1 陰符

世界上最早的密碼工具普遍被認為是在公元前1000年由姜子牙發明的軍用“陰符”。相傳商紂王末年,姜太公輔佐周室。有一次,姜太公帶領的周軍指揮大營被叛兵包圍,情況危急,姜太公令信使突圍,回朝搬兵,但又怕信使遺忘機密,或者周文王不認識信使,耽誤了軍務大事。于是其將自己珍愛的魚竿折成數節,每節長短不一,各代表一件軍機,令信使牢記,不得外傳。信使幾經周折回到朝中,周文王令左右將幾節魚竿合在一起,親自檢驗。周文王辨認出是姜太公的心愛之物,于是親率大軍,解救姜太公,“陰符”由此誕生。
當時“陰符”的規格共有8種(即8個不同的尺寸),每種規格分別代表一種狀態。比如,長為1尺的“陰符”表示大獲全勝、全殲敵軍;長為4寸的“陰符”表示軍隊失敗,將領陣亡等。
尺寸
狀態含義
1尺
大獲全勝、全殲敵軍
9寸
擊破敵軍、擒獲敵將
8寸
迫使敵軍投降、攻佔敵人城邑
7寸
擊退敵人、通報戰況
6寸
激勵軍民、堅強守御
5寸
請求補給糧草、增加兵力
4寸
報告軍隊失敗、將領陣亡
3寸
戰斗失利、士卒傷亡

1.2 陰書

陰符雖有簡便保密的特點,但卻過于簡單,無法表達更復雜的含義。為此,姜子牙又進一步發明了"陰書",即將豎寫的秘密文書橫截成3段,並派出3位信使各執一段,于不同時間、不同路線分別出發,先後送達收件者。任何人只有在收齊3段文件以後才能獲悉秘密文書的全部內容。
1.3 塞塔密碼

公元前400年,古希臘的斯巴達人發明了塞塔(scytale)密碼,也稱密碼棒。他們把長條紙螺旋地斜繞在一根多稜棒上,讓紙條被多稜棒分割成一個個的類似格子一樣的區域,並沿著這個區域,將文字沿棒的水平方向從左到右書寫,寫一個字旋轉一下,換一個區域,寫完一行再另起一行從左到右寫。這樣一來,本來完整通順的一句話,就被機械地分割開了。將信息寫完後,解下來的紙條上的文字消息雜亂無章、無法理解,這就將原來的信息明文轉換成了密文。在把展開的紙條傳遞出去以後,解密人只有將紙條斜繞在另一根同等尺寸的多稜棒上才能看到原始消息。


1.4 隱語

《左傳€€宣公十二年》記載了一種用于替換信息的“隱語”法。所謂“隱語”,就是電影里常見的暗語、黑話,用一個完全不相關的詞來代表特定含義。
2. 第二階段︰古典密碼

時間範圍︰公元前400年-20世紀初 (classical cryptography),長達幾千年
密碼特征︰
古典密碼編碼方法的核心是代替和置換。

  • 代替︰代替密碼是將明文中的字符替代成其他字符。代替密碼是指先建立一個替換表,加密時將需要加密的明文依次通過查表,替換為相應的字符,明文字符被逐個替換後,生成無任何意義的字符串,即密文,替代密碼的密鑰就是其替換表。
  • 置換︰置換密碼又稱換位密碼,是根據一定的規則重新排列明文,以便打破明文的結構特性。置換密碼的特點是保持明文的所有字符不變,只是利用置換打亂了明文字符的位置和次序。也就是說,改變了明文的結構,不改變明文的內容。
2.1 愷撒密碼

公元前58年左右由凱撒大帝發明該密碼應用在軍事通信之中,故而得名為愷撒密碼(Caesar cipher)。愷撒密碼是一種代替密碼,其加密方式就是將英文中的每個字母用另外一個字母來代替。愷撒密碼是對英文26個字母進行移位代替的密碼,屬于“移位代替密碼”,是最簡單的一類代替密碼。愷撒密碼的本質是構建了一張明密代替表,即密碼本。明密代替表就像一把鑰匙,用這把鑰匙可以方便地進行“加密”和“解密”操作。


2.2 棋盤密碼

公元前兩世紀,古希臘人發明了利用Polybius方陣加密的方法,即棋盤密碼。所謂棋盤密碼,就是把字母按序或亂序填入表格里,並對應行和列進行加密,比如最簡單的棋盤密碼︰


1
2
3
4
1
A
B
C
D
2
E
F
G
H
3
I
J
K
L
4
M
N
O
P

假設明文是GOD,那麼加密後則為23 43 14。棋盤密碼雖然簡單,但實用性非常好。
2.3 圓盤密碼

15世紀末,佛羅洛薩人Alberti又發明了圓盤密碼,也稱密碼盤。所謂圓盤密碼,就是兩個同心圓盤上,內盤隨機填寫字符,而外盤的字符則按照一定順序來填寫。使用者只需轉動圓盤就可以找到內外盤字符間的映射方式


2.4 維吉尼亞密碼

上述密碼都可以統稱為單表代替密碼,即明文和密文之間均具有一一對應的關系,了打破這種對應關系,1553年,Giovan Battista Bellaso在其著作中發明了通過字母密鑰來循環加密明文的“維吉尼亞”密碼(也稱維熱納爾密碼,Vigenere密碼)。假設要加密的明文是︰TO BE OR NOT TO BE THAT IS THE QUESTION,選擇某一關鍵詞並重復而得到密鑰,如關鍵詞為RELATIONS,即RE LA TI ONS RE LA TION SR ELA TIONSREL。對于明文的第一個字母T,對應密鑰的第一個字母R,于是使用表格中R行字母表進行加密,就可以在一個特定的26*26表格中搜尋第R行、第T列所對應的字母作為T的密文。以此類推,得到密文為︰KSMEH ZBBLK SMEMP OGAJX SEJCS FLZSY。
得出對應關系如下︰
明文:TO BE OR NOT TO BE THAT IS THE QUESTION
密鑰:RE LA TI ONS RE LA TION SR ELA TIONSREL
密文:KS ME HZ BBL KS ME MPOG AJ XSE JCSFLZSY


解密︰密鑰第一個字母R對應的行中,查找密文的第一個字母K,發現K對應的是t列,所以明文是T,以此類推。
2.5 柵欄密碼

古典密碼中還有一類著名的密碼是“置換密碼”,置換密碼不是用密文字母代替相對應的明文字母,而是通過打亂明文字母的位置,使有意義的明文信息變換為無意義的密文亂碼。柵欄密碼是置換密碼的典型代表。柵欄密碼出現于1861年至1865年的美國南北戰爭時期。其加密原理是︰明文按列寫入,密文按行輸出。
加密原理︰把將要傳遞的信息中的字母交替排成上下兩行。再將下面一行字母排在上面一行的後邊,從而形成一段密碼。比如下面的示例︰
明文︰THE LONGEST DAY MUST HAVE AN END

  • 加密︰
1、把將要傳遞的信息中的字母交替排成上下兩行。
T E O G S D Y U T A E N N
H L N E T A M S H V A E D
2、 密文︰
將下面一行字母排在上面一行的後邊。
TEOGSDYUTAENN HLNETAMSHVAED

  • 解密︰
先將密文分為兩行
T E O G S D Y U T A E N N
H L N E T A M S H V A E D
再按上下上下的順序組合成一句話
明文︰THE LONGEST DAY MUST HAVE AN END
2.6 隱寫術

與密碼技術對應還有一種技術,目的不是為了讓消息變得無法解讀,而是想辦法隱藏消息本身,這種技術一般稱為隱寫術。隱寫術一般分為兩類︰

  • 隱寫藥水
  • 在古代的戰爭中,多見使用隱藏信息的方式保護重要的通信資料。比如先把需要保護的信息用化學藥水寫到紙上,藥水干後,紙上看不出任何的信息,需要使用另外的化學藥水涂抹後才可以閱讀紙上的信息。
  • 藏頭尾詩詞
  • 比如《唐寅詩集》中“我愛秋香”。如下︰
  • 我畫蘭江水悠悠
  • 愛晚亭上楓葉稠
  • 秋月融融照佛寺
  • 香煙裊裊繞輕樓
總結︰古典密碼中不管是愷撒密碼還是柵欄密碼,都有自身的缺點可以被破譯者利用,但是如果將置換密碼與代替密碼二者組合起來,則可以在一定程度上使得變換更復雜。乘積密碼就是以某種方式連續執行兩個或多個密碼變換技術,從而使最後加密結果更隨機,從密碼編碼的安全性角度來看,比單一的代替密碼和置換密碼都更強。古典密碼中的代替密碼(主要代表為愷撒密碼)與置換密碼(主要代表為柵欄密碼)可以組合成多種新的密碼形式,即乘積密碼的具體形式,這也是分組密碼的雛形。
3. 第三階段︰近代密碼

時間範圍︰20世紀初到20世紀50年代,包含1戰和2戰時期
密碼特征︰密碼機的迅速發展,越來越多的數學家加入密碼隊伍
在戰爭中,密碼設計者不斷地設計出新的密碼,這些密碼又不斷地被密碼分析者破譯。設計和破譯就像矛和盾,此消彼長,密碼在戰爭中不斷發展演變,越來越復雜,越來越難以破譯。手工作業方式已難以滿足復雜密碼運算的要求,密碼研究者設計出了一些復雜的機械和電動機械設備,實現了信息的加解密操作,近代密碼時期宣告到來。
3.1 Playfair密碼

在第一次世界大戰前期,英軍陸軍主要使用是英國物理學家Wheatstone在1854年發明的Playfair密碼。Playfair密碼的核心是通過使用一個關鍵詞方格來對字符對進行加密,此前曾被廣泛用于克里米亞和布爾戰爭。但在1915年,Playfair密碼就被同盟國破解了。
3.2 一次性便箋密碼

1918年,美國數學家Gillbert Vernam發明出一次性便箋密碼,它是一種理論上絕對無法破譯的加密系統,被譽為密碼編碼學的聖杯。但由于產生和分發大量隨機密鑰的過程十分困難,因此這種密碼的實際應用受到了很大限制。從另一方面來說,其安全性也更加無法保證。
3.3 恩尼格瑪密碼

1919年,德國工程師Arthur Scherbius設計出了歷史上最著名的密碼機︰Enigma機,恩尼格瑪密碼機是轉子機械密碼機的統稱,它包括了一系列不同的型號,由德國人亞瑟€€謝爾比烏斯和理查德€€里特發明,在20世紀20年代開始被用于商業,後來被一些國家的軍隊與政府進行改造並使用,最著名的是掀起第二次世界大戰的納粹德國。
最先破解早期Enigma密碼機的是波蘭人,其利用德軍電報中前幾個字母的重復出現,摸索出了破譯方案,並將其告知英軍和法軍。英軍在計算機理論之父Turing的帶領下,通過尋找德軍在密鑰選擇上的失誤以及借助戰爭中成功奪取的德軍密碼本破解出重要的德軍情報。
3.4 JN-25密碼

第二次世界大戰中,密碼攻防戰持續升級。除盟軍對恩尼格瑪密碼機破譯的典型案例外,在亞洲戰場,編碼與破譯之間的斗爭同樣是驚心動魄。1943年春天,山本五十六為了控制不斷惡化的殘局,親自前往所羅門群島基地巡視,以鼓舞士氣。1943年4月13日,日軍第八艦隊司令將山本一行視察的行程、時間表,用D號密碼(美軍稱為JN-25)加密後發給有關基地。盡管該密碼的密碼本在4月2日剛剛被換過,但美國破譯人員根據過去收集的資料,利用多表代替的統計特性破譯了這份密報。經過周密安排,美軍飛機于4月18日擊落了飛往視察地途中的山本五十六乘坐的飛機。
4. 第四階段︰ 現代密碼

時間範圍︰1949年至今
密碼特征︰數據的安全基于密鑰而不是算法的保密
1949年信息論創始人Shannon香農發表劃時代論文《The Communication Theory of Secret Systems》,其中定義了理論安全性,提出擴散和混淆原則。奠定了現代密碼學的理論基礎。
1972年,IBM研制出了對稱密碼體制加密算法。3年以後,美國國家標準局將其頒布為國家標準,即數據加密標準(DES)。這是密碼學歷史上一個頗具里程碑意義的事件。
1976年,W.Diffie和M.Hellman發表了《密碼學的新方向》一文,文中首次提出了適應網絡保密通信需求的公鑰密碼思想,掀起了公鑰密碼學的序幕。次年,美國的Ronald Rivest、Adi Shamir和Len Adleman提出了第一個建立在大數因子分解基礎上的公鑰密碼算法,即著名的RSA算法。
1976年,Victor Miller和Neal Koblitz分別提出了如今家喻戶曉的橢圓曲線密碼學(ECC)。盡管在當時而言,ECC更接近于數學理想的範疇,不具備實用性,但ECC在安全性上的優勢以及實現效率使其一直成為密碼學家們樂此不疲的研究課題€€€€相比起其它公鑰密碼算法,ECC的抗攻擊性具有絕對的優勢。例如,使用ECC加密的256位密鑰所提供的安全性,與使用RSA或DSA加密的3072位密鑰相當。這意味著ECC算法所要求的帶寬更低、存儲空間更小。這些優點在某些對于帶寬、處理器能力或存儲空間有限制的應用(比如移動設備)中顯得尤為重要。
90年代初,麻省理工學院教授Ronald Rivest提出了MD4信息摘要算法,它是一種用來測試信息完整性的哈希函數。MD4的實現,旋即開啟了哈希函數的大門,包括後來Ronald Rivest重新提出的安全性更優的MD5,由NSA和NIST提出的SHA函數家族以及Hans Dobbertin,Antoon Bosselaers 和 Bart Prenee提出的RIPEMD。在這一時期,密碼學家來學嘉和JamesMasseey還提出了在軟硬件實現上比DES更優的國際數據加密算法,即IDEA。
隨著計算機能力的不斷提高,不少密碼體系比如主流的DES正逐步面臨淘汰。1998年,電子邊境基金會(EFF)利用耗資25萬美元打造的專用計算機,僅用56個小時就成功破解了DES密鑰。隨後在1999年,EFF僅用了22小時15分就完成了破解工作。此後,美國國家安全局宣布棄用DES轉而啟用由比利時密碼學家Joan Daemen和Vincent Rijmen所提出的Rijndael加密算法,即高級加密標準AES。
進入千禧年以後,MD4、MD5、RIPEMD(RIPEMD-160依旧安全)、SHA1以及RSA-768的強抗踫撞性相繼被攻破,RSA-1024業已在2012年前後被停用。隨著區塊鏈技術的興起,ECC儼然成為密碼學殿堂最亮眼的新星,但依舊難逃量子計算技術的威脅。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|小黑屋|黑莲技术资源论坛 ( 闽ICP备18016623号-7 )|网站地图

GMT+8, 2022-8-19 03:51 , Processed in 0.917438 second(s), 26 queries .

Powered by BBS.HL1.NET X3.4 © 2020-2022

本站IT社区(bbs.hl1.net)所有的资源教程均来自网友分享及互联网收集

快速回复 返回顶部 返回列表