Blowfish是由Bruce Schneier在1993年發明的對稱密鑰分組加密算法,相似的DES和AES都是分組加密算法,Blowfish是用來替代DES算法出現的,而且Blowfish是沒有商用限制的,任何人均可以自由使用。java
對比而言,雖然AES也是一種密碼強度很高的對稱密碼算法,可是若是須要商用的話要向NIST支付受權費用。算法
blowfish和DES同樣,使用的是feistel密碼來進行分組加密。blowfish的分組塊大小是64bits,可變密鑰長度能夠從32bits到448bits不等。數組
blowfish須要進行16輪的feistel加密操做,咱們先從下圖大體感覺一下blowfish算法的加密流程:安全
大概的流程就是將P(原始數據)分紅左右兩部分,先拿左邊的部分和Kr 作異或操做,得出的結果調用F函數,最後將F函數的輸出結果和右半部分進行異或操做。函數
調換左右部分的位置,繼續進行這樣的操做,總共進行16輪就獲得了最終的加密結果。ui
你們能夠看到整個加密過程當中最重要的兩個變量就是Kr 和 F函數。加密
接下來咱們會詳細進行講解。spa
從圖上咱們能夠看到,Kr 的範圍是從K1 到 K18 。總共有18個密鑰組成的數組。 每一個密鑰的長度是32位。code
咱們來看一下密鑰數組是怎麼生成的。繼承
首先咱們使用隨機數來對密鑰數組進行初始化。怎麼才能生成一個足夠隨機的32位數字呢?
一個很經常使用的方法就是使用常量π的小數部分,將其轉換成爲16淨值,以下所示:
K1 = 0x76a301d3
K2 = 0xbc452aef
...
K18 = 0xd7acc4a5
還記得blowfish的可變密鑰的長度嗎?是從32bits到448bits,也就是從1到14個32位的數字。咱們用Pi來表示,那麼就是從P1到P14總共14個可變密鑰。
接下來咱們須要使用K和P進行操做,從而生成最終的K數組。
咱們使用K1和P1進行異或操做,K2和P2進行異或操做,一直到K14和P14。
由於P只有14個值,而K有18個值,因此接下來咱們須要重複使用P的值,也就是K15和P1進行異或,K16和P2進行異或,直到K18和P4。
將異或以後的值做爲新的K數組的值。
如今咱們得到了一個新的K數組。
注意,這個K數組並非最終的數組,咱們接下來看。
在生成最終的P數組以前,咱們還要介紹一個概念叫作S-box。
在密碼學中,s-box的全稱是substitution-box,也就是一個替換盒子,能夠將輸入替換成不一樣的輸出。
S-box 接收 n個bits的輸入,而後將其轉換成m個bits的輸出。
這裏n和m能夠是不等的。
咱們看一下DES中S-box的例子:
上面的S-box將6-bits的輸入轉換成爲4-bits的輸出。
S-box能夠是固定的,也能夠是動態的。好比,在DES中S-box就是靜態的,而在Blowfish和Twofish中S-box就是動態生成的。
Blowfish算法中的F函數須要用到4個S-box,F函數的輸入是32bits,首先將32bits分紅4份,也就是4個8bits。
S-box的做用就是將8bits轉換成爲32bits。
咱們再詳細看一下F函數的工做流程:
S-box生成的值會進行相加,而後進行異或操做。最終獲得最終的32bits。
S-box的初始值也能夠跟K數組同樣,使用常量π的小數部分來初始化。
在上面兩節,咱們生成了初始化的K數組和S-box。
blowfish認爲這樣還不夠安全,不夠隨機。
咱們還須要進行一些操做來生成最終的K數組。
首先咱們取一個全爲0的64bits,而後K數組和S-box,應用blowfish算法,生成一個64bits。
將這個64bits分紅兩部分,分別做爲新的K1 和 K2。
而後將這個64bits做爲輸入,再次調用blowfish算法,做爲新的K3 和 K4。
依次類推,最終生成全部K數組中的元素。
4個S-box的數組也按照上面的流程來進行生成。從而獲得最終的S-box。
有了最終的K數組和S-box,咱們就能夠真正的對要加密的文件進行加密操做了。
用個僞代碼來表示整個流程:
uint32_t P[18]; uint32_t S[4][256]; uint32_t f (uint32_t x) { uint32_t h = S[0][x >> 24] + S[1][x >> 16 & 0xff]; return ( h ^ S[2][x >> 8 & 0xff] ) + S[3][x & 0xff]; } void encrypt (uint32_t & L, uint32_t & R) { for (int i=0 ; i<16 ; i += 2) { L ^= P[i]; R ^= f(L); R ^= P[i+1]; L ^= f(R); } L ^= P[16]; R ^= P[17]; swap (L, R); } void decrypt (uint32_t & L, uint32_t & R) { for (int i=16 ; i > 0 ; i -= 2) { L ^= P[i+1]; R ^= f(L); R ^= P[i]; L ^= f(R); } L ^= P[1]; R ^= P[0]; swap (L, R); } // ... // initializing the P-array and S-boxes with values derived from pi; omitted in the example // ... { for (int i=0 ; i<18 ; ++i) P[i] ^= key[i % keylen]; uint32_t L = 0, R = 0; for (int i=0 ; i<18 ; i+=2) { encrypt (L, R); P[i] = L; P[i+1] = R; } for (int i=0 ; i<4 ; ++i) for (int j=0 ; j<256; j+=2) { encrypt (L, R); S[i][j] = L; S[i][j+1] = R; } }
從上面的流程能夠看出,blowfish在生成最終的K數組和S-box須要耗費必定的時間,可是一旦生成完畢,或者說密鑰不變的狀況下,blowfish仍是很快速的一種分組加密方法。
每一個新的密鑰都須要進行大概4 KB文本的預處理,和其餘分組密碼算法相比,這個會很慢。
那麼慢有沒有好處呢?
固然有,由於對於一個正常應用來講,是不會常常更換密鑰的。因此預處理只會生成一次。在後面使用的時候就會很快了。
而對於惡意攻擊者來講,每次嘗試新的密鑰都須要進行漫長的預處理,因此對攻擊者來講要破解blowfish算法是很是不划算的。因此blowfish是能夠抵禦字典攻擊的。
由於blowfish沒有任何專利限制,任何人均可以避免費使用。這種好處促進了它在密碼軟件中的普及。
好比使用blowfish的bcrypt算法,咱們會在後面的文章中進行講解。
Blowfish使用64位塊大小(與AES的128位塊大小相比)使它容易受到生日攻擊,特別是在HTTPS這樣的環境中。 2016年,SWEET32攻擊演示瞭如何利用生日攻擊對64位塊大小的密碼執行純文本恢復(即解密密文)。
由於blowfish的塊只有64bits,比較小,因此GnuPG項目建議不要使用Blowfish來加密大於4 GB的文件。
除此以外,Blowfish若是隻進行一輪加密的話,容易受到反射性弱鍵的已知明文攻擊。 可是咱們的實現中使用的是16輪加密,因此不容易受到這種攻擊。可是Blowfish的發明人布魯斯·施耐爾(Bruce Schneier)仍是建議你們遷移到Blowfish的繼承者Twofish去。
本文已收錄於 http://www.flydean.com/blowfish/
最通俗的解讀,最深入的乾貨,最簡潔的教程,衆多你不知道的小技巧等你來發現!
歡迎關注個人公衆號:「程序那些事」,懂技術,更懂你!