深入淺出AES算法
AES算法
高級加密標準(Advanced Encryption Standard,AES)是美帝2001年發布的塊加密算法,AES是屬于塊加密算法框架中的一個組件,所以理解AES的關鍵是搞清楚塊加密算法。(塊加密算法是一個“框”,AES只是“框”中的一個東西)
塊加密算法工作原理
塊加密算法也叫分組密碼工作模式(block mode of operation)它會把明文按等長的塊(Block)分組然后利用某種加密算法進行加密——AES就屬于“某種加密算法”中的一種。用一幅圖表示二者關系:
塊加密算法有三個關鍵部分
- 填充方式,負責把明文切分成一塊一塊的。塊加密要求數據一定要符合塊大小,以AES為例它規定每個數據塊的大小是128個bit位(16字節),如果數據不足16字節那么必須**填充**到16字節。填充的數據是字節長度,比如一個5字節的塊需要填充到16個字節,那么剩下的9個字節就全部寫上09 09 09……。 PKCS5Padding和PKCS7Padding是分別出在不同規范的兩個標準,PKCS5規定了塊大小是8字節;PKCS7沒有限制。由于AES已經限制了塊的大小,所以它們兩個在AES里面其實沒有什么區別。所以在Java里面只提供了AES+PKC5Padding(AES+PKCS7Padding,這個說法不對,AES已經限制了塊大小)。
- 加密算法,負責對每一塊的明文進行加密。對算法輸入明文和密鑰,算法輸出加密后的密文塊,常見的算法是AES、DES。
- 工作方式,塊加密非常靈活利用不同的工作模式可以實現**并行、密文可變(每次加密得到的密文都不一樣)、容錯**
塊加密算法的五種工作方式
按照塊加密算法的不同工作模式常見的有5種(為了簡化問題我只貼加密過程):
Key是密鑰,Plaintext是明文,中間的Block Cipher Encryption是加密算法(比如AES就是其中一種)。密鑰和明文作為輸入經過加密之后得到密文——Ciphertext。
ECB工作模式非常簡單,可以并行處理;一個線程負責把數據切分成N塊后由N個線程同時進行加密。它的缺點是同樣的密鑰每次執行加密出來的數據都是相同的。正常人看——比如我,這太正常了,但是“密碼專家”們認為這太弱雞了(囧)。所以他們設計了一種特別的算法,通過一個叫“初始向量(IV,Initialization Vector)”的變量讓每次進行加密得到的密文都不一樣(即便密鑰相同)。剩下的4種塊工作模式都屬于這種牛B的類型。
這里的輸入多了一個叫Initialization Vector(IV)的變量;明文和IV異或之后通過作為加密算法的一個變量輸入,密鑰作為另一個變量輸入。
CBC加密算法是一個串行算法,第二塊的加密依賴于***塊密文作為IV。所以計算它的時候只能按部就班一塊一塊的計算。
之前的塊加密都沒有解決容錯問題——如果我一個數據塊壞掉了那么能不能解密出其余的數據塊。于是就有了CFB工作模式,注意觀察上圖,解密的時候如果***個密文塊損壞那么可以無視這塊內容,直接用第二個密文作為輸入對第三個密文塊進行解密。
CBC雖然狂屌炸(每次都能算出不同的密文)但是不能并行,對于“時間就是金錢”的計算機來說是無法容忍的。于是就有了***次改進——OFB。
注意IV和密鑰經過加密后這里是可以并行的,其中一個線程用于和明文的異或;一個線程可以立馬計算“下次”加密。
OFB算法的并行度太低,僅僅實現一部分并行,于是就有了第二次改進——CTR算法,同時保證了并行度和密文可變性。
CTR算法中的IV變成了兩部分,***個是Nonce可以是一個隨機序列,第二部分是計數器(Countter),是一個遞增的數字。于是加密的時候通過組合Nonce和計算器就可以對得到有規律但是不相同的(每次密文都不同的關鍵是IV的可變)“IV”。
總結
從三個維度理解五種工作模式——密文是否固定、是否可以并行、有沒有容錯
- ECB密文固定,全并行
- CBC密文可變,不可并行
- CFB密文容錯,密文可變,不可并行
- OFB密文可變,部分并行
- CTR密文可變,全并行
需要注意的是除非數據量特別大否則我們不必在乎是否并行;容錯在小數據量的時候也凸顯不出效果,所以CBC一般是***的選擇。
如何跨語言
很多朋友都碰到一個語言寫的AES加密在另個一語言解密不了的問題,究其原因是由于根本沒有理解AES的工作模式(可能就是Google了一下AES加密,然后代碼貼上收工)。所以我覺得跨語言的***步不是找到一種能在所有語言通用的工作模式——所有工作模式每個語言幾乎都支持;而是搞清楚你加密出來的數據是那種工作模式,有沒有用到IV?IV是通過什么方式傳遞給對方的?
比如下面的Java代碼:
我使用了CBC加密模式,這種模式涉及到IV,我們可以用一個固定的IV(比如用key作為IV)——但是意味著沒有了CBC的好處,密文可變。所以我用一個隨機16字節作為IV,返回的時候把它作為***個數據塊;解密的時候只要取出***個數據塊作為IV,然后再對余下的數據進行解密。
如果我們不指定IV參數(init函數的第三個參數),那么iv就是一個隨機數。Java是不會主動把IV附加到密文上,所以這種加密出來的數據是誰也沒有辦法解出來的。
【本文是51CTO專欄作者“邢森”的原創文章,轉載請聯系作者本人獲取授權】