DES加密演算法原理及Python程式碼實現
寫在前面:
1、本文中DES加解密基本流程及S盒等引數參照自楊波《現代密碼學(第四版)》,實現過程均為自編函式。
2、為了說明64bit金鑰中,只有56bit真正參與加解密過程,對網上程式碼中的金鑰生成過程做出了修改,詳見正文。
3、本文借鑑了網上部分程式碼,具體見參考文獻,並對部分地方按題主想法進行了優化修改。
1. DES演算法理論介紹
具體可參見楊波《現代密碼學(第四版)》。本文只做簡要介紹。
1.1 DES介紹
DES全稱為Data Encryption Standard,即資料加密標準,是一種使用金鑰加密的塊演算法,1977年被美國聯邦政府的國家標準局確定為聯邦資料處理標準(FIPS),並授權在非密級政府通訊中使用,隨後該演算法在國際上廣泛流傳開來。
1.2 DES加解密演算法描述
圖1.2是DES加密變換框圖,其中明文一組為64bit,金鑰K長度56bit。加密過程有3個階段:
1、初始置換IP,用於重排明文分組的64位元資料。
2、經過具有相同功能的16輪Feistel變換,每輪中F函式中都有置換和代換運算,第16輪變換的輸出分為左右兩半,並被交換次序。
3、經過一個逆初始置換IP-1(為IP的逆)從而產生64位元的密文。
1.2.1 輪結構
採用Feistel相同的輪結構,將64bit的輪輸入分為32bit的左、右兩半,分別記為L和R:
L
i
=
R
i
−
1
L_i=R_{i-1}
R
i
=
L
i
−
1
⊕
F
(
R
i
−
1
,
K
i
)
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ R_i=L_{i-1}\oplus F\left( R_{i-1},K_i \right)
Ri=Li−1⊕F(Ri−1,Ki)
其中,F函式示意圖見圖1.2.1。
1.2.2 金鑰的生成
圖1.2.2是使用56位元金鑰的方法。金鑰首先通過一個置換函式PC_1,然後,對加密過程的每一輪,通過一個左迴圈移位和一個置換PC_2產生一個子金鑰。其中每輪的置換都相同,但由於金鑰被重複迭代,所以產生的每輪子金鑰不相同。
1.2.3 DES解密
和Feistel密碼一樣,DES的解密和加密使用同一種演算法,但子金鑰使用的順序相反。
2. Python程式碼實現
2.1 實現思路
由於加密與解密演算法類似,此處給出加密演算法實現思路:
Step1:從檔案中讀取明文;
Step2:將明文利用ASCII換成01位元流;
Step3:將明文位元流每64位分為一組,最後不足的用0補齊;
Step4:對64位位元加密操作(進行IP置換,16輪的Feistel變換、交換L、R、IP逆置換,將L、R合併為密文位元流);
Step5:將每一組密文位元流合併轉換成密文字元儲存至檔案。
2.2 模組化程式設計
2.2.1 各個模組呼叫關係及實現功能設計
根據2.1中的實現思路,DES加解密演算法各個模組呼叫關係及實現功能設計如圖2.2.1所示,2.2.2中將詳細介紹各個功能的程式碼實現。
2.2.2 模組功能實現
針對2.2.1的實現思路,分析DES加解密演算法流程,可將其具體分成不同的模組分別設計:
1.)檔案讀入模組
def read_file(filename):
'''
filename : 開啟檔名
return : 讀取檔案中字串
'''
try:
fp = open(filename,"r",encoding='utf-8')
message = fp.read()
fp.close()
return message
except:
print("Open file error!")
2.)檔案寫入模組
將字串寫入檔案text.txt與檔案讀入程式碼類似,只做如下修改:
fp = open('text.txt','w',encoding='utf-8')
fp.write(message)
def write_file(message): 輸入需要寫入字串,即可生成含有該字串的text.txt檔案。
3.)字串轉01位元流
def str_bit( message ):
'''
message :字串
return :將讀入的字串序列轉化成01位元流序列
'''
bits = ""
for i in message:
asc2i = bin(ord(i))[2:] #bin將十進位制數轉二進位制返回帶有0b的01字串
'''為了統一每一個字元的01bit串位數相同,將每一個均補齊8位'''
for j in range(8-len(asc2i)):
asc2i = '0' + asc2i
bits += asc2i
return bits
每一個字元利用ord( )函式轉化成對應ASCII值,利用bin( )將其轉換成二進位制字串。
4.)01位元流轉字元
def bit_str(bits): 輸入01位元串(長度要是8的倍數),返回對應的字元,核心程式碼如下,主要利用int( )和chr( )函式。
for i in range(len(bits)//8):
temp += chr(int(bits[i*8:(i+1)*8],2))
5.)金鑰字串轉位元流
本文假定金鑰位元流的8、16、24、32、40、48、56、64位採用偶校驗方式,分別校驗其前面的7位01串。金鑰字串依然採用ASCII編碼方式,一個字元佔7位,第8位採用偶校驗方式,核心程式碼如下:
def process_key(key):
'''
key : 輸入的金鑰字串
return : 64bit 01序列金鑰(採用偶校驗的方法)
'''
key_bits = ""
for i in key:
count = 0
asc2i = bin(ord(i))[2:]
'''將每一個ascii均補齊7位,第8位作為奇偶效驗位'''
for j in asc2i:
count += int(j)
if count % 2 == 0:
asc2i += '0'
else:
asc2i += '1'
for j in range(7-len(asc2i)):
asc2i = '0' + asc2i
key_bits += asc2i
if len(key_bits) > 64:
return key_bits[0:64]
else:
for i in range(64-len(key_bits)):
key_bits += '0'
return key_bits
6.)對位元流分組
函式定義如下,最後一組位數不足即補0。實現簡單,此處不在贅述。
def divide(bits,bit):
'''
bits : 將01bit按bit一組進行分組
return : 按bit位分組後得到的列表
'''
7.)IP置換
為了實現簡單,提前將IP、IP_RE、PC_1、PC_2、E、P、S等盒值寫入檔案DES_BOX.py,主函式即可直接呼叫。IP置換實現如下:
def IP_change(bits):
'''
bits:一組64位的01位元字串
return:初始置換IP後64bit01序列
'''
ip_str = ""
for i in IP:
ip_str = ip_str + bits[i-1]
return ip_str
8.)PC_1置換
實現程式碼同IP置換,此處不在贅述。
9.)位元串左移
def key_leftshift(key_str,num): 將輸入的01位元流key_str迴圈左移num位返回,實現過於簡單,此處不在贅述。
10.)PC_2置換
實現程式碼同IP置換,此處不在贅述。
11.)16輪金鑰生成
def generate_key(key):
'''
key : 64bit01金鑰序列
return : 16輪的16個48bit01金鑰列表按1-16順序
'''
key_list = ["" for i in range(16)]
key = PC_1_change(key) #1、呼叫置換PC_1
key_left = key[0:28] #2、左右28位分開
key_right = key[28:]
for i in range(len(SHIFT)): #共16輪即16次左迴圈移位
key_left = key_leftshift(key_left, SHIFT[i]) #3、呼叫位元串左移函式
key_right = key_leftshift(key_right, SHIFT[i])
key_i = PC_2_change(key_left + key_right) #4、左右合併呼叫置換PC_2
key_list[i] = key_i #5、將每一輪的56bit金鑰存入列表key_list
return key_list
12.)E置換
實現程式碼同IP置換,此處不在贅述。
13.)異或運算
函式實現較為簡單,將輸入的兩個字串逐位運算即可,僅給出定義如下。
def xor(bits,ki):
'''
bits : 48bit01字串 / 32bit01 F函式輸出
ki : 48bit01金鑰序列 / 32bit01 Li
return :bits與ki異或運算得到的48bit01 / 32bit01
'''
14.)單次S盒查詢
def s(bits,i):
'''
bits : 6 bit01字串
i : 使用第i個s盒
return : 4 bit01字串
'''
row = int(bits[0]+bits[5],2)
col = int(bits[1:5],2)
num = bin(S[i-1][row*16+col])[2:] #i-1號S盒的row*16+col號數
for i in range(4-len(num)): #補齊4位後輸出
num = '0'+num
return num
15.)S盒變換
def S_change(bits): 輸入48bit字串,輸出經過S盒之後的32bit字串。核心程式碼如下,呼叫8次單次S盒查詢函式:
for i in range(8):
temp = bits[i*6:(i+1)*6]
temp = s(temp,i+1)
s_change += temp
16.)P置換
實現程式碼同IP置換,此處不在贅述。
17.)F函式
通過呼叫12-16模組即可實現第
i
i
i輪F函式運算:
def F(bits,ki):
'''
bits : 32bit 01 Ri輸入
ki : 48bit 第i輪金鑰
return : F函式輸出32bit 01序列串
'''
bits = xor(E_change(bits),ki)
bits = P_change(S_change(bits))
return bits
18.)IP逆置換
實現程式碼同IP置換,此處不在贅述。
19.)64bit加密
呼叫IP置換、16輪金鑰生成、F函式、異或運算、IP逆置換等模組可以實現64bit一組明文的加密:
def des_encrypt(bits,key):
'''
bits : 分組64bit 01明文字串
key : 64bit01金鑰
return : 加密得到64bit 01密文序列
'''
bits = IP_change(bits) # IP置換
L = bits[0:32] # 切片分成兩個32bit
R = bits[32:]
key_list = generate_key(key) # 生成16個金鑰
for i in range(16): # 16輪迭代變換
L_next = R
R = xor(L,F(R,key_list[i]))
L = L_next
result = IP_RE_change( R + L) # IP逆置換
return result
20.)64bit解密
def des_decrypt(bits,key):該模組與64bit加密模組流程相同,不同在於16個金鑰使用順序相反,16輪代換程式碼如下,其餘程式碼同加密。
for i in range(16):
L_next = R
R = xor(L,F(R,key_list[15-i]))
L = L_next
21.)整體加密模組
def all_des_encrypt(message,key): 讀入明文字串message,以及金鑰字串key,返回加密後01位元流。通過呼叫字串轉01位元流、金鑰字串轉位元流、對位元流分組、64bit加密等模組即可實現:
def all_des_encrypt(message,key):
'''
message : 讀入明文字串
key : 讀入金鑰串
returns : 密文01序列
'''
message = str_bit(message) # 明文轉01位元流
key = process_key(key) # 64bit初始金鑰生成
mess_div = divide(message, 64) # 明文按64bit一組進行分組
result =""
for i in mess_div:
result += des_encrypt(i, key) #對每一組進行加密運算
return result
22.)整體解密模組
def all_des_decrypt(message,key): 讀入明文字串message,以及金鑰字串key,返回解密後01位元流。與加密類似,此處不在贅述。
2.2.3 主模組
輸出提示語,並與使用者互動。通過呼叫檔案讀入或寫入、字串轉01位元流、位元流轉01字串、加解密等模組實現,虛擬碼如下:
2.3 Python原始碼
全部程式碼及相關注釋點這裡github。
2.4 關於金鑰的說明
起初按如下方式生成64bit金鑰,即將金鑰中每一個字元轉成8bitASCII碼,8個字元共構成64bit金鑰。
def process_key(key):
bin_key = str_bit(key) #呼叫字串轉01位元流模組
return bin_key
結果發現“wuzhenll”和“vt{idomm”這兩個金鑰加密後的密文相同。
這是因為表面上這兩個密碼迥然不同,但是由於它們僅在奇偶校驗位上有區別。例如,w的ASCII為01110111,v的ASCII為01110110,僅僅只在最後一位有區別。
由於64位金鑰中的第8位、第16位、第24位、第32位、第40位、第48位、第56位、第64位作為奇偶校驗位,在PC_1置換時去掉了這8位。如果輸入的密碼只是在這8位上有區別的話,那麼操作後的結果也將是一樣的。所以用這兩個密碼進行加密解密操作得到的結果是一樣的。
2.5 使用說明
執行環境: Python 3.7
使用方法: 將DES_BOX.py、DES.py、以及需要含有明密文的文字檔案放置於同一目錄下,執行DES.py程式,根據相應提示語即可完成操作。明文加密後亂碼會自動儲存到text.txt檔案中。(注:輸入的金鑰將轉化成對應的ASCII按照偶校驗的方式構成64bit初始金鑰)
2.6 結果測試
測試一:明文:keep early hours! 金鑰:password
測試二: 明文:Have a good day! 金鑰:messagee