簡介
Blowfish是由Bruce Schneier在1993年發明的對稱金鑰分組加密演算法,類似的DES和AES都是分組加密演算法,Blowfish是用來替代DES演算法出現的,並且Blowfish是沒有商用限制的,任何人都可以自由使用。
對比而言,雖然AES也是一種密碼強度很高的對稱密碼演算法,但是如果需要商用的話要向NIST支付授權費用。
blowfish詳解
blowfish和DES一樣,使用的是feistel密碼來進行分組加密。blowfish的分組塊大小是64bits,可變金鑰長度可以從32bits到448bits不等。
blowfish需要進行16輪的feistel加密操作,我們先從下圖大致感受一下blowfish演算法的加密流程:
大概的流程就是將P(原始資料)分成左右兩部分,先拿左邊的部分和Kr 做異或操作,得出的結果呼叫F函式,最後將F函式的輸出結果和右半部分進行異或操作。
調換左右部分的位置,繼續進行這樣的操作,總共進行16輪就得到了最終的加密結果。
大家可以看到整個加密過程中最重要的兩個變數就是Kr 和 F函式。
接下來我們會詳細進行講解。
金鑰陣列和S-box
金鑰陣列
從圖上我們可以看到,Kr 的範圍是從K1 到 K18 。總共有18個金鑰組成的陣列。 每個金鑰的長度是32位。
我們來看一下金鑰陣列是怎麼生成的。
首先我們使用隨機數來對金鑰陣列進行初始化。怎麼才能生成一個足夠隨機的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陣列並不是最終的陣列,我們接下來看。
S-box
在生成最終的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陣列
在上面兩節,我們生成了初始化的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。
blowfish
有了最終的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的應用
從上面的流程可以看出,blowfish在生成最終的K陣列和S-box需要耗費一定的時間,但是一旦生成完畢,或者說金鑰不變的情況下,blowfish還是很快速的一種分組加密方法。
每個新的金鑰都需要進行大概4 KB文字的預處理,和其他分組密碼演算法相比,這個會很慢。
那麼慢有沒有好處呢?
當然有,因為對於一個正常應用來說,是不會經常更換金鑰的。所以預處理只會生成一次。在後面使用的時候就會很快了。
而對於惡意攻擊者來說,每次嘗試新的金鑰都需要進行漫長的預處理,所以對攻擊者來說要破解blowfish演算法是非常不划算的。所以blowfish是可以抵禦字典攻擊的。
因為blowfish沒有任何專利限制,任何人都可以免費使用。這種好處促進了它在密碼軟體中的普及。
比如使用blowfish的bcrypt演算法,我們會在後面的文章中進行講解。
blowfish的缺點
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/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!