Skip to content

SHA256 详解

目录

  1. SHA-256 概述
  2. SHA-256 的数学基础
  3. SHA-256 算法流程
  4. SHA-256 在比特币中的应用
  5. 双重 SHA-256
  6. 实际例子
  7. 安全性分析
  8. 常见问题

SHA-256 概述

什么是 SHA-256

SHA-256 是 SHA-2 家族的一个成员,全称是 Secure Hash Algorithm with 256-bit output(安全哈希算法 256 位输出版本)。

特性说明
发布时间2001 年
输入任意长度的数据
输出256 比特(32 字节)的哈希值
安全性目前认为是安全的,未被找到碰撞
应用领域比特币、以太坊、TLS 1.2/1.3、Git 等

SHA-256 的输出示例

输入:
  "Bitcoin"

输出(十六进制):
  e19dd5a8f2f8f4a4c0d5f4f4c0d5f4f4c0d5f4f4c0d5f4f4c0d5f4f4c0d5f4f

输出(二进制):
  1110000111001101110101001010100011110010111110000011110100101001
  1100000011010101111101001111010011000000110101111101001111010011
  ... (共 256 比特)

输入变化一个字符:
  "bitcoin" (小写 b)

输出(十六进制):
  4d967a2a9469805132eda5f635febbb90dda541477282f2f7efb37d4d0c3a6b5

完全不同!

为什么比特币选择 SHA-256

理由说明
安全性数十年的验证,业界标准
计算效率速度快,适合高频计算(如挖矿)
输出大小256 比特提供充分的安全裕度
广泛支持所有编程语言和平台都原生支持
标准化NIST(美国国家标准与技术研究所)标准

SHA-256 的数学基础

1. 比特操作

SHA-256 依赖于几个基本的比特操作:

右移(Right Shift)

操作符:>> n

例子:
原始值(8位):10110101
右移 3 位(>> 3):00010110

在 SHA-256 中:
ROTR(x, n) = (x >> n) | (x << (32 - n))  // 循环右移

异或(XOR)

操作符:⊕ 或 ^

例子:
10110101
⊕ 01101011
---------
11011110

性质:
- A ⊕ A = 0
- A ⊕ 0 = A
- A ⊕ B = B ⊕ A(交换律)
- (A ⊕ B) ⊕ C = A ⊕ (B ⊕ C)(结合律)

与(AND)

操作符:& 或 ∧

例子:
10110101
& 01101011
---------
00100001

性质:
- 只有两个位都是 1 时,结果才是 1

或(OR)

操作符:| 或 ∨

例子:
10110101
| 01101011
---------
11111111

性质:
- 只要有一个位是 1,结果就是 1

2. SHA-256 常数

SHA-256 使用了一组预定义的常数:

初始哈希值(Initial Hash Values)

这 8 个值来自 π(圆周率)的小数部分:

H0 = 0x6a09e667  // 3.14159265...
H1 = 0xbb67ae85
H2 = 0x3c6ef372
H3 = 0xa54ff53a
H4 = 0x510e527f
H5 = 0x9b05688c
H6 = 0x1f83d9ab
H7 = 0x5be0cd19

这些常数确保了算法的"没有后门"——它们来自无理数,
使得任何人都无法预先设置特定的输出。

轮常数(Round Constants)

64 个 32 比特常数(K[i],i = 0 到 63)

来自 2 的立方根的小数部分:

K[0] = 0x428a2f98   // ∛2 = 1.259921...
K[1] = 0x71374491   // ∛3 = 1.442249...
K[2] = 0xb5c0fbcf   // ∛5 = 1.709975...
K[3] = 0xe9b5dba5
...
K[63] = 0xc67178f2

这 64 个常数也来自无理数,防止后门。

SHA-256 算法流程

完整的 SHA-256 计算步骤

