Hash

SHA-3 子功能可逆性說明

  • January 6, 2021

SHA-3 子功能可逆性說明

我剛剛完成了一個非常緩慢且笨拙的 SHA-3 (224,256,384,512) python 實現。練習不是為速度而設計的。我唯一的目標是深入了解 SHA-3 系列函式的下劃線功能,並以我認為可讀的方式對其進行記錄。

參考文獻包括以下內容:

SHA-3 文件-

https://pdfs.semanticscholar.org/8450/06456ff132a406444fa85aa7b5636266a8d0.pdf http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf

Theta() 文件-

任何人都可以簡要解釋 SHA3 中壓縮框的“theta step”嗎?

中間值文件-

http://csrc.nist.gov/groups/ST/toolkit/examples.html

在此過程中,我發現兩組 SHA-3 文件之間存在各種差異,關於 FIPS 202,某些領域,特別是 3.2.2 和 3.2.3,文件可以同時使用編輯和澄清(IMO) . 在這些領域,令我不滿意的是,我求助於反轉提供的中間值來導出 pi() 和 rho(),因為最終解密提供的文件更容易。

因此,以下內容直接引用自 FIPS 202 第 4 節關於海綿結構的內容:

“SHA-3 函式,在 Sec. 圖 6 是海綿構造的實例,其中基礎函式 f 是可逆的,即置換,儘管海綿構造並不要求 f 是可逆的。”

這在 pi() 和 rho() 的可逆性中很明顯。並且鑑於 SHA-3 iota() 的約束也是可逆的。從功能上講,它只是一個帶有捨入常數的 XOR 操作。

分析 theta() 和 chi() 需要更長的時間。

我會在這裡住一段時間:

什麼標準使 Keccak 圓函式的 theta 步是可逆的?

我的具體問題:為什麼 SHA-3 設計者不讓每個子功能都不可逆?

我的好奇程式碼。

def bin_8bit(dec):
   return(str(format(dec,'08b')))

def bin_32bit(dec):
   return(str(format(dec,'032b')))

def bin_4bit(dec):
   return(str(format(dec,'04b')))

def bin_64bit(dec):
   return(str(format(dec,'064b')))

def hex_return(dec):
   return(str(format(dec,'08x')))

def hex_double(dec):
   return(str(format(dec,'02x')))

def hex_single(dec):
   return(str(format(dec,'01x')))

def dec_return_bin(bin_string):
   return(int(bin_string,2))

def dec_return_hex(hex_string):
   return(int(hex_string,16))

def L_P(SET,n):
   to_return=[]
   j=0
   k=n
   while k<len(SET)+1:
       to_return.append(SET[j:k])
       j=k
       k+=n 
   return(to_return)

def s_l(bit_string):
   bit_list=[]
   for i in range(len(bit_string)):
       bit_list.append(bit_string[i])
   return(bit_list)

def l_s(bit_list):
   bit_string=''
   for i in range(len(bit_list)):
       bit_string+=bit_list[i]
   return(bit_string)

def rotate_left(bit_string,n):
   if n==0:
       return(bit_string)
   bit_list = s_l(bit_string)
   count=0
   while count <= n-1:
       list_main=list(bit_list)
       var_0=list_main.pop(0)
       list_main=list(list_main+[var_0])
       bit_list=list(list_main)
       count+=1
   return(l_s(list_main))

def rotate_right(bit_string,n):
   if n==0:
       return(bit_string)
   bit_list = s_l(bit_string)
   count=0
   while count <= n-1:
       list_main=list(bit_list)
       var_0=list_main.pop(-1)
       list_main=list([var_0]+list_main)
       bit_list=list(list_main)
       count+=1
   return(l_s(list_main))

def shift_right(bit_string,n):
   bit_list=s_l(bit_string)
   count=0
   while count <= n-1:
       bit_list.pop(-1)
       count+=1
   front_append=['0']*n
   return(l_s(front_append+bit_list))

def mod_32_addition(input_set):
   value=0
   for i in range(len(input_set)):
       value+=input_set[i]
   mod_32 = 4294967296
   return(value%mod_32)

def xo(bit_string_1,bit_string_2):
   xor_list=[]
   for i in range(len(bit_string_1)):
       if bit_string_1[i]=='0' and bit_string_2[i]=='0':
           xor_list.append('0')
       if bit_string_1[i]=='1' and bit_string_2[i]=='1':
           xor_list.append('0')
       if bit_string_1[i]=='0' and bit_string_2[i]=='1':
           xor_list.append('1')
       if bit_string_1[i]=='1' and bit_string_2[i]=='0':
           xor_list.append('1')
   return(l_s(xor_list))

