eezTool / eeztool.github.io

My blog, see the Issues.
0 stars 0 forks source link

九浅一深SHA-256原理详解 #1

Open eezTool opened 5 years ago

eezTool commented 5 years ago

1. SHA-256 是什么?

简单解释: 狭义的说,SHA-256 是这样一种计算方法:

一个很大的文件 A,经过 SHA-256 计算后,会变成一个很短的字符串 B。 只要文件 A 有任何一丁点改动,算出来的字符串 B 就几乎完全不同。

这样一来,字符串 B 就好像一个「指纹」,通过对比「指纹」,我们就能知道文件 A 是否被篡改 / 伪造。

问题举例:

假设我从网上下载了某个软件 A,我担心 A 不是官方原版,而是被人篡改过的版本。 怎么证明 A 就是原版文件呢?看文件名?文件时间?文件大小?这些都很容易伪造。

解决方法:

  1. 官方计算出原版文件 A0 的 SHA-256 计算结果 B0,并张贴在可靠网站上;
  2. 我们在自己的计算机上,计算出来文件 A 的计算结果 B;
  3. 对比 B0 和 B 这两个很短的字符串,就能知道 A0 和 A 是不是一样了。

下面介绍 SHA-256 的计算过程。

2. SHA-256 的计算过程

2.1 预处理

  1. 我们知道,任何计算机上的文件、数据都是一串 0101111011 这样的二进制数据,我们先将这个二进制数据拿出来;

  2. 在原数据后添加 1 个「1」;

  3. 在「1」后添加 k 个 0,使得数据的长度除以 512 后余 448(0≤k≤511); (注意这个填充是必须要进行的,如果原数据除以 512 后恰好余 448,那就补充 1 个 1 和 511 个 0。)

  4. 继续,将原数据的长度写为 64 位,并添加在后面; 注意该 64 位数据的编码方式为「大端整数」。

  5. 由于 448 + 64 = 512,所以经过上面的处理后,新数据正好被 512 整除。

  6. 预处理后的结果如图: ffsfsf

2.2 分块处理

分块处理的整个流程图:

410075_1

具体步骤如下:

  1. 将数据分块儿,每块儿长度 512-bit;

  2. 继续分,将上面 512-bit 长度的块儿分成 32-bit 长度的「word」,一共有 16 个 word,记为:w[0],w[1],…,w[15];

  3. 将 16 个 word 扩展为 64 个 word,这涉及到如下一些繁琐但其实并不难的数学运算。

    设一个序号标记 i,引入两个中间变量 s0,s1,计算如下: (右移(rightshift)、循环右移(rightrotate)等计算方法可见后文「基础计算知识」部分)

    1. s0 = (w[i-15] rightrotate 7) xor (w[i-15] rightrotate 18) xor (w[i-15] rightshift 3)
    2. s1 = (w[i-2] rightrotate 17) xor (w[i-2] rightrotate 19) xor (w[i-2] rightshift 10)
    3. w[i] = s0 + s1 + w[i-16] + w[i-7]

    当 i =16 时,我们就算出了 w[16];当 i =17,就算出了 w[17]。以此类推,一直到 w[63]。

  4. 注意到以上步骤中加入了一系列混乱且不可逆的运算,这样就使得原数很难被反推出来。

2.3 对第 1 块儿的 64 个 word 进行循环计算

  1. 下面开始处理第一个块儿 M0。首先,引入上面得到的 64 个 word:w[0,1,2,...,63]。

  2. 再额外引入 8 个初始值:

    h0(0) = 01101010000010011110011001100111 h1(0) = 10111011011001111010111010000101 h2(0) = 00111100011011101111001101110010 h3(0) = 10100101010011111111010100111010 h4(0) = 01010001000011100101001001111111 h5(0) = 10011011000001010110100010001100 h6(0) = 00011111100000111101100110101011 h7(0) = 01011011111000001100110100011001

    它们是前 8 个质数(2,3,…,19)的平方根在 2 进制下的小数部分的前 32 位。

  3. 引入 8 个初始变量,它们分别等于上面的初始值:

    a0 = h0(0) b0 = h1(0) c0 = h2(0) d0 = h3(0) e0 = h4(0) f0 = h5(0) g0 = h6(0) h0 = h7(0)

  4. 再额外引入 64 个初始值 k[0..63](具体数值就不列了)。 它们是前 64 个质数(2,3,…,311)的平方根在 2 进制下的小数部分的前 32 位。

  5. 取出上面得到的 a0 ~ h0,w(0),k(0),进行如下计算。 (图中白色的数是我们取出来的已知的数,蓝色的数为中间值,黄色的数为得到的新数): image