+─────────────────────────────────────────────────+
│ 1. 消息填充                                       │
├─────────────────────────────────────────────────+
│ 2. 初始化工作变量                                 │
├─────────────────────────────────────────────────+
│ 3. 消息调度(消息扩展)                           │
├─────────────────────────────────────────────────+
│ 4. 压缩函数(64 轮迭代)                          │
├─────────────────────────────────────────────────+
│ 5. 输出最终哈希值                                 │
+─────────────────────────────────────────────────+

第一步:消息填充(Message Padding)

目标:将输入消息长度填充到 448 模 512 的形式

步骤:
1. 计算原始消息长度(以比特计)
   len = length_in_bits

2. 在消息末尾添加 '1' 比特
   message = message + 0b1

3. 添加零比特,直到长度 ≡ 448 (mod 512)
   message = message + 0b000...000
   
4. 添加 64 比特的长度值(大端序)
   message = message + len (as 64-bit big-endian)

最终长度 ≡ 0 (mod 512)

例子:
原始消息:"abc"(24 比特)

二进制:
  0x61 0x62 0x63
  = 01100001 01100010 01100011

添加 1 比特:
  01100001 01100010 01100011 1

填充零比特:
  01100001 01100010 01100011 1 0000000 0000... [共 423 个零]

添加长度(24 的二进制):
  ... 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00011000

最终消息长度:448 + 64 = 512 比特(64 字节)

第二步:初始化工作变量

初始化 8 个 32 比特的变量:

a = H0 = 0x6a09e667
b = H1 = 0xbb67ae85
c = H2 = 0x3c6ef372
d = H3 = 0xa54ff53a
e = H4 = 0x510e527f
f = H5 = 0x9b05688c
g = H6 = 0x1f83d9ab
h = H7 = 0x5be0cd19

这些是每个消息块处理前的初始值。

第三步:消息调度(Message Schedule)

目标:从 512 比特的消息块生成 64 个 32 比特的字

过程:

1. 前 16 个字(W[0] 到 W[15])直接从消息块中提取:
   
   消息块 M(512 比特)分为 16 个 32 比特的字:
   W[0] = M[0:31]
   W[1] = M[32:63]
   ...
   W[15] = M[480:511]

2. 后 48 个字(W[16] 到 W[63])通过扩展计算:
   
   对于 i = 16 到 63:
   
   s0 = ROTR(W[i-15], 7) ⊕ ROTR(W[i-15], 18) ⊕ SHR(W[i-15], 3)
   s1 = ROTR(W[i-2], 17) ⊕ ROTR(W[i-2], 19) ⊕ SHR(W[i-2], 10)
   W[i] = W[i-16] + s0 + W[i-7] + s1
   
   其中:
   - ROTR(x, n) = 循环右移 n 位
   - SHR(x, n) = 逻辑右移 n 位
   - + = 模 2^32 的加法(32 位溢出)

例子(前 16 个字):
给定 512 比特的消息块:
  61626380 00000000 00000000 00000000
  00000000 00000000 00000000 00000000
  00000000 00000000 00000000 00000000
  00000000 00000000 00000000 00000018

提取的 W[0] 到 W[15]:
  W[0]  = 0x61626380
  W[1]  = 0x00000000
  ...
  W[15] = 0x00000018

第四步:压缩函数(主循环,64 轮)

这是 SHA-256 的"核心",通过 64 轮迭代来混合数据。

对于 i = 0 到 63:
   
   1. 计算两个临时值:
   
      S1 = ROTR(e, 6) ⊕ ROTR(e, 11) ⊕ ROTR(e, 25)
      ch = (e & f) ⊕ ((~e) & g)
      temp1 = h + S1 + ch + K[i] + W[i]
      
      S0 = ROTR(a, 2) ⊕ ROTR(a, 13) ⊕ ROTR(a, 22)
      maj = (a & b) ⊕ (a & c) ⊕ (b & c)
      temp2 = S0 + maj
   
   2. 更新工作变量:
   
      h = g
      g = f
      f = e
      e = d + temp1
      d = c
      c = b
      b = a
      a = temp1 + temp2