def and_2str(bit_string_1,bit_string_2):
   and_list=[]
   for i in range(len(bit_string_1)):
       if bit_string_1[i]=='1' and bit_string_2[i]=='1':
           and_list.append('1')
       else:
           and_list.append('0')

   return(l_s(and_list))

def or_2str(bit_string_1,bit_string_2):
   or_list=[]
   for i in range(len(bit_string_1)):
       if bit_string_1[i]=='0' and bit_string_2[i]=='0':
           or_list.append('0')
       else:
           or_list.append('1')
   return(l_s(or_list))

def not_str(bit_string):
   not_list=[]
   for i in range(len(bit_string)):
       if bit_string[i]=='0':
           not_list.append('1')
       else:
           not_list.append('0')
   return(l_s(not_list))

def init_array():
   int_bits = L_P(L_P('0'*1600,64),5)
   return(int_bits)

def sub_str_concat(list_of_lists):
   to_return=[]
   for i in range(len(list_of_lists)):
       insert=''
       for x in range(len(list_of_lists[i])):
           insert+=list_of_lists[i][x]
       to_return.append(insert)
   return(to_return)

def str_concat(list_of_strings):
   to_return=''
   for i in range(len(list_of_strings)):
       to_return+=list_of_strings[i]
   return(to_return)

def list_concat(list_of_lists):
   to_return=[]
   for i in range(len(list_of_lists)):
       to_return+=list_of_lists[i]
   return(to_return)

def flip_string(a_string):
   to_return=''
   for i in range(1,len(a_string)+1):
       to_return+=a_string[-i]
   return(to_return)

def message_append(str_msg):
   return(str_msg+'01')

def sha_3_rate(output_len):
   if output_len==224:
       return(1152)
   if output_len==256:
       return(1088)
   if output_len==384:
       return(832)
   if output_len==512:
       return(576)

def pad(x,m):
   j=(-m-2)%x
   return('1'+'0'*j+'1')

def trunc(string,index):
   return(string[0:index])

def message_processing(bit_string,digest_len):
   msg_bs = message_append(bit_string) 
   p = msg_bs + pad(sha_3_rate(digest_len),len(msg_bs))
   to_split = L_P(p,8)
   new_hex=[]
   for i in range(len(to_split)):
       new_hex.append(hex_double(int(flip_string(to_split[i]),2)))
   back_append = 200-len(new_hex)
   new_hex = new_hex+['00']*back_append
   total_string=''
   for i in range(len(new_hex)):
       total_string+=new_hex[i]
   to_insert = L_P(total_string,16)
   to_return=[]
   for i in range(len(to_insert)):
       to_return.append(flip_string(L_P(to_insert[i],2)))
   return(to_return)

def message_expansion(hex_list):
   to_convert=''
   for i in range(len(hex_list)):
       to_convert+=bin_8bit(int(hex_list[i],16))
   return(to_convert)

def theta(s):
   c_xz=[]
   for i in range(5):
       c_xz.append(xo(xo(xo(xo(s[i],s[i+5]),s[i+10]),s[i+15]),s[i+20]))
   d_xz=[]
   for i in range(5):
       d_xz.append(xo(c_xz[(i-1)%5],rotate_left(c_xz[(i+1)%5],1)))
   a_xyz=[]
   for i in range(5):
       a_xyz.append([xo(s[i],d_xz[i]),
                     xo(s[i+5],d_xz[i]),
                     xo(s[i+10],d_xz[i]),
                     xo(s[i+15],d_xz[i]),
                     xo(s[i+20],d_xz[i])])
   a_xyz=list_concat(a_xyz)
   order_return=[]
   for i in range(5):
       order_return.append([a_xyz[i],a_xyz[i+5],a_xyz[i+10],a_xyz[i+15],a_xyz[i+20]])
   return(list_concat(order_return))

def rho(s):
   off_set=[0,1,190,28,91,
            36,300,6,55,276,
            3,10,171,153,231,
            105,45,15,21,136,
            210,66,253,120,78]
   to_return=[]
   for i in range(len(s)):
       to_return.append(rotate_left(s[i],off_set[i]))
   return(to_return)

