IOS 逆向開發(二)密碼學 HASH

孔雨露發表於2020-04-05

1. HASH演算法簡介

1.1 HASH是什麼?

  • Hash演算法(也叫雜湊演算法)

Hash,一般翻譯做“雜湊”,也有直接音譯為“雜湊”的,就是把任意長度的輸入通過雜湊演算法變換成固定長度的輸出,該輸出就是雜湊值。這種轉換是一種壓縮對映,也就是,雜湊值的空間通常遠小於輸入的空間,不同的輸入可能會雜湊成相同的輸出,所以不可能從雜湊值來確定唯一的輸入值。簡單的說就是一種將任意長度的訊息壓縮到某一固定長度的訊息摘要的函式

先舉個例子。我們每個活在世上的人,為了能夠參與各種社會活動,都需要一個用於識別自己的標誌。也許你覺得名字或是身份證就足以代表你這個人,但是這種代表性非常脆弱,因為重名的人很多,身份證也可以偽造。最可靠的辦法是把一個人的所有基因序列記錄下來用來代表這個人,但顯然,這樣做並不實際。而指紋看上去是一種不錯的選擇,雖然一些專業組織仍然可以模擬某個人的指紋,但這種代價實在太高了。 而對於在網際網路世界裡傳送的檔案來說,如何標誌一個檔案的身份同樣重要。比如說我們下載一個檔案,檔案的下載過程中會經過很多網路伺服器、路由器的中轉,如何保證這個檔案就是我們所需要的呢?我們不可能去一一檢測這個檔案的每個位元組,也不能簡單地利用檔名、檔案大小這些極容易偽裝的資訊,這時候,我們就需要一種指紋一樣的標誌來檢查檔案的可靠性,這種指紋就是我們現在所用的Hash演算法(也叫雜湊演算法)。

  • 雜湊演算法(Hash Algorithm),又稱雜湊演算法,雜湊演算法,是一種從任意檔案中創造小的數字「指紋」的方法。與指紋一樣,雜湊演算法就是一種以較短的資訊來保證檔案唯一性的標誌,這種標誌與檔案的每一個位元組都相關,而且難以找到逆向規律。因此,當原有檔案發生改變時,其標誌值也會發生改變,從而告訴檔案使用者當前的檔案已經不是你所需求的檔案。
  1. 這種標誌有何意義呢? 之前檔案下載過程就是一個很好的例子,事實上,現在大部分的網路部署和版本控制工具都在使用雜湊演算法來保證檔案可靠性。而另一方面,我們在進行檔案系統同步、備份等工具時,使用雜湊演算法來標誌檔案唯一效能幫助我們減少系統開銷,這一點在很多雲端儲存伺服器中都有應用。
  2. 當然,作為一種指紋,雜湊演算法最重要的用途在於給證書、文件、密碼等高安全係數的內容新增加密保護。這一方面的用途主要是得益於雜湊演算法的不可逆性,這種不可逆性體現在,你不僅不可能根據一段通過雜湊演算法得到的指紋來獲得原有的檔案,也不可能簡單地創造一個檔案並讓它的指紋與一段目標指紋相一致。雜湊演算法的這種不可逆性維持著很多安全框架的運營,

1.2 Hash的特點

  1. 演算法是公開的

  2. 對相同資料運算,得到的結果是一樣的

  3. 對不同資料運算,如MD5得到的結果預設是128位,32個字元(16進位制標識)。

  4. 沒法逆運算

  5. 一個優秀的 hash 演算法,將能實現:

  1. 正向快速:給定明文和 hash 演算法,在有限時間和有限資源內能計算出 hash 值。
  2. 逆向困難:給定(若干) hash 值,在有限時間內很難(基本不可能)逆推出明文。
  3. 輸入敏感:原始輸入資訊修改一點資訊,產生的 hash 值看起來應該都有很大不同。
  4. 衝突避免:很難找到兩段內容不同的明文,使得它們的 hash 值一致(發生衝突)。即對於任意兩個不同的資料塊,其hash值相同的可能性極小;對於一個給定的資料塊,找到和它hash值相同的資料塊極為困難。

1.3 Hash的作用

  • 主要用途有:
  1. 使用者密碼的加密
  2. 搜尋引擎
  3. 版權
  4. 數字簽名
  • Hash在在密碼學中的應用

在密碼學中,hash演算法的作用主要是用於訊息摘要和簽名,換句話說,它主要用於對整個訊息的完整性進行校驗。舉個例子,我們登陸知乎的時候都需要輸入密碼,那麼知乎如果明文儲存這個密碼,那麼黑客就很容易竊取大家的密碼來登陸,特別不安全。那麼知乎就想到了一個方法,使用hash演算法生成一個密碼的簽名,知乎後臺只儲存這個簽名值。由於hash演算法是不可逆的,那麼黑客即便得到這個簽名,也絲毫沒有用處;而如果你在網站登陸介面上輸入你的密碼,那麼知乎後臺就會重新計算一下這個hash值,與網站中儲存的原hash值進行比對,如果相同,證明你擁有這個賬戶的密碼,那麼就會允許你登陸。銀行也是如此,銀行是萬萬不敢儲存使用者密碼的原文的,只會儲存密碼的hash值而而已。在這些應用場景裡,對於抗碰撞和抗篡改能力要求極高,對速度的要求在其次。一個設計良好的hash演算法,其抗碰撞能力是很高的。以MD5為例,其輸出長度為128位,設計預期碰撞概率為2^128,這是一個極小極小的數字——而即便是在MD5被王小云教授破解之後,其碰撞概率上限也高達,也就是說,至少需要找次才能有1/2的概率來找到一個與目標檔案相同的hash值。

1.4 Hash有哪些流行的演算法

  • 目前流行的 Hash 演算法包括 MD5、SHA-1 和 SHA-2。

  • MD4(RFC 1320)是 MIT 的 Ronald L. Rivest 在 1990 年設計的,MD 是 Message Digest 的縮寫。其輸出為 128 位。MD4 已證明不夠安全。

  • MD5(RFC 1321)是 Rivest 於1991年對 MD4 的改進版本。它對輸入仍以 512 位分組,其輸出是 128 位。MD5 比 MD4 複雜,並且計算速度要慢一點,更安全一些。MD5 已被證明不具備"強抗碰撞性"。

  • SHA (Secure Hash Algorithm)是一個 Hash 函式族,由 NIST(National Institute of Standards and Technology)於 1993 年釋出第一個演算法。目前知名的 SHA-1 在 1995 年面世,它的輸出為長度 160 位的 hash 值,因此抗窮舉性更好。SHA-1 設計時基於和 MD4 相同原理,並且模仿了該演算法。SHA-1 已被證明不具"強抗碰撞性"。

  • 為了提高安全性,NIST 還設計出了 SHA-224、SHA-256、SHA-384,和 SHA-512 演算法(統稱為 SHA-2),跟 SHA-1 演算法原理類似。SHA-3 相關演算法也已被提出。

1.5 Hash演算法的碰撞

  • 如果我們隨便拿9個數去計算,肯定至少會得到兩個相同的值,我們把這種情況就叫做雜湊演算法的「碰撞」(Collision)

