SM3雜湊算法實現——第三部分
一、SM3 密碼概述
我們首先把需要用到的算法呈現出來,最后我們再考慮如何集合為一個庫的方法,這一部分我們就開始編寫一個新的算法:國家商用密碼標准SM3密碼算法。
首先要明白SM3是一個什么樣的東西:單向加密算法。也可以稱之為密碼哈希算法、雜湊算法、摘要算法,都可以指這類算法。顧名思義,這類算法只能加密不能解密,所以不是為了直接保護數據的秘密性而存在的,不是讓使用者解密這串密文得到原文而使用的。這類算法一般用於保護數據明文的完整性,抗篡改而產生。只要輸入的是同樣的明文,那么輸出的密文(雜湊值/摘要值/哈希結果)就是一樣的,而找到一個字符串與預期字符串的輸出結果是一樣的,這目前在理論上不可實現,也就是說在目前的技術前提下,還不能做到篡改原文還能保持摘要值不受影響的事情。這類算法比較出名的比如MD5,SHA-1代表的系列算法,但其中部分算法如MD5已經被證實並不安全,所以在實際使用過程中一定要使用當前核准的優秀加密算法!
密碼雜湊算法在現代密碼學中起着重要的作用,它將任意長度的消息壓縮成固定長度的摘要。它是密碼學3大基礎算法之一(加密算法、數字簽名算法和雜湊算法),用於數據的完整性校驗、身份認證、數字簽名、密鑰推導、消息認證碼和隨機比特生成器等。
2012年,國家商用密碼管理辦公室發布了SM3密碼雜湊算法為密碼行業標准。2016年,國家標准化委員會公布了SM3密碼雜湊算法為國家標准。目前SM3已經提交ISO國際標准化組織,進入DIS階段。
二、SM3 詳細設計
如所有的密碼雜湊算法,SM3沒有密鑰這個概念,輸入數據長度為任意,輸出長度為32字節(256-bit)下面我們按步驟一點點看:
1.初始化常量
廢話不多說,簡單的定義:
IV = "\x73\x80\x16\x6f\x49\x14\xb2\xb9\x17\x24\x42\xd7\xda\x8a\x06\x00\xa9\x6f\x30\xbc\x16\x31\x38\xaa\xe3\x8d\xee\x4d\xb0\xfb\x0e\x4e"
T0_15 = "\x79\xcc\x45\x19"
T16_63 = "\x7a\x87\x9d\x8a"
其中T0_15表示CF模塊中輪數0<=j<16時取用的值,T16_63表示輪數16<=j<64時取用的T值。IV為初始狀態常量。
2.明文消息填充
對長度為l(l<2^64)比特的消息m,首先將比特'1'填充至消息末尾,再添加k個'0',其中k滿足l+k+1===448 mod512,取最小非負整數。再添加一個64比特字符串,該比特串是長度l的二進制表示。這樣,我們就把明文填充至512比特的倍數長度。
看不太明白?代碼見!
3.迭代壓縮結構
填充后的消息M就可以按照512bit進行分組啦!依次分為n個消息組,分別與初始常量IV按以下方式進行迭代:
FOR i=0 TO (n-1):
V[i+1] = CF( V[i] , B[i] );
ENDFOR
其中CF為壓縮函數,V[0] = IV,B為填充后的消息分組,迭代的輸出結果就是最后依次循環中被賦值的變量V[n]。
4.壓縮函數
咱先不說那些復雜的公式,先看圖理解一下,這個部分的結構比較龐大:
右半部分與序列密碼中的部分很是相似,可以將原來的數據進行充分的混合,隨着寄存器不停地進動,左端的信息會進入狀態更新區內,與數據V進行混合運算,再次充分混淆。這樣一來,每部分輸出的信息V都是本段數據和之前所有數據共同運算的結果,可以代表這之前的數據是完整無修改的。公示詳述如下:
將最后一輪產生的V進行輸出,即得到雜湊值運算結果!
這里面顯然有幾個地方還沒有定義,往下看:
5.布爾函數和置換函數
在CF壓縮函數中涉及到的布爾函數和置換函數的詳細定義描述為公式如下:
如此設計的布爾函數和置換函數讓每個部分的數據都參與運算且運算效率極高,配合CF結構保證了運算的非線性特性明顯。
至此,整個算法的介紹到此為止。
三、Python語言程序實現
# 數字格式轉化
def int2str( num ) :
out = ""
out = out + chr( num//65536 ) + chr( (num%65536)//256 ) + chr( num%256 )
return out
def str2hex( string ):
out = ""
for i in range ( 0 , len(string) ):
out = out + " " + hex(ord( string[i] ))
return out
# 字符串異或運算,后面分別是字符串與、或、非運算
def strxor( message , key , len ):
out = ""
for i in range ( 0 , len ):
ch = ord(message[i]) ^ ord(key[i])
out = out + chr(ch)
return out
def strand( message , key , len ):
out = ""
for i in range ( 0 , len ):
ch = ord(message[i]) & ord(key[i])
out = out + chr(ch)
return out
def stror( message , key , len ):
out = ""
for i in range ( 0 , len ):
ch = ord(message[i]) | ord(key[i])
out = out + chr(ch)
return out
def strnot( string , len ) :
out = ""
for i in range ( 0 , len ) :
ch = ~ord(string[i])
out = out + chr(ch)
return out
# 字符串按比特向左移位
def strldc( string , bit ):
byte = bit // 8
bit = bit % 8
out = ""
if bit == 0 :
out = string[byte:] + string[:byte]
else :
reg = string[byte:] + string[:byte+1]
for i in range (0,len(reg)-1):
out = out + chr(((ord(reg[i])*(2**bit))+(ord(reg[i+1])//(2**(8-bit))))%256)
out = out[:len(string)]
return out
# 4字節模加運算
def stradd_4( str1 , str2 ) :
out = ""
num1 = ord(str1[0])*16777216+ord(str1[1])*65536+ord(str1[2])*256+ord(str1[3])
num2 = ord(str2[0])*16777216+ord(str2[1])*65536+ord(str2[2])*256+ord(str2[3])
add = (num1 + num2)%4294967296
out = out + chr(add//1677216) + chr((add%1677216)//65536) + chr((add%65536)//256) + chr(add%256)
return out
def functionFF( A , B , C , j ) :
if j < 16 :
FF = strxor( A , strxor( B , C , 4 ) , 4 )
if j > 15 :
FF = stror( stror( strand(A,B,4) , strand(A,C,4) , 4 ) , strand(B,C,4) , 4)
return FF
def functionGG( A , B , C , j ) :
if j<16 :
GG = strxor( A , strxor( B , C , 4 ) , 4 )
if j > 15 :
GG = stror( strand(A,B,4) , strand(strnot(A,4),C,4) , 4 )
return GG
def functionP( string , mode ) :
out = ''
if mode == 0 :
out = strxor( string , strxor( strldc(string,9) , strldc(string,17) , 4 ) , 4 )
if mode == 1 :
out = strxor( string , strxor( strldc(string,15) , strldc(string,23) , 4 ) , 4 )
return out
def functionCF( V , B ) :
for i in range (0,68) :
# 消息擴展過程
P = strxor( strxor( B[0:4] , B[28:32] , 4 ) , strldc( B[42:46] , 15 ) , 4)
Badd = strxor( P , strxor( B[40:44] , strldc(B[12:16],7) , 4 ) , 4 )
out1 = strxor( B[0:4] , B[16:20] , 4 )
out = B[0:4]
B = B[0:60] + Badd
# 狀態更新過程
if i < 64 :
if i < 16 :
SS1 = strldc( stradd_4( strldc( V[0:4] , 12 ) , stradd_4( V[16:20] , strldc(T0_15,i%32) ) ) , 7 )
else :
SS1 = strldc( stradd_4( strldc( V[0:4] , 12 ) , stradd_4( V[16:20] , strldc(T16_63,i%32) ) ) , 7 )
SS2 = strxor( SS1 , strldc( V[0:4] , 12 ) , 4 )
TT1 = stradd_4( stradd_4( functionFF( V[0:4] , V[4:8] , V[8:12] , i ) , V[12:16] ) , stradd_4( SS2 , out1 ) )
TT2 = stradd_4( stradd_4( functionGG( V[16:20] , V[20:24] , V[24:28] , i ) , V[28:32] ) , stradd_4( SS1 , out ) )
V = strxor( V , TT1 + V[0:4] + strldc(V[4:8],9) + V[8:12] + functionP(TT2,0) + V[16:20] + strldc(V[20:24],19) + V[24:28] , 32 )
return V
IV = "\x73\x80\x16\x6f\x49\x14\xb2\xb9\x17\x24\x42\xd7\xda\x8a\x06\x00\xa9\x6f\x30\xbc\x16\x31\x38\xaa\xe3\x8d\xee\x4d\xb0\xfb\x0e\x4e"
T0_15 = "\x79\xcc\x45\x19"
T16_63 = "\x7a\x87\x9d\x8a"
plain = input( "請輸入雜湊函數明文:" )
l = int2str( len(plain)*8 )
plain = plain + "\x80"
k = 56 - (len(plain)%64) - 1
plain = plain + "\x00"*k
plain = plain + l
print( "plain :" , str2hex(plain) )
for i in range ( 0 , len(plain)//64-1 ) :
IV = functionCF( IV , plain[64*l:64*l+64] )
hash_value = IV
print( " hash :" , str2hex(hash_value) )
下面附上程序運行的效果圖:
由於SM3的功能不具備多樣化特征,且代碼長度短,這里暫不呈現SM3類的封裝和使用過程,故代碼方面的展示到此為止。
四、實現難點
雜湊算法一直以來沒有嘗試去實現過,也就是說這是作者第一次嘗試去實現一個密碼雜湊算法,剛開始做的時候壓力還是很大的,但是隨着算法步驟的一點點解讀,才發現其中的內部結構並不復雜,都是由計算機基本運算組成的,但讀懂文字解釋的步驟還是相當有難度的。
論文依據:SM3密碼雜湊算法——王小雲、於紅波。更多關於SM3密碼的分析與設計細節詳見論文。