SHA256算法原理及其实现
SHA家族的五個算法,分別是SHA-1、SHA-224、SHA-256、SHA-384,和SHA-512,由美國國家安全局(NSA)所規劃,并由美國國家規范與技能研究院(NIST)發布。
該算法是美國的政府規范算法,后四者有時并稱為SHA-2。
SHA在很多安全協定中廣為運用,包含TLS和SSL、PGP、SSH、S/MIME和IPsec,曾被視為是MD5(更早之前被廣為運用的雜湊函數)的后繼者。 但SHA-1的安全性現在被密碼學家嚴峻質疑,有學者曾經爆出NSA在SHA-1留下的后門。
雖然至今尚未出現對SHA-2有效的攻擊,但是它的算法跟SHA-1基本上仍然相似,因此有些人開始發展其他替代的雜湊算法。
學習區塊鏈,總是無法避開各種加密算法,因為各種加密算法在實現區塊鏈當中的各個環節都有著不可替代的作用。這里介紹一下在比特幣挖礦以及merkle樹當中被大量使用的鼎鼎大名的SHA256算法。
SHA-2 族算法簡介
一個n位的哈希函數就是一個從任意長的消息到n位哈希值的映射,一個n位的加密哈希函數就是一個單向的、避免碰撞的n位哈希函數。這樣的函數是目前在數字簽名和密碼保護當中極為重要的手段。
當前比較流行的哈希函數主要有128位的MD4和MD5和160位的SHA-1,今天介紹的SHA-2族有著更多位的輸出哈希值,破解難度更大,能夠提高更高的安全性。
SHA-2,名稱來自于安全散列算法2(英語:Secure Hash Algorithm 2)的縮寫,一種密碼散列函數算法標準,由美國國家安全局研發,由美國國家標準與技術研究院(NIST)在2001年發布。屬于SHA算法之一,是SHA-1的后繼者。其下又可再分為六個不同的算法標準,包括了:SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。
這些變體除了生成摘要的長度、循環運行的次數等一些細微差異之外,基本結構是一致的。本文主要講一講SHA256。
SHA256原理詳解
概述
對于任意長度的消息,SHA256都會產生一個256位的哈希值,稱作消息摘要。這個摘要相當于是個長度為32個字節的數組,通常有一個長度為64的十六進制字符串來表示,其中1個字節=8位,一個十六進制的字符的長度為4位。
來看一個具體的例子:
BlockChain這句話經過哈希函數SHA256后得到的哈希值為:
3a6fed5fc11392b3ee9f81caf017b48640d7458766a8eb0382899a605b41f2b9總體上,HSA256與MD4、MD5以及HSA-1等哈希函數的操作流程類似,待哈希的消息在繼續哈希計算之前首先要進行以下兩個步驟:
- 對消息進行補位處理,是的最終的長度是512位的倍數,然后
- 以512位為單位對消息進行分塊為
?
?消息區塊將進行逐個處理:從一個固定的初始哈希開始,進行以下序列的計算:
?
其中是SHA256的壓縮函數,是mod?加法,即將兩個數字加在一起,如果對取余,?是消息區塊的哈希值.
算法詳細描述
SHA256的壓縮函數主要對512位的消息區塊和256位的中間哈希值進行操作,本質上,它是一個通過將消息區塊為密鑰對中間哈希值進行加密的256位加密算法。 因此,為了描述SHA256算法,有以下兩方面的組件需要描述:
- SHA256壓縮函數
- SHA256消息處理流程
以下的描述當中所使用到的標記如下:
- : 按位異或
- : 按位與
- : 按位或
- : 補位
- : 相加以后對求余
- : 右移n位
- : 循環右移n位
以上所有的操作都是針對32位字節.
常量初始化
初始哈希值取自自然數中前面8個素數(2,3,5,7,11,13,17,19)的平方根的小數部分, 并且取前面的32位. 下面舉個例子:?小數部分約為0.414213562373095048, 而其中
于是, 質數2的平方根的小數部分取前32位就對應0x6a09e667.
如此類推, 初始哈希值由以下8個32位的哈希初值構成:
SHA256算法當中還使用到64個常數, 取自自然數中前面64個素數的立方根的小數部分的前32位, 如果用16進制表示, 則相應的常數序列如下:
428a2f98 71374491 b5c0fbcf e9b5dba5 3956c25b 59f111f1 923f82a4 ab1c5ed5 d807aa98 12835b01 243185be 550c7dc3 72be5d74 80deb1fe 9bdc06a7 c19bf174 e49b69c1 efbe4786 0fc19dc6 240ca1cc 2de92c6f 4a7484aa 5cb0a9dc 76f988da 983e5152 a831c66d b00327c8 bf597fc7 c6e00bf3 d5a79147 06ca6351 14292967 27b70a85 2e1b2138 4d2c6dfc 53380d13 650a7354 766a0abb 81c2c92e 92722c85 a2bfe8a1 a81a664b c24b8b70 c76c51a3 d192e819 d6990624 f40e3585 106aa070 19a4c116 1e376c08 2748774c 34b0bcb5 391c0cb3 4ed8aa4a 5b9cca4f 682e6ff3 748f82ee 78a5636f 84c87814 8cc70208 90befffa a4506ceb bef9a3f7 c67178f2消息預處理
在計算消息的哈希摘要之前需要對消息進行預處理:
- 對消息進行補碼處理: 假設消息的二進制編碼長度為位. 首先在消息末尾補上一位"1", 然后再補上個"0", 其中為下列方程的最小非負整數
舉個例子, 以消息"abc"為例顯示補位的過程.
a,b,c對應的ASCII碼和二進制編碼分別如下:
原始字符 ASCII碼 二進制編碼 a 97 01100001 b 98 01100010 c 99 01100011因此, 原始信息"abc"的二進制編碼為:01100001 01100010 01100011, 第一步補位, 首先在消息末尾補上一位"1", 結果為:?01100001 01100010 01100011 1; 然后進行第二步的補位, 因為, 可以得到, 在第一步補位后的消息后面再補423個"0", 結果如下:
最后還需要在上述字節串后面繼續進行補碼, 這個時候補的是原消息"abc"的二進制長度的64位二進制表示形式, 補完以后的結果如下:
最終補完以后的消息二進制位數長度是512的倍數.
這里需要注意的兩點是不管原來的消息長度是多少, 即使長度已經滿足對512取模后余數是448,補位也必須要進行,這時要填充512位. 另外, 考慮到最后要將消息長度轉換為64位二進制編碼, 因此, 長度的必須小于, 絕大多數情況, 這個足夠大了.
- 將補碼處理后的消息以512位為單位分塊為:?, 其中第個消息塊的前32位表示為:?, 后面32位為:?, 以此類推, 最后32位的消息塊可表示為:?. 我們采用Big endian約定對數據進行編碼, 即認為第一個字節是最高位字節, 因此, 對于每一個32位字節, 最最左邊的比特是最大的比特位.
摘要計算主循環
哈希計算算法如下:
- ?(?= 補碼后消息塊個數)
- 用第個中間哈希值來對?進行初始化, 當時, 就使用初始化哈希, 即:
- 應用SHA256壓縮函數來更新
-
- 計算(具體定義如下)
-
- 計算第個中間哈希值
- 為最終需要的哈希。
邏輯函數定義
SHA256算法當中所使用到的6個邏輯函數如下:每個函數都對32位字節進行操縱,并輸出32位字節。
擴展消息塊通過以下方式進行計算:
圖形表示
SHA256壓縮函數的圖形表示如下:
擴展消息塊的求解算法可以表示如下:
SHA偽代碼
Note 1: All variables are 32 bit unsigned integers and addition is calculated modulo 232 Note 2: For each round, there is one round constant k[i] and one entry in the message schedule array w[i], 0 ≤ i ≤ 63 Note 3: The compression function uses 8 working variables, a through h Note 4: Big-endian convention is used when expressing the constants in this pseudocode,and when parsing message block data from bytes to words, for example,the first word of the input message "abc" after padding is 0x61626380Initialize hash values: (first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19): h0 := 0x6a09e667 h1 := 0xbb67ae85 h2 := 0x3c6ef372 h3 := 0xa54ff53a h4 := 0x510e527f h5 := 0x9b05688c h6 := 0x1f83d9ab h7 := 0x5be0cd19Initialize array of round constants: (first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311): k[0..63] :=0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2Pre-processing (Padding): begin with the original message of length L bits append a single '1' bit append K '0' bits, where K is the minimum number >= 0 such that L + 1 + K + 64 is a multiple of 512 append L as a 64-bit big-endian integer, making the total post-processed length a multiple of 512 bitsProcess the message in successive 512-bit chunks: break message into 512-bit chunks for each chunkcreate a 64-entry message schedule array w[0..63] of 32-bit words(The initial values in w[0..63] don't matter, so many implementations zero them here)copy chunk into first 16 words w[0..15] of the message schedule arrayExtend the first 16 words into the remaining 48 words w[16..63] of the message schedule array:for i from 16 to 63s0 := (w[i-15] rightrotate 7) xor (w[i-15] rightrotate 18) xor (w[i-15] rightshift 3)s1 := (w[i- 2] rightrotate 17) xor (w[i- 2] rightrotate 19) xor (w[i- 2] rightshift 10)w[i] := w[i-16] + s0 + w[i-7] + s1Initialize working variables to current hash value:a := h0b := h1c := h2d := h3e := h4f := h5g := h6h := h7Compression function main loop:for i from 0 to 63S1 := (e rightrotate 6) xor (e rightrotate 11) xor (e rightrotate 25)ch := (e and f) xor ((not e) and g)temp1 := h + S1 + ch + k[i] + w[i]S0 := (a rightrotate 2) xor (a rightrotate 13) xor (a rightrotate 22)maj := (a and b) xor (a and c) xor (b and c)temp2 := S0 + majh := gg := ff := ee := d + temp1d := cc := bb := aa := temp1 + temp2Add the compressed chunk to the current hash value:h0 := h0 + ah1 := h1 + bh2 := h2 + ch3 := h3 + dh4 := h4 + eh5 := h5 + fh6 := h6 + gh7 := h7 + hProduce the final hash value (big-endian): digest := hash := h0 append h1 append h2 append h3 append h4 append h5 append h6 append h7SHA256代碼實現
下面是基于上述偽代碼用go語言對SHA256進行的實現.
package mainimport ("encoding/binary" )func wikiSha256(message []byte) [32]byte {//初始哈希值h0 := uint32(0x6a09e667)h1 := uint32(0xbb67ae85)h2 := uint32(0x3c6ef372)h3 := uint32(0xa54ff53a)h4 := uint32(0x510e527f)h5 := uint32(0x9b05688c)h6 := uint32(0x1f83d9ab)h7 := uint32(0x5be0cd19)//計算過程當中用到的常數k := [64]uint32{0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2}padded := append(message, 0x80)if len(padded) % 64 < 56 {suffix := make([]byte, 56 - (len(padded) % 64))padded = append(padded, suffix...)} else {suffix := make([]byte, 64 + 56 - (len(padded) % 64))padded = append(padded, suffix...)}msgLen := len(message) * 8bs := make([]byte, 8)binary.BigEndian.PutUint64(bs, uint64(msgLen))padded = append(padded, bs...)broken := [][]byte{};for i := 0; i < len(padded) / 64; i++ {broken = append(broken, padded[i * 64: i * 64 + 63])}//主循環for _, chunk := range broken {w := []uint32{}for i := 0; i < 16; i++ {w = append(w, binary.BigEndian.Uint32(chunk[i * 4:i * 4 + 4]))}w = append(w, make([]uint32, 48)...)//W消息區塊處理for i := 16; i < 64; i++ {s0 := rightRotate(w[i - 15], 7) ^ rightRotate(w[i - 15], 18) ^ (w[i - 15] >> 3)s1 := rightRotate(w[i - 2], 17) ^ rightRotate(w[i - 2], 19) ^ (w[i - 2] >> 10)w[i] = w[i - 16] + s0 + w[i - 7] + s1}a := h0b := h1c := h2d := h3e := h4f := h5g := h6h := h7//應用SHA256壓縮函數更新a,b,...,hfor i := 0; i < 64; i++ {S1 := rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25)ch := (e & f) ^ ((^e) & g)temp1 := h + S1 + ch + k[i] + w[i]S0 := rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22)maj := (a & b) ^ (a & c) ^ (b & c)temp2 := S0 + majh = gg = ff = ee = d + temp1d = cc = bb = aa = temp1 + temp2}h0 = h0 + ah1 = h1 + bh2 = h2 + ch3 = h3 + dh4 = h4 + eh5 = h5 + fh6 = h6 + gh7 = h7 + h}hashBytes := [][]byte{iToB(h0), iToB(h1), iToB(h2), iToB(h3), iToB(h4), iToB(h5), iToB(h6), iToB(h7)}hash := []byte{}hashArray := [32]byte{}for i := 0; i < 8; i ++ {hash = append(hash, hashBytes[i]...)}copy(hashArray[:], hash[0:32])return hashArray }func iToB(i uint32) []byte {bs := make([]byte, 4)binary.BigEndian.PutUint32(bs, i)return bs }//循環右移函數 func rightRotate(n uint32, d uint) uint32 {return (n >> d) | (n << (32 - d)) }總結
以上是生活随笔為你收集整理的SHA256算法原理及其实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: LOJ#6282. 数列分块入门 6
- 下一篇: 使用offsetof对结构体指针偏移操作