這很容易理解,因為作為一種可用的雜湊演算法,其位數一定是有限的,也就是說它能記錄的檔案是有限的——而檔案數量是無限的,兩個檔案指紋發生碰撞的概率永遠不會是零。 但這並不意味著雜湊演算法就不能用了,因為凡事都要考慮代價,買光所有彩票去中一次頭獎是毫無意義的。現代雜湊演算法所存在的理由就是,它的不可逆效能在較大概率上得到實現,也就是說,發現碰撞的概率很小,這種碰撞能被利用的概率更小。 隨意找到一組碰撞是有可能的,只要窮舉就可以。雜湊演算法得到的指紋位數是有限的,比如MD5演算法指紋字長為128位,意味著只要我們窮舉2^128次,就肯定能得到一組碰撞——當然,這個時間代價是難以想象的,而更重要的是,僅僅找到一組碰撞並沒有什麼實際意義。更有意義的是,如果我們已經有了一組指紋,能否找到一個原始檔案,讓它的雜湊計算結果等於這組指紋。如果這一點被實現,我們就可以很容易地篡改和偽造網路證書、密碼等關鍵資訊。 你也許已經聽過MD5已經被破解的新聞——但事實上,即便是MD5這種已經過時的雜湊演算法,也很難實現逆向運算。我們現在更多的還是依賴於海量字典來進行嘗試,也就是通過已經知道的大量的檔案——指紋對應關係,搜尋某個指紋所對應的檔案是否在資料庫裡存在。

1.6 MD5簡介

  • MD5的應用
  1. MD5的典型應用是對一段資訊(Message)產生資訊摘要(Message-Digest),以防止被篡改。比如,在UNIX下有很多軟體在下載的時候都有一個檔名相同,副檔名為.md5的檔案,在這個檔案中通常只有一行文字,大致結構如: MD5 (tanajiya.tar.gz) = 0ca175b9c0f726a831d895e269332461
  2. 這就是tanajiya.tar.gz檔案的數字簽名。MD5將整個檔案當作一個大文字資訊,通過其不可逆的字串變換演算法,產生了這個唯一的MD5資訊摘要。
  3. 大家都知道,地球上任何人都有自己獨一無二的指紋,這常常成為公安機關鑑別罪犯身份最值得信賴的方法;與之類似,MD5就可以為任何檔案(不管其大小、格式、數量)產生一個同樣獨一無二的“數字指紋”,如果任何人對檔名做了任何改動,其MD5值也就是對應的“數字指紋”都會發生變化。
  4. 我們常常在某些軟體下載站點的某軟體資訊中看到其MD5值,它的作用就在於我們可以在下載該軟體後,對下載回來的檔案用專門的軟體(如Windows MD5 Check等)做一次MD5校驗,以確保我們獲得的檔案與該站點提供的檔案為同一檔案。利用MD5演算法來進行檔案校驗的方案被大量應用到軟體下載站、論壇資料庫、系統檔案安全等方面。
  5. MD5的典型應用是對一段Message(位元組串)產生fingerprint(指紋),以防止被“篡改”。舉個例子,你將一段話寫在一個叫 readme.txt檔案中,並對這個readme.txt產生一個MD5的值並記錄在案,然後你可以傳播這個檔案給別人,別人如果修改了檔案中的任何內容,你對這個檔案重新計算MD5時就會發現(兩個MD5值不相同)。如果再有一個第三方的認證機構,用MD5還可以防止檔案作者的“抵賴”,這就是所謂的數字簽名應用。
  6. MD5還廣泛用於作業系統的登陸認證上,如Unix、各類BSD系統登入密碼、數字簽名等諸多方。如在UNIX系統中使用者的密碼是以MD5(或其它類似的演算法)經Hash運算後儲存在檔案系統中。當使用者登入的時候,系統把使用者輸入的密碼進行MD5 Hash運算,然後再去和儲存在檔案系統中的MD5值進行比較,進而確定輸入的密碼是否正確。通過這樣的步驟,系統在並不知道使用者密碼的明碼的情況下就可以確定使用者登入系統的合法性。這可以避免使用者的密碼被具有系統管理員許可權的使用者知道。MD5將任意長度的“位元組串”對映為一個128bit的大整數,並且是通過該128bit反推原始字串是困難的,換句話說就是,即使你看到源程式和演算法描述,也無法將一個MD5的值變換回原始的字串,從數學原理上說,是因為原始的字串有無窮多個,這有點象不存在反函式的數學函式。所以,要遇到了md5密碼的問題,比較好的辦法是:你可以用這個系統中的md5()函式重新設一個密碼,如admin,把生成的一串密碼的Hash值覆蓋原來的Hash值就行了。

1.6.1 MD5 加密

  • MD5線上加密網站
    MD5線上加密網站
  • 由於MD5加密是不可逆的,所以正常無法破解, 但是由於有前面說到的雜湊碰撞存在,所以有了破解的方式。

1.6.2 MD5 破解

  • 有一個很出名的網站https://cmd5.com/ 收錄了很多md5值,可以線上檢視MD5的原始值。

    網站介紹

  • 剛剛md5加密123456得到了:32位大寫的MD5值:E10ADC3949BA59ABBE56E057F20F883E

  • 現在在解密網站上面驗證

    解密網站上面驗證

2. 對稱加密, 非對稱加密, HMAC

2.1 HMAC

  • 上面講解了hash演算法和用途,hash演算法在密碼學上面可以用來加密,如伺服器認證賬號資訊時,根本不需要知道密碼是什麼,只需要驗證密碼的md5值就可以了。
  • 一般使用hash加密的方式主要有:
  1. 直接使用MD5
  2. MD5加鹽
  3. HMAC加密方案
  • HMAC: HMAC不是一種加密演算法,而是一種加密方案。使用一個金鑰加密,並且做了兩次雜湊。在實際開發中,金鑰來自於伺服器。每個賬號匹配一個金鑰。

  • HMAC 加密方案的過程:

1、客戶端填寫一個賬號,發給服務端驗證。服務端返回一個隨機數給客戶端。 2、此隨機數就是HMAC的key。客戶端的密碼使用這個key加密,然後把這個加密之後的hash值發個客戶端。客戶端儲存這個key。 3、服務端儲存客服端傳送過來的hash值。這個hash值只會傳輸一次。在註冊或換手機登入的情況下傳輸。 4、以後的每次登入驗證都是用這個hash值加上服務端時間戳(精確到分),然後再hash一次,得到新的hash傳送給服務端。服務端用它儲存的hash值也加上服務端時間,然後再hash一次,用得到的hash和客戶端傳送過來的hash比對。比對這一分鐘和上一分鐘,只要有一個比對成功,就算成功。 5、有一種情況,客戶端換了手機且開啟了裝置驗證,向服務端要key。伺服器會先向授權裝置發起是否授權,授權通過傳送key,授權不通過不傳送key。

2.2 對稱加密

  • 什麼是對稱加密?

對稱加密(也叫私鑰加密)指加密和解密使用相同金鑰的加密演算法。有時又叫傳統密碼演算法,就是加密金鑰能夠從解密金鑰中推算出來,同時解密金鑰也可以從加密金鑰中推算出來。而在大多數的對稱演算法中,加密金鑰和解密金鑰是相同的,所以也稱這種加密演算法為祕密金鑰演算法或單金鑰演算法。它要求傳送方和接收方在安全通訊之前,商定一個金鑰。對稱演算法的安全性依賴於金鑰,洩漏金鑰就意味著任何人都可以對他們傳送或接收的訊息解密,所以金鑰的保密性對通訊性至關重要。

  • 對稱加密的優點

對稱加密演算法的特點是演算法公開、計算量小、加密速度快、加密效率高

  • 對稱加密演算法的缺點?
  1. 要求提供一條安全的渠道使通訊雙方在首次通訊時協商一個共同的金鑰。直接的面對面協商可能是不現實而且難於實施的,所以雙方可能需要藉助於郵件和電話等其它相對不夠安全的手段來進行協商;
  2. 金鑰的數目難於管理。因為對於每一個合作者都需要使用不同的金鑰,很難適應開放社會中大量的資訊交流;
  3. 對稱加密演算法一般不能提供資訊完整性的鑑別。它無法驗證傳送者和接受者的身份;
  4. 對稱金鑰的管理和分發工作是一件具有潛在危險的和煩瑣的過程。對稱加密是基於共同保守祕密來實現的,採用對稱加密技術的貿易雙方必須保證採用的是相同的金鑰,保證彼此金鑰的交換是安全可靠的,同時還要設定防止金鑰洩密和更改金鑰的程式。