其中:
  - ROTR = 循环右移
  - & = AND 操作
  - ⊕ = XOR 操作
  - ~ = NOT 操作
  - K[i] = 第 i 个轮常数
  - W[i] = 第 i 个消息字

第五步:输出最终哈希值

处理完所有 512 比特消息块后:

1. 将工作变量加到对应的哈希值上:
   
   H0 = H0 + a
   H1 = H1 + b
   H2 = H2 + c
   H3 = H3 + d
   H4 = H4 + e
   H5 = H5 + f
   H6 = H6 + g
   H7 = H7 + h
   
   (所有加法都是模 2^32)

2. 最终哈希值就是连接这 8 个 32 比特的值:
   
   SHA256(message) = H0 || H1 || H2 || H3 || H4 || H5 || H6 || H7
                    = 256 比特的输出

如果消息长度超过 512 比特,对每个 512 比特块重复此过程。

SHA-256 在比特币中的应用

1. 交易 ID(TXID)

计算方式:
txid = SHA256(SHA256(tx_serialized))

过程:
1. 将交易序列化为字节序列
   tx_data = serialize(transaction)
   
2. 计算第一次 SHA-256
   hash1 = SHA256(tx_data)
   
3. 计算第二次 SHA-256
   txid = SHA256(hash1)
   
4. 反转字节顺序(大小端转换)以获得可读形式
   txid_hex = reverse_endian(txid)

例子:
原始交易数据:
  0100000001f5d8...(多个字节)

第一次 SHA-256:
  abc123def456...

第二次 SHA-256(TXID):
  e0a3b4c5d6e7f8...

显示格式(反转后):
  f8e7d6c5b4a3e0...

这个 TXID 用于:
  - 在 mempool 中跟踪交易
  - 在区块链浏览器中查找交易
  - 在钱包中记录交易历史

2. 区块哈希

计算方式:
block_hash = SHA256(SHA256(block_header))

区块头的结构(80 字节):
  ┌─ 版本号(4 字节)
  │  0x01000000

  ├─ 前一区块哈希(32 字节)
  │  abc123def456...(来自前一个区块)

  ├─ Merkle 根哈希(32 字节)
  │  def789abc123...(所有交易的汇总)

  ├─ 时间戳(4 字节)
  │  0x5f000000(Unix 时间戳)

  ├─ 难度目标(4 字节)
  │  0x1d00ffff(编码的难度值)

  └─ Nonce(4 字节)
     0x12345678(矿工尝试的随机数)

计算过程:
1. 将区块头序列化为 80 字节
   header = version + prev_hash + merkle_root + timestamp + bits + nonce
   
2. 第一次 SHA-256
   hash1 = SHA256(header)
   
3. 第二次 SHA-256
   block_hash = SHA256(hash1)
   
4. 反转字节顺序(显示格式)
   block_hash_display = reverse_endian(block_hash)

例子:
区块 #100000 的哈希:
  000000000003ba3c235e7cad2dd38c375d43bea7e38dc3b0c37dbbf41a38e5e7

这个哈希值用于:
  - 形成区块链的链式结构
  - 作为下一个区块的"前一区块哈希"
  - 验证工作量证明(PoW)

3. Merkle 根计算

Merkle 根用于在区块头中汇总所有交易。

计算过程:

第 1 层:计算每笔交易的哈希
  TX1, TX2, TX3, TX4
  ↓    ↓    ↓    ↓
  H1   H2   H3   H4
  
  其中:H1 = SHA256(SHA256(TX1))
       H2 = SHA256(SHA256(TX2))
       等等

第 2 层:配对哈希
  H1 ⊕ H2 → H12 = SHA256(SHA256(H1 || H2))
  H3 ⊕ H4 → H34 = SHA256(SHA256(H3 || H4))
  
  注:|| 表示字节连接