具体的算式如下:

(循环右移(rightrotate)、与(and)等计算方法可见后文「基础计算知识」部分))

计算 x:

  1. Σa = (a0 rightrotate 2) xor (a0 rightrotate 13) xor (a0 rightrotate 22)
  2. maj = (a0 and b0) xor (a0 and c0) xor (b0 and c0)
  3. x = Σa + maj

计算 y:

  1. Σe = (e0 rightrotate 6) xor (e0 rightrotate 11) xor (e0 rightrotate 25)
  2. ch = (e0 and f0) xor ((not e0) and g0)
  3. y = Σe + ch + w(0) + k(0) + h0

计算最终的新数: a1 = x + y b1 = a0 c1 = b0 d1 = c0 e1 = d0 + y f1 = e1 g1 = f1 h1 = g1

  1. 注意到上面的计算式中包含了 w(0),并有着一系列混乱的计算,这样就使得 w(0) 的信息包含在了新数中,并且几乎无法通过逆运算还原出来。

  2. 继续计算:这次取出 w(1),k(1),以及上一步中得到的 a1 ~ h1,回到第 5 步,进行同样的运算。 以此类推,进行 64 次运算,我们就将 w(0) ~ w(63) 这 64 个数全部计算了一遍,得到了 a64 - h64,如下图所示: (图中的 k(0) ~ k(63) 是第 4 步中引入的常数,目的是增加计算的混乱程度)

    410123_0
  3. 将最终结果与 h0(0) ~ h7(0) 相加,得到 h0(1) ~ h7(1):

    h0(1) = h0(0) + a64 h1(1) = h1(0) + b64 h2(1) = h2(0) + c64 h3(1) = h3(0) + d64 h4(1) = h4(0) + e64 h5(1) = h5(0) + f64 h6(1) = h6(0) + g64 h7(1) = h7(0) + h64

  4. 以上就是对第一个块内的 64 个 word 进行计算并得到最终数据的方法。

2.4 将 n 个块进行同样的运算,得到最终结果

  1. 对第 2 个块,以上一步中得到的 h0(1) ~ h7(1) 为初始变量:

    a0 = h0(1) b0 = h1(1) c0 = h2(1) d0 = h3(1) e0 = h4(1) f0 = h5(1) g0 = h6(1) h0 = h7(1)

  2. 进行同样的 64 次计算,并将结果与 h0(1) ~ h7(1) 相加,得到 h0(2) ~ h7(2);

  3. 以此类推,对 n 个块都进行同样的计算,便得到最终结果,如图所示: fsfsdfs

  4. 将最后的 h0(n) ~ h7(n) 串联起来,便是最终的 SHA-256 计算结果了。

3. 基础计算知识

3.1 右移 n(rightshift n)

将二进制数据往右移动 n 位,移出去的 n 位丢掉不要了,移空的 n 位补 0。举例:将 8 位数字 11111111 右移 3 位:

原数据: 11111111
rightshift 3: 00011111

3.2 循环右移 n(rightrotate n)

将二进制数据往右移动 n 位,移空的 n 位用移出去的 n 位补上。举例:将 8 位数字 11111100 循环右移 3 位:

原数据: 11111100
rightrotate 3: 10011111

3.3 异或(xor,即 exclusive OR,数学符号为 ⊕)