2.2.1 常用的對稱加密演算法

  • 常用對稱加密演算法主要有:DES演算法,3DES演算法,TDEA演算法,Blowfish演算法,RC5演算法,IDEA演算法。
  • 對稱加密演算法原理

對稱加密演算法的優點在於加解密的高速度和使用長金鑰時的難破解性。假設兩個使用者需要使用對稱加密方法加密然後交換資料,則使用者最少需要2個金鑰並交換使用,如果企業內使用者有n個,則整個企業共需要n×(n-1) 個金鑰,金鑰的生成和分發將成為企業資訊部門的惡夢。對稱加密演算法的安全性取決於加密金鑰的儲存情況,但要求企業中每一個持有金鑰的人都保守祕密是不可能的,他們通常會有意無意的把金鑰洩漏出去--如果一個使用者使用的金鑰被入侵者所獲得,入侵者便可以讀取該使用者金鑰加密的所有文件,如果整個企業共用一個加密金鑰,那整個企業文件的保密性便無從談起。

  • DES加密演算法: 對稱加密演算法中最經典的演算法莫過於DES加密演算法。DES加密採用的是分組加密的方法,使用56位金鑰加密64位明文,最後產生64位密文。DES演算法的基本流程如下:
    DES加密演算法
  1. DES對64位的明文分組M進行操作,M經過一個初始置換IP置換成m0,將m0明文分成左半部分和右半部分m0=(L0,R0),各32位長。然後進行16輪完全相同的運算,這些運算稱為函式f,在運算過程中,資料與密匙結合。經過16輪運算之後,可以看到第16輪運算,將右側第15輪運算的結果(R15)作為左側運算的最終結果(L16),而右側最後的結果(R16)為左側第15輪運算結果(L15)和函式f運算結果的異或運算所得。此後,再將左、右部分合在一起經過一個逆置換,輸出密文。
  2. 實際加密過程要分成兩個同時進行的過程,即加密過程和金鑰生成過程:
    加密過程
  3. 在16輪迴圈的每一輪中,密匙位移位,然後再從密匙的64位中選出48位。通過一個擴充套件置換將資料的右半部分擴充套件成48位,並通過一個異或操作替代成新的32位資料,在將其置換一次。這四步運算構成了圖6-2中的函式f。然後,通過另一個異或運算,函式f的輸出與左半部分結合,其結果成為新的右半部分,原來的右半部分成為新的左半部分。該操作重複16次。
  4. DES演算法的解密過程和加密過程幾乎完全相同,只是使用金鑰的順序相反。
  5. NIST(National Institute of Standards and Technology,美國國家標準技術研究院)在1999年釋出了新的DES加密標準,3DES取代DES成為新的加密標準。3DES採用168位的金鑰,三重加密,但速度較慢。之後,又出現了AES(Advanced Encryption Standard,先進加密標準)等高階對稱機密演算法。
  • TripleDES加密演算法
  1. 由於DES演算法安全性方面的原因,為了提高DES演算法的抗攻擊性,因此提出了Triple-DES演算法。
  2. Triple-DES演算法的基本原理是:用兩個金鑰對資料進行3次加密/解密運算。即首先使用第一個金鑰對資料進行加密,然後用第二個金鑰對其進行解密,最後用第一個金鑰再加密。這兩個金鑰可以是同一個,也可以不同,它們也可以來源於一個128位金鑰,只是在加密/解密時將其分割成兩個64位的金鑰,分別輪換使用這兩個64位金鑰去完成加密/解密運算。Triple-DES演算法保留了DES演算法運算速度快的特點,通過增加運算次數和金鑰長度(兩個64位金鑰相當於128位金鑰)來增加破解者的破解時間,但是從密碼學本身來說,其安全強度並沒有增加。
  • RC系列演算法:
  1. 現在我們用到的RC系列演算法包括RC2、RC4、RC5、RC6演算法,其中RC4是序列密碼演算法,其他三種是分組密碼演算法。
  2. RC2演算法: 該演算法設計的目的是用來取代DES演算法,它採用金鑰長度可變的對明文采取64位分組的分組加密演算法,屬於Festel網路結構。
  3. RC4演算法: 該演算法是一個金鑰長度可變的面向位元組流的加密演算法,以隨機置換為基礎。該演算法執行速度快,每輸出1位元組的結果僅需要8~16位元組的機器指令。RC4演算法比較容易描述,它首先用8~2048位可變長度的金鑰初始化一個256位元組的狀態向量S。S的成員標記為S[0],S[1],…,S[255],整個置換過程都包含0~255的8位元數。對於加密和解密,設位元組資料為K,由S中256個元素按一定方式選出一個元素生成,每生成一個K值,元素中的資料就要被重新置換一次。
  • Rijndael演算法:
  1. Rijndael是一個反覆運算的加密演算法,它允許可變動的資料區塊及金鑰的長度。資料區塊與金鑰長度的變動是各自獨立的。
  2. 在Rijndael演算法中定義了兩個名詞: State:在運算過程中所產生的中間值,是一個4×Nb的矩陣,Nb可由資料長度除以32位求得,也就是把資料分割成Nb個區塊。 Cipher Key:用來做加密運算的金鑰,形式是一個4×Nk的矩陣,Nk可由金鑰長度除以32位求得,也就是把金鑰分割成Nk個32位的子金鑰。
  3. 在Rijndael演算法中,運算的回合數(Nr)是由Nb及Nk決定的

2.3 非對稱加密

  • 什麼是非對稱加密?

非對稱加密:指加密和解密使用不同金鑰的加密演算法。非對稱加密演算法需要兩個金鑰:公鑰(publickey)和私鑰(privatekey)。 公鑰與私鑰是一對存在,如果用公鑰對資料進行加密,只有用對應的私鑰才能解密;如果用金鑰對資料進行加密,那麼只有用對應的公鑰才能解密。因為加密和解密使用的是兩個不同的金鑰,所以這種演算法叫作非對稱加密演算法。

  • 非對稱加密的特點:

非對稱密碼體制的特點:演算法強度複雜、安全性依賴於演算法與金鑰但是由於其演算法複雜,而使得加密解密速度沒有對稱加密解密的速度快。對稱密碼體制中只有一種金鑰,並且是非公開的,如果要解密就得讓對方知道金鑰。所以保證其安全性就是保證金鑰的安全,而非對稱金鑰體制有兩種金鑰,其中一個是公開的,這樣就可以不需要像對稱密碼那樣傳輸對方的金鑰了。這樣安全性就大了很多。

  • 非對稱加密演算法是一種金鑰的保密方法。

  • 非對稱加密的典型演算法是RSA加密方式,目前大部分銀行都是使用這種加密方式。更多RSA加密的詳情可以參考我前面的一篇部落格:IOS 逆向開發(一)密碼學 非對稱加密RSA

  • 對稱加密和非對稱加密的對比

  1. 非對稱密碼體制的特點:演算法強度複雜、安全性依賴於演算法與金鑰但是由於其演算法複雜,而使得加密解密速度沒有對稱加密解密的速度快。
  2. 對稱密碼體制中只有一種金鑰,並且是非公開的,如果要解密就得讓對方知道金鑰,所以保證其安全性就是保證金鑰的安全。而非對稱金鑰體制有兩種金鑰,其中一個是公開的,這樣就可以不需要像對稱密碼那樣傳輸對方的金鑰了。這樣安全性就大了很多。
  3. 假設兩個使用者要加密交換資料,雙方交換公鑰,使用時一方用對方的公鑰加密,另一方即可用自己的私鑰解密。
  4. 如果企業中有n個使用者,企業需要生成n對金鑰,並分發n個公鑰。由於公鑰是可以公開的,使用者只要保管好自己的私鑰即可,因此加密金鑰的分發將變得 十分簡單。同時,由於每個使用者的私鑰是唯一的,其他使用者除了可以通過"資訊傳送者的公鑰"來驗證資訊的來源是否真實,還可以確保傳送者無法否認曾傳送過該資訊。非對稱加密的缺點是加解密速度要遠遠慢於對稱加密,在某些極端情況下,甚至能比對稱加密慢上1000倍。
  5. 非對稱的好處顯而易見,非對稱加密體系不要求通訊雙方事先傳遞金鑰或有任何約定就能完成保密通訊,並且金鑰管理方便,可實現防止假冒和抵賴,因此,更適合網路通訊中的保密通訊要求。
  • 總的說來,有以下幾點:
  1. 對稱加密加密與解密使用的是同樣的金鑰,所以速度快,但由於需要將金鑰在網路傳輸,所以安全性不高。
  2. 非對稱加密使用了一對金鑰,公鑰與私鑰,所以安全性高,但加密與解密速度慢。
  3. 常用解決的辦法是將對稱加密的金鑰使用非對稱加密的公鑰進行加密,然後傳送出去,接收方使用私鑰進行解密得到對稱加密的金鑰,然後雙方可以使用對稱加密來進行溝通。這就是在HTTPS中使用的方式。

