最開始只是想整理一下密碼學課程的做業,後面越寫越多,就索性寫成一篇入門的介紹。我會把本身對對稱加密的理解和一些做業的代碼串起來,力圖清晰明白地展現出來,文中全部代碼都放在個人Github上,若是有錯誤之處還請輕拍。php
加密是爲了防止要傳達的內容被別人知道。例如,你若是想在課堂上傳小紙條給後位小紅說:i love coding
,但又怕在遞紙條的過程當中被老師看到,知道了你的心思,因而將每一個字母變字母表中的後一個字母(如a變成b,i變成j,z變成a),獲得密文:j mpwf dpejoh
,這樣即老師人拿到這紙條,也不知道你說的是什麼。git
這就是一個加密的過程,把本來的內容稱爲明文,通常用p表示;加密後獲得的內容稱爲密文,通常用c表示;而加密的這個過程能夠看作是一個加密函數E,即github
$$c = E( p )$$算法
E是指Encrypt,函數輸入是明文,輸出是加密以後的密文。上面的例子中i love coding
即是明文,j mpwf dpejoh
即是密文,而把字母在字母表中向後移動一位的操做就是加密函數。安全
在小紅獲得小紙條後,能夠根據你加密的方法,將每一個字母變成字母表中的前一個字母,就能夠從你的密文小紙條獲得你要說的內容i love coding
,心照不宣,順便還會懷疑一下你的腦殼……不管怎樣,這個解密的過程就也能夠看作是一個解密函數D,即網絡
$$p = D( c )$$函數
D是指Decrypt,函數輸入是密文,輸出是解密以後的明文。post
在這個過程這種,小紅可以成功解密小紙條的前提是,你得和她在課前約定好你加密的時候移動的是1位,2位仍是幾位,否則他就會和老師同樣一臉懵逼,不知道你在說啥。大家提早約定好的這個「幾位」,就是加密和解密的密鑰k,你會根據這個祕鑰來進行加密,小紅會根據這個祕鑰來進行解密。加密
因此你的傳紙條的動做抽象成這個過程:spa
明文p---->加密函數E---->密文c---->傳輸---->密文c----->解密函數D---->明文p
或者用公式來表達是:
$$c = D_k( E_k( c ) )$$
用大白話說就是:明文用同一個密鑰先加密再解密獲得的仍是同一個明文(等於沒說...)
從這裏咱們能夠總結出加密體質的五個要素:{明文p,密文c,密鑰k,加密函數E,解密函數D},對稱解密的的意思就是說,加密和解密的密鑰是同樣的,上面的過程是否是正好很對稱呢?
爲了方便使用,不用每次本身手動掰手指數字符,你還寫了Python程序:
# 移位密碼 def _move_leter(letter, n): """ 把字母變爲字母表後n位的字母,z後面接a :param letter: 小寫字母 :param n: 要移動的字母 :return: 移動的結果 """ return chr((ord(letter) - ord('a') + n) % 26 + ord('a')) def Encrypt(k, p): """ 移位密碼加密函數E :param k: 祕鑰k,每一個字母在字母表中移動k位 :param p: 明文p :return: 密文c """ letter_list = list(p.lower()) c = ''.join([_move_leter(x, k) for x in letter_list]) return c def Decrypt(k, c): """ 移位密碼解密函數D :param k: 祕鑰k,每一個字母在字母表中移動k位 :param c: 密文c :return: 明文p """ letter_list = list(c.lower()) p = ''.join([_move_leter(x, -k) for x in letter_list]) return p if __name__ == '__main__': p = 'ilovecoding' print('明文:' + p) print('密文:' + Encrypt(1, p)) print('解密:' + Decrypt(1, Encrypt(1, p))) assert Decrypt(1, Encrypt(1, p)) == p
運行這段代碼,就能夠看到輸出了:
明文:ilovecoding 密文:jmpwfdpejoh 解密:ilovecoding
終於,如今你能和你的小紅祕密地傳達紙條內容了,迎來全班人羨慕的目光,今後走上人生巔峯,本文到此結束。
...Hey,醒醒...
面對你倆日益頻繁的紙條往來,老師終於坐不住了,他想知道你倆寫的究竟是啥,因而在某次逮到你遞紙條以後,決定下功夫破解你所使用的密碼,也就是密碼分析。
根據他的瞭解,以你的水平,最可能用的就是移位密碼,但具體每次移動了幾位,沒法直接觀察得出。不過他又一想,你移動的位數頂可能是25位,由於,移動26位的效果等於沒移動,移27位的效果不就跟移動1位的效果是同樣的嘛!這就是說,你的密碼只能是0-25中的某一個數字,而不多是其餘的,就這麼二十幾個祕鑰,一個一個試就能知道你寫的是啥!
老師果真聰明絕頂,關鍵是也還會Python,就索性寫了一個程序,每次嘗試用不一樣的祕鑰來進行解密,並觀察解密出來的內容是否有意義:
def analyze(c): """ 移位密碼分析 :param c: 密文c :return: """ for k in range(26): # 用不一樣的祕鑰k嘗試解密 print('祕鑰%d:' % k + Decrypt(k, c)) if __name__ == '__main__': c = 'jmpwfdpejoh' analyze(c)
運行程序輸出結果爲:
祕鑰0:jmpwfdpejoh 祕鑰1:ilovecoding 祕鑰2:hknudbnchmf 祕鑰3:gjmtcambgle ...........
逐行觀察輸出結果,到第二行的時候就能看到原來的明文,也就知道了你要對小紅說的內容以及大家所約定的祕鑰。面對你冒着巨大風險在課堂上所傳遞的紙條內容,老師內心可能也是複雜的...
Anyway,你的小祕密已經被老師知道了,此時比較灰心,一直在想,到底是什麼緣由導致紙條計劃失敗?其實緣由很明顯,各位也看出來了,小明所使用的加密體制中,可用的祕鑰太少,或者說祕鑰空間過小,別人直接一一列舉進行窮搜就能破解,這就提示咱們:一個好的加密體制,它的祕鑰空間應該是足夠大的。
其實,你這次所用的移位密碼是古典的加密體制之一,聽說凱撒打仗時就用這種方法與將軍們聯繫,因此位移密碼也叫凱撒密碼(Caesar cipher)。相似的還有代換密碼,仿設射密碼等等,都是將單個字母替換成別的字母,來達到加密的目的。報紙上的猜謎遊戲就常常用這些方法,通常根據字母頻率進行破解,有興趣能夠進行進一步的瞭解。
因此到底要用什麼樣的加密方法,才能保證我和小紅的祕密不被人偷窺呢?
俗話說,知己知彼,百戰不殆,瞭解破解者的密碼分析方法,或許可以幫助咱們想出更安全的密碼體制。能夠在不一樣的情形下考察密碼體制的安全性,通常咱們都假設破解者知道咱們所使用的密碼體制,也就是說,不把密碼體制的安全性寄託在密碼體制的保密性上,而是放在祕鑰上。
破解者的目的就是找出所使用的祕鑰,常見的有如下幾種攻擊情形:
天啊,你不由驚呼,在這麼強的假設下,真的會有密碼體制可以存活嗎?
答案是有,並且這種密碼體制已經被普遍應用,甚至能夠說無處不在,它就是AES(Advanced Encryption Standard)。
難道不是要介紹AES嗎,怎麼會變成SPN網絡,這是啥?能夠吃嗎?
AES、DES等不少現代對稱加密方法的核心就是SPN網絡,它是代換-置換網絡(Substitution-Permutation Network)的縮寫,是現代對稱加密方法設計的藍本。能夠說,瞭解SPN網絡,就基本瞭解了AES。
很巧的是,這個網絡正好是容易理解的。SPN網絡的思想很簡單:既然加密一次不夠安全,那我就加密屢次,把第一次加密產生的密文再進行加密,解密的時候我連續進行兩次解密就能夠了,這樣是否是就安全了一些呢?
對於密碼體制\(S_1\),其加密與解密函數爲\(E_1\)與\(D_1\),對於密碼體制\(S_2\),其加密與解密函數爲\(E_2\)與\(D_2\),我構造出一個新的密碼體制\(S_3\),其加密函數爲:$$c = E_2( E_1( p ) )$$
解密函數爲$$p=D_1( D_2( c ) )$$
記爲$$S_3 = S_1 * S_2$$這樣破解\(S_3\)就可能會困難些。這個想法是否是很直接呢?這個思想在1949年才被提出,而提出者,可能理科生都多少聽過他的名字——香農(Shannon)。
注意,不是任何的加密體制均可以這樣「乘」起來變得更強,例如對於你的移位密碼,嵌套起來仍是移位密碼(爲何?),沒有任何改善,即\(S_1*S_1=S_1\),這樣的密碼體制被稱爲冪等的。
若是密碼體制不是冪等的,那麼屢次迭代就可能可以提升安全性,SPN就是使用這種思想,包含多輪的迭代,每輪的操做都是相同的。下面,介紹SPN單輪的操做:
SPN網絡是對必定長度的比特進行操做的,在本文中的SPN網絡中,一次加密的長度爲16個比特,即2字節,也就是說每次加密16比特的明文,輸出16比特的密文。
一個SPN網絡包含多輪迭代,每輪迭代的操做內容都同樣是:異或運算-->分組代換-->單比特置換
異或運算是比較常見的二元比特運算,用⊕表示,其規則就是「相同得0,不一樣得1」:
0 ⊕ 0 = 0 1 ⊕ 1 = 0 1 ⊕ 0 = 1 0 ⊕ 1 = 1
對於比特串,直接按每一位對應進行計算便可以了:
0011 ⊕ 1010 = 1001
異或的有比較有意思的性質:一個比特串亦或另外一個比特串兩遍,仍是等於他本身,即a ⊕ b ⊕ b = a
,這是由於a ⊕ b ⊕ b = a ⊕ ( b ⊕ b ) =a ⊕ 0 = a
,能夠帶入一些例子試試看。
SPN網絡中,每一輪的第一步就是把輸入的比特串w和祕鑰k進行亦或:u = w ⊕ k
,如:
0001110000100011 = 0010011010110111 ⊕ 0011101010010100
這一步的目的是根據祕鑰對明文進行混淆。若是你只知道輸出u
而不知道祕鑰k
,那麼你就猜不出實際輸入的w
是什麼,它是什麼均可能,並且是等機率的。例如對於1 = a ⊕ b
,不告訴你b是0仍是1,你就不知道a是什麼。而對於和
操做,若是知道1 = a and b
,那麼就能肯定a與b都是1。
這就是第一步,是否是很簡單呢?
這一步也很簡單,將第一步輸出的16比特的串分爲4組,每組4比特,即0001110000100011
寫成0001 1100 0010 0011
。而後對於每組再根據事先所定的表進行代換,代換表長這樣:
代換前 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
代換後 | E | 4 | D | 1 | 2 | F | B | 8 | 3 | A | 6 | C | 5 | 9 | 0 | 7 |
就拿第一列來講,表的意思是:若是你是0
(0000),那麼我要把你換成E
(1110),就是一個簡單的映射操做。
原比特串長這樣:0001 1100 0010 0011
<==> 1 C 2 3
,再對每一個字母查表獲得:4 5 D 1
<==> 0100 0101 1101 0001
,這樣就獲得代換後的比特串0100 0101 1101 0001
,完成了第二步。
這個表通常稱爲S盒(Substitution),這個過程能夠用v = S(u)
表示,u
是第一步異或的結果,也是第二步分組代換的輸入,v
是第二步的輸出。須要注意,S盒的輸入和輸出通常是非線性的關係。
單比特置換是將16比特中的每一比特,根據P盒(Permutation)移動挪位,這樣說很不直觀,直接上例子,P盒長這樣:
置換前的位置 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
置換後的位置 | 1 | 5 | 6 | 13 | 2 | 6 | 10 | 14 | 3 | 7 | 11 | 15 | 4 | 8 | 12 | 16 |
拿第二列來講,表的意思是:第二個比特要挪到第五個比特的位置,舉個好看的例子:
0100 0000 0000 0000 置換後爲==> 0000 1000 0000 0000
這個例子裏面第二個比特的1
挪到了第五的位置,而其餘位置的比特都是0
,挪位置以後仍是0
。
對於第二部輸出的結果1100 1101 1100 0100
,置換後的比特串爲0010 1110 0000 0111
,這樣就完成了第三步。
這一步能夠用W = S(v)
表示,v
是第二部的輸出,也是第三步的輸入,W
是第三步的輸出,P盒置換是一種線性的變換。
這三步放在一塊兒結果以下,建議讀者本身計算一遍:
w = 0010 0110 1011 0111 k = 0011 1010 1001 0100 第一步,異或運算: u = w ⊕ k = 0001 1100 0010 0011 第二步,分組代換: v = S(u) = 0100 0101 1101 0001 第三步,單比特置換: W = P(v) = 0010 1110 0000 0111
能夠寫成:W = P( S(w ⊕ k) )
,這樣就完成了一輪迭代,裏面用到的參數有k,S盒與P盒,如圖(圖片來自維基百科):
弄清楚一輪的流程,SPN總體就很容易明白了,就是一輪一輪的乘起來,上一輪的輸出做爲這一輪的輸入:
w0 = x w1 = P(S(w0 ⊕ k1)) w2 = P(S(w1 ⊕ k2)) w3 = P(S(w2 ⊕ k3)) w4 = P(S(w3 ⊕ k4)) y = w4
w0
就是16比特的明文,w4
是4輪操做後的16比特密文結果,是否是很簡單?須要注意的是,每一輪迭代的祕鑰k是不同的,通常是由一個基礎祕鑰經特定祕鑰編排算法生成的,而使用的S盒P盒都是相同的,會提早肯定好,而且是公開的。
下圖是一個三輪SPN網絡的示意圖(圖片來自維基百科):
注意在最後一輪去掉了代換操做,這樣作可使加密算法稍微作一些調整就能夠用來進行解密。
OK! SPN網絡就是這些內容,你已經掌握了它,若是你還想和小紅傳紙條的話,能夠試試用它加密,會比移位密碼更安全一些。
什麼?本身手動代換置換太麻煩?不用怕,貼心的我已經爲你準備好了Python代碼。
我實現的是4輪迭代的SPN網絡,以及加密和解密算法,其結構圖以下(圖片來自 Cryptography Theory and Practice ):
每次加密輸入16比特的明文,輸出16比特的密文,代碼以下:
# S盒參數 S_Box = [14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7] # P盒參數 P_Box = [1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15, 4, 8, 12, 16] def gen_K_list(K): """ 祕鑰編排算法,由一個32比特祕鑰生成5個16比特子祕鑰 :param K: 32比特祕鑰 :return: [k1,k2,k3,k4,k5],五個16比特子祕鑰 """ Ks = [] for i in range(5, 0, -1): ki = K % (2 ** 16) Ks.insert(0, ki) K = K >> 4 return Ks def pi_s(s_box, ur): """ 分組代換操做 :param s_box:S盒參數 :param ur:輸入比特串,16比特 :return:輸出比特串,16比特 """ vr = 0 for i in range(4): uri = ur % (2 ** 4) vri = s_box[uri] vr = vr + (vri << (4 * i)) ur = ur >> 4 return vr def pi_p(p_box, vr): """ 單比特置換操做 :param p_box:P盒參數 :param vr:輸入比特串,16比特 :return:輸出比特串,16比特 """ wr = 0 for i in range(15, -1, -1): vri = vr % 2 vr = vr >> 1 wr = wr + (vri << (16 - p_box[i])) return wr def reverse_Sbox(s_box): """ 求S盒的逆 :param s_box:S盒參數 :return:S盒的逆 """ re_box = [-1] * 16 for i in range(16): re_box[s_box[i]] = i return re_box def reverse_Pbox(p_box): """ 求P盒的逆 :param s_box:P盒參數 :return:P盒的逆 """ re_box = [-1] * 16 for i in range(16): re_box[p_box[i] - 1] = i + 1 return re_box def do_SPN(x, s_box, p_box, Ks): """ 5輪的SPN網絡,能夠用來進行加密或解密 :param x: 16比特輸入 :param s_box: S盒參數 :param p_box: P盒參數 :param Ks: [k1,k2,k3,k4,k5],五個16比特子祕鑰 :return: 16比特輸出 """ wr = x for r in range(3): ur = wr ^ Ks[r] # 異或操做 vr = pi_s(s_box, ur) # 分組代換 wr = pi_p(p_box, vr) # 單比特置換 ur = wr ^ Ks[3] vr = pi_s(s_box, ur) y = vr ^ Ks[4] return y def encrypt(K, x): """ 根據祕鑰K對16比特明文x進行加密 :param K:32比特祕鑰 :param x:16比特明文 :return:16比特密文 """ Ks = gen_K_list(K) return do_SPN(x, S_Box, P_Box, Ks) def decrypt(K, y): """ 根據祕鑰K對16比特密文y進行解密。 :param K:32比特祕鑰 :param y:16比特密文 :return:16比特明文 """ Ks = gen_K_list(K) Ks.reverse() # 祕鑰逆序編排 # 祕鑰置換 Ks[1] = pi_p(P_Box, Ks[1]) Ks[2] = pi_p(P_Box, Ks[2]) Ks[3] = pi_p(P_Box, Ks[3]) s_rbox = reverse_Sbox(S_Box) # S盒求逆 p_rbox = reverse_Pbox(P_Box) # P盒求逆 return do_SPN(y, s_rbox, p_rbox, Ks) if __name__ == '__main__': x = 0b0010011010110111 K = 0b00111010100101001101011000111111 print('初始明文:', format(x, '016b')) print('加密密文:', format(encrypt(K, x), '016b')) print('解密結果:', format(decrypt(K, encrypt(K, x)), '016b')) assert decrypt(K, encrypt(K, x)) == x
能夠直接看do_SPN
函數,函數裏面循環3次,對應3輪迭代,第4輪迭代沒有置換操做。encrypt
與decrypt
函數調用do_SPN
函數便可進行加密和解密操做(爲何能夠調用SPN進行解密?能夠對照代碼觀察SPN的結構想想),運行程序輸出爲:
初始明文: 0010011010110111 加密密文: 1011110011010110 解密結果: 0010011010110111
至此,SPN網絡已經徹底實現!那麼它的安全性如何呢?
首先,咱們知道,這個SPN網絡的祕鑰是32位的,大約是有4百萬的候選祕鑰,這個數量的祕鑰,手動窮搜是很難的,用計算機來窮搜就會比較容易了,不過咱們隨時對它進行改造,增長祕鑰長度,如256位,這時候機器窮搜也不行了。
其次,若是SPN層數較少,對其進行線性分析或者差分分析一般會比較容易成功,這些分析方法的代碼我也已經寫好了,不過今天的內容已經夠多了,咱們下次再介紹它:)