SHA-256算法和區塊鏈原理初探

組內技術分享的內容,目前網上相關資料不少,但讀起來都不太合本身的習慣,因而本身整理並編寫一篇簡潔並便於(本身)理解和分享的文章。
由於以前對密碼學沒有專門研究,本身的體會或理解會特別標註爲「我的理解」,請注意甄別,若有必要能夠自行查證。
閱讀前須要樹立一種觀點:大部分場景都是基於機率的大小而言的,好比SHA256安全性、區塊鏈不可更改性等。html

SHA-256算法

簡介

區塊鏈的基礎算法之一,在其中用於區塊hash計算方法。算法

是SHA-2下的一個算法標準,而SHA-2全稱安全散列算法2,即Secure Hash Algorithm 2,屬於SHA算法之一,是SHA-1的後繼者,一種密碼散列函數算法標準,由美國國家安全局研發,由美國國家標準與技術研究院(NIST)在2001年發佈。其下一共分六個不一樣的算法標準:SHA-22四、SHA-25六、SHA-38四、SHA-5十二、SHA-512/22四、SHA-512/256。數據庫

  • 我的理解: SHA-2沒有完整的安全證實,能夠認爲是一種實踐中產生的算法。

基本流程

  1. 將信息m補齊到長度對512取模爲448獲得m'
  2. 將信息m'尾部添加一個用64位表示的長度獲得m"
  3. 將信息m"按512位分割成n份,循環處理n次,最終得到一個256位的信息摘要m''',即爲所需的摘要值。

理論基礎

散列函數安全性

散列(hash)函數須要知足如下性質纔是安全的:
1. 不能從散列結果獲得原始信息,也就是密碼學中的原像問題。
2. 不能出現兩個不一樣的原始信息,可是hash結果同樣,即碰撞問題。
3. 最好由原信息的微小變更能讓hash結果面目全非,即雪崩效應。安全

  • 我的理解
    SHA-256的壓縮函數不能徹底知足這些性質,「安全」能夠看作是破解的難度大小的相對值,而非絕對值。即,不是絕對安全,可是大機率安全。
    • 對於第1條,SHA-256作了充分混淆,可是能不能找到原像?有可能,可是時間上不容許。舉例,一個長度是1的字符串,其SHA-256結果爲m。那麼遇到m,能夠猜想原像有多是1。「有可能」的緣由見第2條的理解。
    • 對於第2條,由於散列後的摘要長度是2^256,當有2^256+1個原始信息時,必定有重複,只是在這個尺度下碰撞概率很小。
    • 第3條實際上強制性很弱,須要大量的反例來證實。我尚未看到相關的反例。

Merkle-Damgard結構

SHA-256的壓縮函數知足Merkle-Damgard結構,此時能夠保證壓縮函數在知足散列函數安全性的前提下,在分組後作壓縮時仍能保證。關於Merkle-Damgard結構,能夠參考《密碼學原理與實踐》第四章及文末參考資料[2]。
  • 我的理解
    上一小節能夠看到SHA-256的壓縮函數的安全是機率性的,那麼能夠認爲SHA-256分組處理時,分組處理的安全機率≈原壓縮函數的安全機率。

僞碼

// 全部變量均爲無符號32位整型,計算時以2^32爲模
// 1. 變量初始化
// 1.1 初始化變量第一部分:前8個質數(2, 3, 5 ... 19)的平方根的前32位
h0 := 0x6a09e667
h1 := 0xbb67ae85
h2 := 0x3c6ef372
h3 := 0xa54ff53a
h4 := 0x510e527f
h5 := 0x9b05688c
h6 := 0x1f83d9ab
h7 := 0x5be0cd19

// 1.2 初始化變量第二部分:前64個質數(2, 3, ..., 311)的立方根的前32位
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, 0xc67178f2

// 2. 預處理

// 2.1 給原始消息m末尾增長(接續)一個二進制1
m0 = add1bit(m)

// 2.2 給m0末尾增長連續k個0,直到其長度對 512取模後餘數爲448
// k >= 0
m1 = add0bitsWhenMod512Equals448(m0)

// 2.3 給m1末尾增長64bit表示的m1的長度, 以大端表示
m2 = addLengthBigEndian(m1)

// 3. 將m1拆分紅l個以512bit爲長度的塊
sub_ms[l] = splitBy512bit(m1)
// 3.1 子塊處理
for(sub_m : sub_ms ) {
    // 對每一個512bit的子塊,進一步拆成16個32bit的子塊
    w[0...15] = splitBy32bit(sub_m)
    for(i from 16 to 63) {
        // 計算w[16]到w[63]
        //rightrotate 循環右移x位, rightshift右移x位高位補0
        s0 := (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] + s1
    }

    // 局部變量初始化
    a := h0
    b := h1
    c := h2
    d := h3
    e := h4
    f := h5
    g := h6
    h := h7

    // 子塊內主循環
    for i from 0 to 63 {
        s0 := (a rightrotate 2) xor (a rightrotate 13) xor(a rightrotate 22)
        maj := (a and b) xor (a and c) xor(b and c)
        t2 := s0 + maj
        s1 := (e rightrotate 6) xor (e rightrotate 11) xor(e rightrotate 25)
        ch := (e and f) xor ((not e) and g)
        t1 := h + s1 + ch + k[i] + w[i]
        h := g
        g := f
        f := e
        e := d + t1
        d := c
        c := b
        b := a
        a := t1 + t2
    }

    // 子塊結果更新到全局變量
    h0 := h0 + a
    h1 := h1 + b
    h2 := h2 + c
    h3 := h3 + d
    h4 := h4 + e
    h5 := h5 + f
    h6 := h6 + g
    h7 := h7 + h
}