2.3.1 非對稱加密演算法

  • RSA、Elgamal、揹包演算法、Rabin、HD,ECC(橢圓曲線加密演算法)。
  • 使用最廣泛的是RSA演算法,Elgamal是另一種常用的非對稱加密演算法。
  • Elgamal由Taher Elgamal於1985年發明,其基礎是DiffieˉHellman金鑰交換演算法,後者使通訊雙方能通過公開通訊來推匯出只有他們知道的祕密金鑰值[DiffieˉHellman]。DiffieˉHellman是Whitfield Diffie和Martin Hellman於1976年發明的,被視為第一種 非對稱加密演算法,DiffieˉHellman 與RSA的不同之處在於,DiffieˉHellman不是加密演算法,它只是生成可用作對稱金鑰的祕密數值。在DiffieˉHellman金鑰交換過程中,傳送方和接收方分別生成一個祕密的隨機數,並根據隨機數推匯出公開值,然後,雙方再交換公開值。DiffieˉHellman演算法的基礎是具備生成共享金鑰的能力。只要交換了公開值,雙方就能使用自己的私有數和對方的公開值來生成對稱金鑰,稱為共享金鑰,對雙方來說,該對稱金鑰是相同的,可以用於使用對稱加密演算法加密資料。
  • 與RSA相比,DiffieˉHellman的優勢之一是每次交換金鑰時都使用一組新值,而使用RSA演算法時,如果攻擊者獲得了私鑰,那麼他不僅能解密之前截獲的訊息,還能解密之後的所有訊息。然而,RSA可以通過認證(如使用X.509數字證書)來防止中間人攻擊,但Diff ieˉHellman在應對中間人攻擊時非常脆弱。

2.3.2 完整的非對稱加密過程

  • 假如現在 你向支付寶 轉賬(術語資料資訊),為了保證資訊傳送的保密性、真實性、完整性和不可否認性,需要對傳送的資訊進行數字加密和簽名,其傳送過程為:
  1. 首先你要確認是否是支付寶的數字證書,如果確認為支付寶身份後,則對方真實可信。可以向對方傳送資訊;
  2. 你準備好要傳送的數字資訊(明文)計算要轉的多少錢,對方支付寶賬號等;
  3. 你 對數字資訊進行雜湊運算,得到一個資訊摘要(客戶端主要職責);
  4. 你用自己的私鑰對資訊摘要進行加密得到 你 的數字簽名,並將其附在數字資訊上;
  5. 你隨機產生一個加密金鑰,並用此密碼對要傳送的資訊進行加密(密文);
  6. 你用 支付寶的公鑰對剛才隨機產生的加密金鑰進行加密,將加密後的 DES 金鑰連同密文一起傳送給支付寶;
  7. 支付寶收到 你 傳送來的密文和加密過的 DES 金鑰,先用自己的私鑰對加密的 DES 金鑰進行解密,得到 你隨機產生的加密金鑰;
  8. 支付寶 然後用隨機金鑰對收到的密文進行解密,得到明文的數字資訊,然後將隨機金鑰拋棄;
  9. 支付寶 用你 的公鑰對 你的的數字簽名進行解密,得到資訊摘要;
  10. 支付寶用相同的雜湊演算法對收到的明文再進行一次雜湊運算,得到一個新的資訊摘要;
  11. 支付寶將收到的資訊摘要和新產生的資訊摘要進行比較,如果一致,說明收到的資訊沒有被修改過;
  12. 確定收到資訊,然後進行向對方進行付款交易,一次非對稱密過程結束。

2.3.3 對稱加密的應用模式

  • ECB(Electronic Code Book):電子密碼本模式。每一塊資料,獨立加密。

最基本的加密模式,也就是通常理解的加密,相同的明文將永遠加密成相同的密文,無初始向量,容易受到密碼本重放攻擊,一般情況下很少用。

  • CBC(Cipher Block Chaining):密碼分組連結模式。使用一個金鑰和一個初始化向量[IV]對資料執行加密。

明文被加密前要與前面的密文進行異或運算後再加密,因此只要選擇不同的初始向量,相同的密文加密後會形成不同的密文,這是目前應用最廣泛的模式。CBC加密後的密文是上下文相關的,但明文的錯誤不會傳遞到後續分組,但如果一個分組丟失,後面的分組將全部作廢(同步錯誤)。

2.3.3.1 終端驗證

  • 終端輸入:vi message.txt
  • 通過vi編輯文字資訊如下:
    通過vi編輯文字資訊
  • 按“esc”鍵,然後輸入:wq儲存
  • 然後終端輸入:openssl enc -des-ecb -K 616263 -nosalt -in message.txt -out msg1.bin 將message.txt 通過ECB(Electronic Code Book):電子密碼本模式。每一塊資料,獨立加密。輸出的結果儲存到msg1.bin檔案裡面。
    ECB加密

注意:上述加密命令中616263是加密的key,-nosalt是表示不加鹽。

  • 輸出後,我們可以看到生成了msg1.bin檔案:

    生成了msg1.bin檔案

  • 然後我們稍微修改一下message.txt檔案:

    稍微修改一下message.txt檔案

  • 然後重新輸入: openssl enc -des-ecb -K 616263 -nosalt -in message.txt -out msg2.bin進行加密,生成msg2.bin檔案。

  • 接下來我們檢視一下msg1.bin和msg2.bin有什麼區別

  • 我們使用命令xxd msg1.bin檢視:

    msg1.bin和msg2.bin二進位制資料不同處對比

  • 只修改了一個數字,但是整個塊8個位元組都不一樣了,說明是一塊塊進行加密的。

  • 接下來,我們輸入:openssl enc -des-cbc -iv 0102030405060708 -K 616263 -nosalt -in message.txt -out msg3.bin 得到加密檔案msg3.bin

    得到加密檔案msg3.bin

  • 然後我們再修改一下原始檔案message.txt,將2改回1

    修改一下原始檔案message.txt,將2改回1
    修改後的message.txt資料

  • 接下來,我們輸入:openssl enc -des-cbc -iv 0102030405060708 -K 616263 -nosalt -in message.txt -out msg4.bin 得到加密檔案msg4.bin

    得到加密檔案msg4.bin

  • 然後我們通過xxd msg3.bin,xxd msg4.bin來檢視對比msg3.bin和msg4.bin檔案。

    對比msg3.bin和msg4.bin檔案

  • 由此可以看出,CBC(Cipher Block Chaining):密碼分組連結模式。使用一個金鑰和一個初始化向量[IV]對資料執行加密。加密時是密碼鏈條。