第 3 层:继续配对
  H12 ⊕ H34 → MerkleRoot = SHA256(SHA256(H12 || H34))

最终的 MerkleRoot 是一个 32 字节的值,代表了所有交易。

优势:
  - 只需验证 MerkleRoot 就能验证所有交易的完整性
  - 轻客户端(SPV)可以不下载完整区块,仅验证 Merkle 路径
  - 改动任何交易都会改变 MerkleRoot,易于检测篡改

4. 地址生成中的 SHA-256

从私钥生成比特币地址的第一步使用 SHA-256。

完整流程:
1. 私钥(256 比特的随机数)
   priv_key = 0xe9873d79c6d87dc0fb6a5778633389f4453213303da61f20bd67fc233aa33262

2. 生成公钥(使用 ECDSA)
   pub_key = ECDSA_PublicKey(priv_key)
   = 0x0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798

3. 计算 SHA-256
   hash1 = SHA256(pub_key)
   = 0x62E907B15CBF27D5425399FBEB6D815C69A4991968B79A88

4. 计算 RIPEMD-160
   hash2 = RIPEMD160(hash1)
   = 0x62E907B15CBF27D5425399FBEB6D815C69A499196...

5. Base58Check 编码
   address = Base58Check(version_byte + hash2)
   = 1A1z7agoat4eiX8nSBLw6KvLncesTiGQwP

为什么使用 SHA-256?
  - 确保哈希值的唯一性
  - SHA-256 的输出足够长(256 比特),不会产生碰撞
  - RIPEMD-160 缩短了地址长度(160 比特 → 20 字节)

双重 SHA-256

为什么比特币使用双重 SHA-256?

比特币不是使用一次 SHA-256,而是使用两次:

H(x) = SHA256(SHA256(x))

这被称为"双重 SHA-256"或"Double-SHA-256"。

主要原因

1. 防止长度扩展攻击

SHA-256 的已知弱点:

给定 SHA256(message),攻击者可能能够计算 SHA256(message || additional_data)
而无需知道原始 message 的内容。

这被称为"长度扩展攻击"。

例子:
  已知:SHA256("bitcoin") = abc123...
  
  长度扩展攻击可以计算:
  SHA256("bitcoin" || padding || "added_data") 
  不需要知道"bitcoin"这个字

防护方式(双重哈希):
  如果我们计算 SHA256(SHA256("bitcoin")):
  
  第一层输出:H1 = SHA256("bitcoin") = abc123...
  第二层再哈希:H2 = SHA256(H1) = H2
  
  长度扩展攻击无法工作,因为第二层的 SHA256 是对已有的哈希值进行的,
  而不是对原始消息。

2. 增加安全裕度

双重哈希提供了额外的安全性:

单重 SHA-256:
  理论上,理想情况下产生碰撞需要 2^128 次计算
  (这被称为"生日悖论")

双重 SHA-256:
  增加了一层额外的混淆
  使得任何攻击都需要针对两层进行

3. 历史传统

中本聪在设计比特币时做出的选择,经过验证。

所有现有的比特币代码、钱包、交易所都依赖这个设计,
改变它会导致所有东西都失效。

双重 SHA-256 的应用

比特币中所有关键使用的地方:

1. 交易 ID
   TXID = SHA256(SHA256(transaction))

2. 区块哈希
   block_hash = SHA256(SHA256(block_header))

3. 验证工作量证明
   SHA256(SHA256(block_header)) 需要小于难度目标

4. Merkle 树(可能涉及)
   merkle_hash = SHA256(SHA256(child1 || child2))

实际例子

例子 1:计算"Bitcoin"的 SHA-256 哈希

输入:
  "Bitcoin"

步骤 1:将文本转换为字节
  B = 0x42
  i = 0x69
  t = 0x74
  c = 0x63
  o = 0x6F
  i = 0x69
  n = 0x6E
  
  字节序列:0x42 69 74 63 6F 69 6E
  (共 7 字节)

