1. 程式人生 > 其它 >DES加密演算法原理及Python程式碼實現

DES加密演算法原理及Python程式碼實現

技術標籤: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加密流程框圖

  圖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}

Li=Ri1
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=Li1F(Ri1,Ki)
  其中,F函式示意圖見圖1.2.1。

圖1.2.1 F函式示意圖

1.2.2 金鑰的生成

圖 1.2.2 16輪金鑰生成圖

  圖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.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

參考文獻

DES演算法原理完整版

DES加解密python實現

DES演算法中金鑰的校驗位