3. 數字簽名

  • 什麼是數字簽名?
  1. 數字簽名:是將摘要資訊用傳送者的私鑰加密,與原文一起傳送給接收者。接收者只有用傳送者的公鑰才能解密被加密的摘要資訊,然後用HASH函式對收到的原文產生一個摘要資訊,與解密的摘要資訊對比。
  2. 如果相同,則說明收到的資訊是完整的,在傳輸過程中沒有被修改; 否則說明資訊被修改過,因此數字簽名能夠驗證資訊的完整性。 如果中途資料被纂改或者丟失。那麼對方就可以根據數字簽名來辨別是否是來自對方的第一手資訊資料。
  3. 數字簽名是個加密的過程,數字簽名驗證是個解密的過程
  4. 數字簽名用來,保證資訊傳輸的完整性、傳送者的身份認證、防止交易中的抵賴發生。
  5. 非對稱加密演算法實現機密資訊交換的基本過程是:甲方生成一對金鑰並將其中的一把作為公用金鑰向其它方公開;得到該公用金鑰的乙方使用該金鑰對機密資訊進行加密後再傳送給甲方;甲方再用自己儲存的另一把專用金鑰對加密後的資訊進行解密。
  • 什麼是數字證書
  1. 數字證書:就是網際網路通訊中標誌通訊各方身份資訊的一串數字,提供了一種在Internet上驗證通訊實體身份的方式,數字證書不是數字身份證,而是身份認證機構蓋在數字身份證上的一個章或印(或者說加在數字身份證上的一個簽名)。
  2. 它是由權威機構——CA機構,又稱為證書授權(Certificate Authority)中心發行的,人們可以在網上用它來識別對方的身份。
  3. 數字證書繫結了公鑰及其持有者的真實身份,它類似於現實生活中的居民身份證,所不同的是數字證書不再是紙質的證照,而是一段含有證書持有者身份資訊並經過認證中心稽核簽發的電子資料,廣泛用在電子商務和移動網際網路中。

3.1 數字前面在HTTPS協議中的應用

  • 瀏覽器中籤名證書認證中的對稱加密和非對稱加密混合應用
  1. 瀏覽器向伺服器發出請求,詢問對方支援的對稱加密演算法和非對稱加密演算法;伺服器回應自己支援的演算法。
  2. 瀏覽器選擇雙方都支援的加密演算法,並請求伺服器出示自己的證書;伺服器回應自己的證書。
  3. 瀏覽器隨機產生一個用於本次會話的對稱加密的鑰匙,並使用伺服器證書中附帶的公鑰對該鑰匙進行加密後傳遞給伺服器;伺服器為本次會話保持該對稱加密的鑰匙。第三方不知道伺服器的私鑰,即使截獲了資料也無法解密。非對稱加密讓任何瀏覽器都可以與伺服器進行加密會話。
  4. 瀏覽器使用對稱加密的鑰匙對請求訊息加密後傳送給伺服器,伺服器使用該對稱加密的鑰匙進行解密;伺服器使用對稱加密的鑰匙對響應訊息加密後傳送給瀏覽器,瀏覽器使用該對稱加密的鑰匙進行解密。第三方不知道對稱加密的鑰匙,即使截獲了資料也無法解密。對稱加密提高了加密速度 。

3.1.1 HTTPS中的SSL流程:

  1. 為了提高安全性,我們常用的做法是使用對稱加密的手段加密資料。可是隻使用對稱加密的話,雙方通訊的開始總會以明文的方式傳輸金鑰。那麼從一開始這個金鑰就洩露了,談不上什麼安全。所以 TLS/SSL 在握手的階段,結合非對稱加密的手段,保證只有通訊雙方才知道對稱加密的金鑰。大概的流程如下:
    HTTPS中的SSL流程
    CA證書驗證過程:
    CA證書驗證過程
    CA證書驗證過程2
  • SSL/TLS 握手過程
    SSL/TLS 握手過程
  • Client Hello: 握手第一步是客戶端向服務端傳送 Client Hello 訊息,這個訊息裡包含了一個客戶端生成的隨機數 Random1、客戶端支援的加密套件(Support Ciphers)和 SSL Version 等資訊。通過 Wireshark 抓包,我們可以看到如下資訊:

Client Hello

  • Server Hello: 第二步是服務端向客戶端傳送 Server Hello 訊息,這個訊息會從 Client Hello 傳過來的 Support Ciphers 裡確定一份加密套件,這個套件決定了後續加密和生成摘要時具體使用哪些演算法,另外還會生成一份隨機數 Random2。注意,至此客戶端和服務端都擁有了兩個隨機數(Random1+ Random2),這兩個隨機數會在後續生成對稱祕鑰時用到。

    Server Hello

  • Certificate: 這一步是服務端將自己的證書下發給客戶端,讓客戶端驗證自己的身份,客戶端驗證通過後取出證書中的公鑰。

    Certificate

  • Server Key Exchange: 如果是DH演算法,這裡傳送伺服器使用的DH引數。RSA演算法不需要這一步。

    Server Key Exchange

  • Certificate Request: Certificate Request 是服務端要求客戶端上報證書,這一步是可選的,對於安全性要求高的場景會用到。

  • Server Hello Done: Server Hello Done 通知客戶端 Server Hello 過程結束。

    Server Hello 過程結束

  • Certificate Verify:客戶端收到服務端傳來的證書後,先從 CA 驗證該證書的合法性,驗證通過後取出證書中的服務端公鑰,再生成一個隨機數 Random3,再用服務端公鑰非對稱加密 Random3 生成 PreMaster Key。

  • Client Key Exchange: 上面客戶端根據伺服器傳來的公鑰生成了 PreMaster Key,Client Key Exchange 就是將這個 key 傳給服務端,服務端再用自己的私鑰解出這個 PreMaster Key 得到客戶端生成的 Random3。至此,客戶端和服務端都擁有 Random1 + Random2 + Random3,兩邊再根據同樣的演算法就可以生成一份祕鑰,握手結束後的應用層資料都是使用這個祕鑰進行對稱加密。為什麼要使用三個隨機數呢?這是因為 SSL/TLS 握手過程的資料都是明文傳輸的,並且多個隨機數種子來生成祕鑰不容易被暴力破解出來。客戶端將 PreMaster Key 傳給服務端的過程如下圖所示:

    Certificate Verify

  • Change Cipher Spec(Client):這一步是客戶端通知服務端後面再傳送的訊息都會使用前面協商出來的祕鑰加密了,是一條事件訊息:

    Change Cipher Spec

  • Encrypted Handshake Message(Client): 這一步對應的是 Client Finish 訊息,客戶端將前面的握手訊息生成摘要再用協商好的祕鑰加密,這是客戶端發出的第一條加密訊息。服務端接收後會用祕鑰解密,能解出來說明前面協商出來的祕鑰是一致的。

    Encrypted Handshake Message(Client)

  • Change Cipher Spec(Server):這一步是服務端通知客戶端後面再傳送的訊息都會使用加密,也是一條事件訊息。

  • Encrypted Handshake Message(Server):這一步對應的是 Server Finish 訊息,服務端也會將握手過程的訊息生成摘要再用祕鑰加密,這是服務端發出的第一條加密訊息。客戶端接收後會用祕鑰解密,能解出來說明協商的祕鑰是一致的。

    Encrypted Handshake Message

  • Application Data: 到這裡,雙方已安全地協商出了同一份祕鑰,所有的應用層資料都會用這個祕鑰加密後再通過 TCP 進行可靠傳輸。

3.1.2 HTTPS中的雙向驗證

  • 雙向驗證流程圖:
    雙向驗證流程圖

4. 對稱加密終端演練

  • 終端測試指令

  • DES(ECB)加密