def pi(s):
   index=[0,6,12,18,24,
          3,9,10,16,22,
          1,7,13,19,20,
          4,5,11,17,23,
          2,8,14,15,21]
   to_return=[]
   for i in range(len(index)):
       for x in range(len(s)):
           if index[i]==x:
               to_return.append(s[x])
   return(to_return)

def chi(s):
   def sf(find_list,set_main):
       return(set_main.index(find_list))
   sm=[[0,0],[1,0],[2,0],[3,0],[4,0],
       [0,1],[1,1],[2,1],[3,1],[4,1],
       [0,2],[1,2],[2,2],[3,2],[4,2],
       [0,3],[1,3],[2,3],[3,3],[4,3],
       [0,4],[1,4],[2,4],[3,4],[4,4]]
   to_return=[]
   for i in range(25):
       to_return.append(xo(s[i],and_2str(not_str(s[sf([(sm[i][0]+1)%5,sm[i][1]],sm)]),s[sf([(sm[i][0]+2)%5,sm[i][1]],sm)])))
   return(to_return)

def iota(s,i_r):
   def rc(t):
       def xor_bit(a,b):
           return('1' if ((a == '1') ^ (b == '1')) else '0')
       if t%255==0:
           return('1')
       r=['1','0','0','0','0','0','0','0']
       for i in range(1,(t%255)+1):
           r = ['0'] + r
           r[0] = xor_bit(r[0],r[8])
           r[4] = xor_bit(r[4],r[8])
           r[5] = xor_bit(r[5],r[8])
           r[6] = xor_bit(r[6],r[8])
           r = trunc(r,8)
       return(r[0])
   to_index=[]
   for x in range(24):
       to_check=''
       RC = ['0']*64
       for i in range(7):
           RC[(2**i)-1]=rc(i+(7*x))
       to_index.append(flip_string(str_concat(RC)))
   s[0]=xo(s[0],to_index[i_r])
   return(s)

def message_bit_return(string_input):
   bit_list=[]
   for i in range(len(string_input)):
       bit_list.append(bin_8bit(ord(string_input[i])))
   return(l_s(bit_list))

def processing(message_string):
   to_invert=L_P(message_bit_return(message_string),8)
   to_return=[]
   for i in range(len(to_invert)):
       to_return.append(flip_string(to_invert[i]))
   return(str_concat(to_return))

def sha_3(to_hash,digest_len):
   x = L_P(message_expansion(L_P(str_concat(message_processing(processing(to_hash),digest_len)),2)),64)
   for i in range(24):
       x = iota(chi(pi(rho(theta(x)))),i)
   to_flip=[]
   for i in range(digest_len/64):
       to_flip.append(x[i])
   to_convert=[]
   for i in range(len(to_flip)):
       to_convert.append(L_P(to_flip[i],8))
   bin_list=[]
   for i in range(len(to_convert)):
       bin_list.append(flip_string(to_convert[i]))
   bin_list=L_P(str_concat(bin_list),4)
   to_return=[]
   for i in range(len(bin_list)):
       to_return.append(hex_single(int(bin_list[i],2)))
   to_return=str_concat(to_return)
   return(to_return)

散列函式通常是通過將問題分成兩部分來設計的:

  1. 一個固定大小的核心函式。這在大多數雜湊中稱為壓縮函式,但在 Keccak 中稱為排列
  2. 域擴展器——一種使用固定大小核心函式來處理任意長度輸入的算法。在 SHA-2 等較舊的雜湊中,這是 Merkle-Damgård 構造;在 Keccak 是海綿結構

核心功能通常通過密碼分析來評估;密碼學家試圖在實踐中實際破解它。另一方面,域擴展器通常通過安全證明進行評估:密碼學家試圖找到一個數學論據,表明如果生成的散列函式不安全,則不是域擴展器的錯。

Bertoni、Daemen、Peeters 和 Assche 的論文“Cryptographic Sponge Functions”包含海綿結構的安全證明。他們使用兩種核心函式進行證明:

  1. **隨機變換:**任何隨機選擇的函式 $ b $ -bit 塊作為它的域和範圍。
  2. 隨機排列:在 $ b $ 位塊——即具有相同域和範圍的可逆函式。

他們得出結論(第 65 頁):

值得注意的是,使用隨機排列

$$ in the sponge construction’s security proof $$與使用隨機變換相比,會產生更好的 [隨機預言不可微分性] 約束。

原因非常技術性,但您的問題的答案是:海綿結構的安全證明告訴我們,使用排列作為其核心功能效果更好。

引用自:https://crypto.stackexchange.com/questions/42612