// 3.2 最終的摘要結果計算, append即位拼接
digest = hash = h0 append h1 append h2 append h3 append h4 append h5 append h6 append h7

區塊鏈原理

一句話定義

去中心的分佈式數據庫。數據結構

數據結構

僞碼以下app

// 區塊與前個區塊相連。

/**
 * 區塊
 */
class Block {
    /** 區塊頭 */
    BlockHead head;

    /** 區塊體 */
    BlockBody body;
}

/**
 * 區塊頭
 */
class BlockHead {
    /** 生成時間 */
    Date gmtGenerate;

    /** 本區塊體的hash值 */
    String bodyHashCode;

    /** 上個區塊的hash值 */
    String prevHashCode;

    /** 難度係數 */
    int difficulty;

    /** 隨機數, 32位*/
    int nonce;
}

/**
 * 區塊體
 */
class BlockBody {
    /** 數據 */
    byte[] data;
}

區塊的不可修改性

區塊的hash=SHA256(區塊頭),區塊頭包含上一個區塊hash及本區塊體的hash。所以,當前區塊(不管區塊頭和區塊體)或上一個區塊發生變化,都會致使區塊的hash變化。這個關係能夠用公式表達爲:分佈式

blockHash = SHA256(block.header) = SHA256(prevBlockHash + block.body + other)

所以,修改一個區塊會致使斷鏈,必須連鎖地修改後續全部區塊。因爲後面會提到計算hash很慢的緣由,幾乎不可能,除非擁有全網51%計算能力——區塊鏈所以得名。函數

*__我的理解__爲何是全網51%計算的能力?即在全網中全部計算能力都在挖礦時,修改的速度超過建立的速度。假設當前全網只有1%的計算能力在挖礦,那麼你只要這1%的51%——全網的0.51%足矣。區塊鏈

挖礦——建立區塊原理

爲何稱爲「挖礦」?

全部節點都會建立新的區塊。爲了保證節點同步,區塊添加的速度不能太快。同步的速度由發明者中本聰設置爲平均10分鐘1次,即1小時6個。控制速度的方式是,建立區塊須要大量的計算才能得到當前塊的hash,此後才能添加新的塊。這個海量計算過程相似於從大量的沙子中獲取有用的一粒金子,所以被比喻爲「挖礦」。可是「金子」並不徹底是指這粒「合適的hash」,下文會提到。3d

難度係數與隨機值

只有符合要求的hash才被區塊連接受,這個要求是:hash < 常量targetMax / 當前區塊difficulty。

大部分計算的hash是不符合條件的。爲了使hash發生變化,須要改動區塊頭。爲了讓區塊頭產生變化,中本聰在區塊頭增長了隨機值Nonce。從0開始計算到2^32位找到Nonce具體值的過程,就是挖礦的過程,所以Nonce和對應的hash合起來纔是金子,而計算得到的hash只是須要作進一步確認金子的原石。

存在全部值都不是符合要求的Nonce的狀況,此時協議容許礦工改變區塊體,從新計算。

固然也存在符合條件的hash值有多個的狀況,爲了下降這種狀況的機率,能夠調節difficulty值。

建立時間平均化

根據當前的建立速度,能夠動態地更改difficulty的大小來調節,確保近似平均10分鐘1次。隨着運算能力提高,difficulty會愈來愈大,致使挖礦難度愈來愈高。

區塊鏈的分叉

若是同時提交兩個區塊,二者鏈接了同一個區塊,那麼區塊鏈採用哪個?

新節點老是採用最長的區塊鏈;哪一個分支先達到6個新區塊(「6次確認」),區塊鏈就會採用那條鏈。6次確認只須要1個小時就會完成。可見,取決於哪條分支擁有更多的計算速度。

被丟棄的短鏈,相關計算是徹底沒有價值的。在比特幣中是這樣,以太坊則會受到必定的補償。

應用場景

8年的持續運行證明了其可行性。可是代價是:10分鐘同步一次;大量無心義計算。所以使用場景有限:

  • 不存在全部節點信任的管理中心
  • 不要求實時寫入
  • 挖礦收益彌補自己成本

目前的應用:比特幣;電子證書等。

附:參考資料

  1. SHA-2定義及僞代碼
  2. SHA256算法的理論基礎(含SHA256與Merkle-Damgard結構關係簡單證實)
  3. 區塊鏈入門教程——阮一峯
相關文章
相關標籤/搜索