終端輸入命令

 echo -n hello | openssl enc -des-ecb -K 616263 -nosalt | base64
複製程式碼

加密後結果輸出為:HQr0Oij2kbo=

DES(ECB)加密

  • DES(CBC)加密

終端輸入命令

echo -n hello | openssl enc -des-cbc -iv 0102030405060708 -K 616263 -nosalt | base64
複製程式碼

加密後結果:alvrvb3Gz88=

DES(CBC)加密

  • AES(ECB)加密

終端輸入命令

  echo -n hello | openssl enc -aes-128-ecb -K 616263 -nosalt | base64
複製程式碼

加密後結果:d1QG4T2tivoi0Kiu3NEmZQ==

AES(ECB)加密

  • AES(CBC)加密

終端輸入命令

  echo -n hello | openssl enc -aes-128-cbc -iv 0102030405060708 -K 616263 -nosalt | base64
複製程式碼

加密後結果:u3W/N816uzFpcg6pZ+kbdg==

AES(CBC)加密

  • DES(ECB)解密

終端輸入命令

  echo -n HQr0Oij2kbo= | base64 -D | openssl enc -des-ecb -K 616263 -nosalt -d
複製程式碼

解密後結果:

DES(ECB)解密

  • DES(CBC)解密

終端輸入命令

  echo -n alvrvb3Gz88= | base64 -D | openssl enc -des-cbc -iv 0102030405060708 -K 616263 -nosalt -d
複製程式碼

加密後結果:

DES(CBC)解密

  • AES(ECB)解密

終端輸入命令

  echo -n d1QG4T2tivoi0Kiu3NEmZQ== | base64 -D | openssl enc -aes-128-ecb -K 616263 -nosalt -d
複製程式碼

加密後結果:

AES(ECB)解密

  • AES(CBC)解密

終端輸入命令

 echo -n u3W/N816uzFpcg6pZ+kbdg== | base64 -D | openssl enc -aes-128-cbc -iv 0102030405060708 -K 616263 -nosalt -d
複製程式碼

加密後結果:

AES(CBC)解密

5. 對稱加密實戰

5.1 Hash加密程式碼

  • 本篇部落格涉及Demo程式碼地址:Hash加密演算法demo

  • 新建一個NSString擴充套件類:NSString+Hash

NSString+Hash.h檔案

//
//  NSString+Hash.h
//  001-KEncryDemo
//
//  Created by 孔雨露 on 2019/12/14.
//  Copyright © 2019 Apple. All rights reserved.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface NSString (Hash) 
#pragma mark - 雜湊函式
    /**
     *  計算MD5雜湊結果
     *
     *  終端測試命令:
     *  @code
     *  md5 -s "string"
     *  @endcode
     *
     *  <p>提示:隨著 MD5 碰撞生成器的出現,MD5 演算法不應被用於任何軟體完整性檢查或程式碼簽名的用途。<p>
     *
     *  @return 32個字元的MD5雜湊字串
     */
- (NSString *)md5String;
    
    /**
     *  計算SHA1雜湊結果
     *
     *  終端測試命令:
     *  @code
     *  echo -n "string" | openssl sha -sha1
     *  @endcode
     *
     *  @return 40個字元的SHA1雜湊字串
     */
- (NSString *)sha1String;
    
    /**
     *  計算SHA256雜湊結果
     *
     *  終端測試命令:
     *  @code
     *  echo -n "string" | openssl sha -sha256
     *  @endcode
     *
     *  @return 64個字元的SHA256雜湊字串
     */
- (NSString *)sha256String;
    
    /**
     *  計算SHA 512雜湊結果
     *
     *  終端測試命令:
     *  @code
     *  echo -n "string" | openssl sha -sha512
     *  @endcode
     *
     *  @return 128個字元的SHA 512雜湊字串
     */
- (NSString *)sha512String;
    
#pragma mark - HMAC 雜湊函式
    /**
     *  計算HMAC MD5雜湊結果
     *
     *  終端測試命令:
     *  @code
     *  echo -n "string" | openssl dgst -md5 -hmac "key"
     *  @endcode
     *
     *  @return 32個字元的HMAC MD5雜湊字串
     */
- (NSString *)hmacMD5StringWithKey:(NSString *)key;
    
    /**
     *  計算HMAC SHA1雜湊結果
     *
     *  終端測試命令:
     *  @code
     *  echo -n "string" | openssl sha -sha1 -hmac "key"
     *  @endcode
     *
     *  @return 40個字元的HMAC SHA1雜湊字串
     */
- (NSString *)hmacSHA1StringWithKey:(NSString *)key;
    
    /**
     *  計算HMAC SHA256雜湊結果
     *
     *  終端測試命令:
     *  @code
     *  echo -n "string" | openssl sha -sha256 -hmac "key"
     *  @endcode
     *
     *  @return 64個字元的HMAC SHA256雜湊字串
     */
- (NSString *)hmacSHA256StringWithKey:(NSString *)key;
    
    /**
     *  計算HMAC SHA512雜湊結果
     *
     *  終端測試命令:
     *  @code
     *  echo -n "string" | openssl sha -sha512 -hmac "key"
     *  @endcode
     *
     *  @return 128個字元的HMAC SHA512雜湊字串
     */
- (NSString *)hmacSHA512StringWithKey:(NSString *)key;
    
#pragma mark - 檔案雜湊函式
    
    /**
     *  計算檔案的MD5雜湊結果
     *
     *  終端測試命令:
     *  @code
     *  md5 file.dat
     *  @endcode
     *
     *  @return 32個字元的MD5雜湊字串
     */
- (NSString *)fileMD5Hash;
    
    /**
     *  計算檔案的SHA1雜湊結果
     *
     *  終端測試命令:
     *  @code
     *  openssl sha -sha1 file.dat
     *  @endcode
     *
     *  @return 40個字元的SHA1雜湊字串
     */
- (NSString *)fileSHA1Hash;
    
    /**
     *  計算檔案的SHA256雜湊結果
     *
     *  終端測試命令:
     *  @code
     *  openssl sha -sha256 file.dat
     *  @endcode
     *
     *  @return 64個字元的SHA256雜湊字串
     */
- (NSString *)fileSHA256Hash;
    
    /**
     *  計算檔案的SHA512雜湊結果
     *
     *  終端測試命令:
     *  @code
     *  openssl sha -sha512 file.dat
     *  @endcode
     *
     *  @return 128個字元的SHA512雜湊字串
     */
- (NSString *)fileSHA512Hash;
@end

NS_ASSUME_NONNULL_END

複製程式碼

NSString+Hash.m檔案

//
//  NSString+Hash.m
//  001-KEncryDemo
//
//  Created by 孔雨露 on 2019/12/14.
//  Copyright © 2019 Apple. All rights reserved.
//

#import "NSString+Hash.h"
#import <CommonCrypto/CommonCrypto.h>

@implementation NSString (Hash)
#pragma mark - 雜湊函式
- (NSString *)md5String {
    const char *str = self.UTF8String;
    uint8_t buffer[CC_MD5_DIGEST_LENGTH];
    
    CC_MD5(str, (CC_LONG)strlen(str), buffer);
    
    return [self stringFromBytes:buffer length:CC_MD5_DIGEST_LENGTH];
}
    
- (NSString *)sha1String {
    const char *str = self.UTF8String;
    uint8_t buffer[CC_SHA1_DIGEST_LENGTH];
    
    CC_SHA1(str, (CC_LONG)strlen(str), buffer);
    
    return [self stringFromBytes:buffer length:CC_SHA1_DIGEST_LENGTH];
}
    
- (NSString *)sha256String {
    const char *str = self.UTF8String;
    uint8_t buffer[CC_SHA256_DIGEST_LENGTH];
    
    CC_SHA256(str, (CC_LONG)strlen(str), buffer);
    
    return [self stringFromBytes:buffer length:CC_SHA256_DIGEST_LENGTH];
}
    