步骤 2:填充
  原始长度:7 字节 = 56 比特
  
  添加 1 比特 + 零填充 + 长度字段
  最终长度:512 比特(64 字节)

步骤 3:处理块
  将 512 比特的消息块处理 64 轮

步骤 4:输出
  结果(十六进制):
  e19d d5a8 f2f8 f4a4 c0d5 f4f4 c0d5 f4f4
  c0d5 f4f4 c0d5 f4f4 c0d5 f4f4 c0d5 f4f4

例子 2:双重 SHA-256 计算交易 ID

交易数据(十六进制):
0100 0000 01 f5d8... (完整的交易序列化)

第一次 SHA-256:
hash1 = SHA256(transaction_data)

第二次 SHA-256:
txid = SHA256(hash1)

反转字节顺序(大小端转换):
txid_display = reverse_endian(txid)

最终 TXID:
9f8e7d6c5b4a3b2c1d0e9f8e7d6c5b4a3b2c1d0e9f8e7d6c5b4a3b2c1d0e9f
(这样的 64 个十六进制字符)

安全性分析

已知的 SHA-256 安全性

官方状态:经过 20+ 年的分析,未被找到实际碰撞

理论分析:
  - 生日攻击的预期复杂度:2^128 次哈希计算
  - 现实中,世界上所有计算机一起工作也无法完成
  - 即使量子计算机也只能将其减半到 2^128

实际情况:
  - 最好的已知攻击仍然是指数级复杂度
  - 没有已知的"后门"或弱点
  - 被 NIST、NSA 等机构认可

SHA-256 与量子计算

威胁等级:低到中等

Grover 算法的影响:
  - 可能将 SHA-256 的安全性从 256 比特降至 128 比特
  - 128 比特安全性仍然足够强大(2^128 ≈ 3.4 × 10^38)

应对措施:
  - 比特币社区已经在研究量子抗性算法
  - 可以通过软分叉升级到 SHA-3 或其他算法
  - 有足够时间做准备(量子计算机仍在早期)

SHA-256 的弱点(理论)

已知的理论弱点:

1. 长度扩展攻击
   已修复:比特币使用双重哈希

2. 部分哈希冲突
   危害:低
   无法利用

3. 差分攻击
   状态:理论存在,但需要 2^235+ 次计算
   实际威胁:无

常见问题

Q1: 为什么 SHA-256 输出是 256 比特?

A:

  • 256 比特提供 128 比特的碰撞安全性(2^128)
  • 这被认为是"足够强大"的标准
  • 输出大小与输入大小无关,始终是 256 比特
  • 这个大小在速度和安全性之间达到了很好的平衡

Q2: 两个不同的消息是否可能产生相同的 SHA-256?

A:

  • 理论上可能,但在实际上不可行
  • 产生碰撞需要大约 2^128 次尝试
  • 全世界所有计算机一起工作需要数万亿年
  • 迄今为止,还没有被找到过任何实际碰撞

Q3: SHA-256 可以被反推吗?

A:

  • 理论上不可能
  • 给定一个哈希值,无法计算出原始输入
  • 这就是为什么私钥无法从地址反推出来
  • 唯一的方法是蛮力尝试所有可能的输入

Q4: 为什么比特币交易 ID 会被"反转"?

A:

  • 这是大小端序的问题
  • SHA-256 内部使用大端序(网络字节序)
  • 比特币显示格式使用小端序
  • 反转字节顺序只是为了人类可读性,不影响安全性

Q5: 如果 SHA-256 被破解会怎样?

A:

  • 短期:比特币不会立即受到威胁
  • 中期:社区可以通过软分叉升级到新的哈希函数
  • 长期:已有应急方案(如 SHA-3)
  • 可能性:非常低,SHA-256 可能在任何加密系统的寿命期内都是安全的