把两个数对齐,比较每一个位,如果相同,则 xor 结果为 0,如果不同,则 xor 结果为 1。举例:

第 1 个数: 1 0 1 1 0 1 0 1
第 2 个数: 0 0 1 0 1 0 1 1
xor 结果: 1 0 0 1 1 1 1 0

3.4 与(and)

把两个数对齐,比较每一个位,如果都为 1,则 and 结果为 1,只要有一个不是 1,结果就是 0。举例:

第 1 个数: 1 0 1 1 0 1 0 1
第 2 个数: 0 0 1 0 1 0 1 1
and 结果: 0 0 1 0 0 0 0 1

3.5 非(not)

将二进制数每 1 位都取反,即 1 变成 0,0 变成 1。举例:

原数据: 1 0 1 1 0 1 0 1
not 结果: 0 1 0 0 1 0 1 0

4. 其他 SHA-256 知识点

  1. SHA-256 是一种哈希(Hash)算法,本质上都是将一串数据进行计算而得到另一串固定大小的数据。无论是软件、视频、文档还是字符串,只要是数据,都可以进行计算。这些数据又可称为「明文」,计算的结果可称为「密文」。

  2. SHA-256 的密文长度为 32 个字节,1 字节 = 8 位,32 * 8 = 256,所以叫 SHA-256。

  3. 著名的哈希算法还有 MD5,SHA-1,SHA-3 等,其中 MD5,SHA-1 已被证实为不够安全。

  4. 优秀加密哈希的部分特点:

    • 唯一性:同样的明文,每次计算得到的密文都是不变的;
    • 计算快:给出明文 A,能很快的算出密文 B;
    • 相关性低
      1. 给你一堆明文 A0,A1,A2,…,然后告诉你其中一个的计算结果是 B。你茫然地看着 B,几乎无法猜出对应的 A ──除非把所有明文都算一遍。
      2. 只要明文 A 有任何一丁点改动,算出来的 B 就几乎完全不同(雪崩效应,avalanche effect)。
    • 抗碰撞(collision):
      1. 有个明文 A0 的密文是 B0。我伪造了这个明文,称为 A1,我肯定非常想让 A1 的计算结果也等于 B0。结果我发现,无论怎么构造 A1,都几乎无法得到 B0 这个结果──这样就基本防止了伪造。
      2. 我想设计一对 A0,A1,使得计算出来的 B0 和 B1 相等,或者相似(只有少数几位不同)。即使没有任何限制,也几乎无法设计出来。
      3. 我想设计出某个「靓号」密文,例如末尾有 8 个 8。即使没有任何限制,也几乎无法设计出来。

5. SHA-256 相关名词解释

SHA-256:一种 SHA-2 算法,某串数据经过 SHA-256 算法计算后,会变成一串长度为 256 位的数据,因此称为 SHA-256。

SHA-2:安全散列算法 2(Secure Hash Algorithm 2),属于 SHA 算法之一,与 SHA-1 基本相似,是其改进版。由于 SHA-1 已被证实为不够安全,因此目前主流采用 SHA-256。

SHA:安全散列算法(Secure Hash Algorithm),是一种目前应用较为广泛的散列函数家族,包括:

散列函数:Hash function,又称散列算法、哈希函数,是一种将数据串 A 进行计算而得到另一串固定大小的数据 B 的方法。得到的数据 B 通常较小,B 又被称为“散列值”(hash values,hash codes,hash sums,digests,hashes)、“指纹”、“摘要”等。

资料来源:

sd016808 commented 3 months ago

寫得很好,簡單易懂,再麻煩修正以下的小錯誤:

1. 再额外引入 64 个初始值 k[0..63](具体数值就不列了)。 它们是前 64 个质数(2,3,…,311)的平方根在 2 进制下的小数部分的前 32 位。

這部分有錯,應該是立方根不是平方根

H用的是平方根 K用的是立方根

2. f1 = e1 g1 = f1 h1 = g1 應該修正為 f1 = e0 g1 = f0 h1 = g0