- (NSString *)sha512String {
    const char *str = self.UTF8String;
    uint8_t buffer[CC_SHA512_DIGEST_LENGTH];
    
    CC_SHA512(str, (CC_LONG)strlen(str), buffer);
    
    return [self stringFromBytes:buffer length:CC_SHA512_DIGEST_LENGTH];
}
    
#pragma mark - HMAC 雜湊函式
- (NSString *)hmacMD5StringWithKey:(NSString *)key {
    const char *keyData = key.UTF8String;
    const char *strData = self.UTF8String;
    uint8_t buffer[CC_MD5_DIGEST_LENGTH];
    
    CCHmac(kCCHmacAlgMD5, keyData, strlen(keyData), strData, strlen(strData), buffer);
    
    return [self stringFromBytes:buffer length:CC_MD5_DIGEST_LENGTH];
}
    
- (NSString *)hmacSHA1StringWithKey:(NSString *)key {
    const char *keyData = key.UTF8String;
    const char *strData = self.UTF8String;
    uint8_t buffer[CC_SHA1_DIGEST_LENGTH];
    
    CCHmac(kCCHmacAlgSHA1, keyData, strlen(keyData), strData, strlen(strData), buffer);
    
    return [self stringFromBytes:buffer length:CC_SHA1_DIGEST_LENGTH];
}
    
- (NSString *)hmacSHA256StringWithKey:(NSString *)key {
    const char *keyData = key.UTF8String;
    const char *strData = self.UTF8String;
    uint8_t buffer[CC_SHA256_DIGEST_LENGTH];
    
    CCHmac(kCCHmacAlgSHA256, keyData, strlen(keyData), strData, strlen(strData), buffer);
    
    return [self stringFromBytes:buffer length:CC_SHA256_DIGEST_LENGTH];
}
    
- (NSString *)hmacSHA512StringWithKey:(NSString *)key {
    const char *keyData = key.UTF8String;
    const char *strData = self.UTF8String;
    uint8_t buffer[CC_SHA512_DIGEST_LENGTH];
    
    CCHmac(kCCHmacAlgSHA512, keyData, strlen(keyData), strData, strlen(strData), buffer);
    
    return [self stringFromBytes:buffer length:CC_SHA512_DIGEST_LENGTH];
}
    
#pragma mark - 檔案雜湊函式
    
#define FileHashDefaultChunkSizeForReadingData 4096
    
- (NSString *)fileMD5Hash {
    NSFileHandle *fp = [NSFileHandle fileHandleForReadingAtPath:self];
    if (fp == nil) {
        return nil;
    }
    
    CC_MD5_CTX hashCtx;
    CC_MD5_Init(&hashCtx);
    
    while (YES) {
        @autoreleasepool {
            NSData *data = [fp readDataOfLength:FileHashDefaultChunkSizeForReadingData];
            
            CC_MD5_Update(&hashCtx, data.bytes, (CC_LONG)data.length);
            
            if (data.length == 0) {
                break;
            }
        }
    }
    [fp closeFile];
    
    uint8_t buffer[CC_MD5_DIGEST_LENGTH];
    CC_MD5_Final(buffer, &hashCtx);
    
    return [self stringFromBytes:buffer length:CC_MD5_DIGEST_LENGTH];
}
    
- (NSString *)fileSHA1Hash {
    NSFileHandle *fp = [NSFileHandle fileHandleForReadingAtPath:self];
    if (fp == nil) {
        return nil;
    }
    
    CC_SHA1_CTX hashCtx;
    CC_SHA1_Init(&hashCtx);
    
    while (YES) {
        @autoreleasepool {
            NSData *data = [fp readDataOfLength:FileHashDefaultChunkSizeForReadingData];
            
            CC_SHA1_Update(&hashCtx, data.bytes, (CC_LONG)data.length);
            
            if (data.length == 0) {
                break;
            }
        }
    }
    [fp closeFile];
    
    uint8_t buffer[CC_SHA1_DIGEST_LENGTH];
    CC_SHA1_Final(buffer, &hashCtx);
    
    return [self stringFromBytes:buffer length:CC_SHA1_DIGEST_LENGTH];
}
    
- (NSString *)fileSHA256Hash {
    NSFileHandle *fp = [NSFileHandle fileHandleForReadingAtPath:self];
    if (fp == nil) {
        return nil;
    }
    
    CC_SHA256_CTX hashCtx;
    CC_SHA256_Init(&hashCtx);
    
    while (YES) {
        @autoreleasepool {
            NSData *data = [fp readDataOfLength:FileHashDefaultChunkSizeForReadingData];
            
            CC_SHA256_Update(&hashCtx, data.bytes, (CC_LONG)data.length);
            
            if (data.length == 0) {
                break;
            }
        }
    }
    [fp closeFile];
    
    uint8_t buffer[CC_SHA256_DIGEST_LENGTH];
    CC_SHA256_Final(buffer, &hashCtx);
    
    return [self stringFromBytes:buffer length:CC_SHA256_DIGEST_LENGTH];
}
    
- (NSString *)fileSHA512Hash {
    NSFileHandle *fp = [NSFileHandle fileHandleForReadingAtPath:self];
    if (fp == nil) {
        return nil;
    }
    
    CC_SHA512_CTX hashCtx;
    CC_SHA512_Init(&hashCtx);
    
    while (YES) {
        @autoreleasepool {
            NSData *data = [fp readDataOfLength:FileHashDefaultChunkSizeForReadingData];
            
            CC_SHA512_Update(&hashCtx, data.bytes, (CC_LONG)data.length);
            
            if (data.length == 0) {
                break;
            }
        }
    }
    [fp closeFile];
    
    uint8_t buffer[CC_SHA512_DIGEST_LENGTH];
    CC_SHA512_Final(buffer, &hashCtx);
    
    return [self stringFromBytes:buffer length:CC_SHA512_DIGEST_LENGTH];
}
    
#pragma mark - 助手方法
    /**
     *  返回二進位制 Bytes 流的字串表示形式
     *
     *  @param bytes  二進位制 Bytes 陣列
     *  @param length 陣列長度
     *
     *  @return 字串表示形式
     */
- (NSString *)stringFromBytes:(uint8_t *)bytes length:(int)length {
    NSMutableString *strM = [NSMutableString string];
    
    for (int i = 0; i < length; i++) {
        [strM appendFormat:@"%02x", bytes[I]];
    }
    
    return [strM copy];
}

@end

複製程式碼
  • 新建一個加密工具類:
    新建一個加密工具類
    KEncryptionTool.h檔案:
//
//  KEncryptionTool.h
//  001-KylAppEncrypt
//
//  Created by 孔雨露 on 2019/12/14.
//  Copyright © 2019 Apple. All rights reserved.
//
/**
*  終端測試指令
*
*  DES(ECB)加密
*  $ echo -n hello | openssl enc -des-ecb -K 616263 -nosalt | base64
*
* DES(CBC)加密
*  $ echo -n hello | openssl enc -des-cbc -iv 0102030405060708 -K 616263 -nosalt | base64
*
*  AES(ECB)加密
*  $ echo -n hello | openssl enc -aes-128-ecb -K 616263 -nosalt | base64
*
*  AES(CBC)加密
*  $ echo -n hello | openssl enc -aes-128-cbc -iv 0102030405060708 -K 616263 -nosalt | base64
*
*  DES(ECB)解密
*  $ echo -n HQr0Oij2kbo= | base64 -D | openssl enc -des-ecb -K 616263 -nosalt -d
*
*  DES(CBC)解密
*  $ echo -n alvrvb3Gz88= | base64 -D | openssl enc -des-cbc -iv 0102030405060708 -K 616263 -nosalt -d
*
*  AES(ECB)解密
*  $ echo -n d1QG4T2tivoi0Kiu3NEmZQ== | base64 -D | openssl enc -aes-128-ecb -K 616263 -nosalt -d
*
*  AES(CBC)解密
*  $ echo -n u3W/N816uzFpcg6pZ+kbdg== | base64 -D | openssl enc -aes-128-cbc -iv 0102030405060708 -K 616263 -nosalt -d
*
*  提示:
*      1> 加密過程是先加密,再base64編碼
*      2> 解密過程是先base64解碼,再解密
*/


