SHA256 详解
目录
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,结果就是 12. 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 可能在任何加密系统的寿命期内都是安全的