#import <Foundation/Foundation.h>


NS_ASSUME_NONNULL_BEGIN

@interface KEncryptionTool : NSObject

    + (instancetype)shared;
    
    /**
     @constant   kCCAlgorithmAES     高階加密標準,128位(預設)
     @constant   kCCAlgorithmDES     資料加密標準
     */
    @property (nonatomic, assign) uint32_t algorithm;
    
    /**
     *  加密字串並返回base64編碼字串
     *
     *  @param string    要加密的字串
     *  @param keyString 加密金鑰
     *  @param iv        初始化向量(8個位元組)
     *
     *  @return 返回加密後的base64編碼字串
     */

- (NSString *)encryptString:(NSString *)string keyString:(NSString *)keyString iv:(NSData *)iv;
    
    /**
     *  解密字串
     *
     *  @param string    加密並base64編碼後的字串
     *  @param keyString 解密金鑰
     *  @param iv        初始化向量(8個位元組)
     *
     *  @return 返回解密後的字串
     */
- (NSString *)decryptString:(NSString *)string keyString:(NSString *)keyString iv:(NSData *)iv;
@end

NS_ASSUME_NONNULL_END

複製程式碼

KEncryptionTool.m檔案:

//
//  KEncryptionTool.m
//  001-KylAppEncrypt
//
//  Created by 孔雨露 on 2019/12/14.
//  Copyright © 2019 Apple. All rights reserved.
//



#import "KEncryptionTool.h"
#import <CommonCrypto/CommonCrypto.h>
@interface KEncryptionTool()
@property (nonatomic, assign) int keySize;
@property (nonatomic, assign) int blockSize;

@end


@implementation KEncryptionTool
+ (instancetype)shared {
    static KEncryptionTool *instance;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
        instance.algorithm = kCCAlgorithmAES;
    });
    
    return instance;
}
    
- (void)setAlgorithm:(uint32_t)algorithm {
    _algorithm = algorithm;
    switch (algorithm) {
        case kCCAlgorithmAES:
        self.keySize = kCCKeySizeAES128;
        self.blockSize = kCCBlockSizeAES128;
        break;
        case kCCAlgorithmDES:
        self.keySize = kCCKeySizeDES;
        self.blockSize = kCCBlockSizeDES;
        break;
        default:
        break;
    }
}
    
- (NSString *)encryptString:(NSString *)string keyString:(NSString *)keyString iv:(NSData *)iv {
    
    // 設定祕鑰
    NSData *keyData = [keyString dataUsingEncoding:NSUTF8StringEncoding];
    uint8_t cKey[self.keySize];
    bzero(cKey, sizeof(cKey));
    [keyData getBytes:cKey length:self.keySize];
    
    // 設定iv
    uint8_t cIv[self.blockSize];
    bzero(cIv, self.blockSize);
    int option = 0;
    if (iv) {
        [iv getBytes:cIv length:self.blockSize];
        option = kCCOptionPKCS7Padding;
    } else {
        option = kCCOptionPKCS7Padding | kCCOptionECBMode;
    }
    
    // 設定輸出緩衝區
    NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
    size_t bufferSize = [data length] + self.blockSize;
    void *buffer = malloc(bufferSize);
    
    // 開始加密
    size_t encryptedSize = 0;
    //加密解密都是它 -- CCCrypt
    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
                                          self.algorithm,
                                          option,
                                          cKey,
                                          self.keySize,
                                          cIv,
                                          [data bytes],
                                          [data length],
                                          buffer,
                                          bufferSize,
                                          &encryptedSize);
    
    NSData *result = nil;
    if (cryptStatus == kCCSuccess) {
        result = [NSData dataWithBytesNoCopy:buffer length:encryptedSize];
    } else {
        free(buffer);
        NSLog(@"[錯誤] 加密失敗|狀態編碼: %d", cryptStatus);
    }
    
    return [result base64EncodedStringWithOptions:0];
}
    
- (NSString *)decryptString:(NSString *)string keyString:(NSString *)keyString iv:(NSData *)iv {
    
    // 設定祕鑰
    NSData *keyData = [keyString dataUsingEncoding:NSUTF8StringEncoding];
    uint8_t cKey[self.keySize];
    bzero(cKey, sizeof(cKey));
    [keyData getBytes:cKey length:self.keySize];
    
    // 設定iv
    uint8_t cIv[self.blockSize];
    bzero(cIv, self.blockSize);
    int option = 0;
    if (iv) {
        [iv getBytes:cIv length:self.blockSize];
        option = kCCOptionPKCS7Padding;//CBC 加密!
    } else {
        option = kCCOptionPKCS7Padding | kCCOptionECBMode;//ECB加密!
    }
    
    // 設定輸出緩衝區
    NSData *data = [[NSData alloc] initWithBase64EncodedString:string options:0];
    size_t bufferSize = [data length] + self.blockSize;
    void *buffer = malloc(bufferSize);
    
    // 開始解密
    size_t decryptedSize = 0;
    /**CCCrypt 對稱加密演算法的核心函式(加密/解密)
     引數:
     1、kCCEncrypt 加密/kCCDecrypt 解密
     2、加密演算法、預設的 AES/DES
     3、加密方式的選項
        kCCOptionPKCS7Padding | kCCOptionECBMode;//ECB加密!
        kCCOptionPKCS7Padding;//CBC 加密!
     4、加密金鑰
     5、金鑰長度
     6、iv 初始化向量,ECB 不需要指定
     7、加密的資料
     8、加密的資料長度
     9、緩衝區(地址),存放密文的
     10、緩衝區的大小
     11、加密結果大小
     */
    CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
                                          self.algorithm,
                                          option,
                                          cKey,
                                          self.keySize,
                                          cIv,
                                          [data bytes],
                                          [data length],
                                          buffer,
                                          bufferSize,
                                          &decryptedSize);
    
    NSData *result = nil;
    if (cryptStatus == kCCSuccess) {
        result = [NSData dataWithBytesNoCopy:buffer length:decryptedSize];
    } else {
        free(buffer);
        NSLog(@"[錯誤] 解密失敗|狀態編碼: %d", cryptStatus);
    }
    
    return [[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding];
}
    
@end

複製程式碼
  • 測試程式碼:
- (void) testEncrpytion {
    /** AES - ECB */
    NSString * key = @"abc";
    NSString * encStr = [[KEncryptionTool shared] encryptString:@"hello" keyString:key iv:nil];
    
    NSLog(@"加密的結果是:%@",encStr);
    
    NSLog(@"解密的結果是:%@",[[KEncryptionTool shared] decryptString:encStr keyString:key iv:nil]);
    
    /** AES - CBC 加密 */
    uint8_t iv[8] = {1,2,3,4,5,6,7,8};
    NSData * ivData = [NSData dataWithBytes:iv length:sizeof(iv)];
    
    
    
    NSLog(@"CBC加密:%@",[[KEncryptionTool shared] encryptString:@"hello" keyString:@"abc" iv:ivData]);
    
    NSLog(@"解密:%@",[[KEncryptionTool shared] decryptString:@"u3W/N816uzFpcg6pZ+kbdg==" keyString:key iv:ivData]);
}
複製程式碼
  • 測試結果:
    加密解密測試輸出

參考:www.jianshu.com/p/a3624d4e2…

相關文章