
什么是 SHA-256?
SHA-256(安全哈希算法 256 位)是 SHA-2 家族中的一种密码学哈希函数,由 NIST 于 2001 年标准化。它接受任意输入——一个字节、一个文件、整个数据库转储——并产生固定长度的 256 位(32 字节)输出,通常显示为 64 个十六进制字符。
输入: "hello"
输出: 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
输入: "hello."(多一个字符)
输出: 0d9b02b1b3cd52e9fcf5db5c5e38cfe97c97a4e8f0d4fba7fee7b5b5b5b60a00
尽管输入只有微小变化,第二个输出却完全不同——这就是雪崩效应,良好哈希函数的核心属性。

SHA-256 的核心属性
确定性: 相同输入始终产生相同输出。无需重新下载原始文件即可验证哈希。
单向性(抗原像性): 无法从哈希逆向推导出原始输入。给定输出,找到任何能产生该输出的输入都需要天文数字的计算量。
抗碰撞性: 找到两个不同输入但哈希输出相同的情况在计算上不可行。SHA-256 尚未发现实际碰撞(而 MD5 和 SHA-1 在这方面已被攻破)。
固定输出大小: 无论哈希单个字符还是 100GB 视频文件,输出始终是 256 位。
快速: 现代硬件上 SHA-256 每秒可计算数十亿次哈希。这对文件校验很好,但使其不适合密码哈希(应使用 bcrypt、Argon2 或 PBKDF2)。
SHA-256 的工作原理
SHA-256 使用 Merkle-Damgård 结构:
- 填充: 输入被填充为 512 位的倍数,附加一个 1 位、若干 0 位,然后是一个 64 位的长度字段
- 解析: 填充后的输入被划分为 512 位(64 字节)的块
- 压缩: 每个块经过 64 轮混合处理,使用从前 64 个素数的立方根的小数部分导出的常数
- 链接: 每个块的输出作为下一个块的初始状态输入
- 最终哈希: 最后一个块处理后的 256 位状态即为哈希值
这种设计意味着 SHA-256 是顺序的——处理块 N 依赖于块 N-1 的输出。
在代码中生成 SHA-256
JavaScript(浏览器和 Node.js)
// 浏览器 — Web Crypto API(内置,无依赖)
async function sha256(message) {
const encoder = new TextEncoder();
const data = encoder.encode(message);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
const hash = await sha256('hello world');
// => b94d27b9934d3e08a52e52d7da7dabfac484efe04294e576a55df5b2b1e63d4c
// Node.js — 内置 crypto 模块
const crypto = require('crypto');
const hash = crypto.createHash('sha256').update('hello world').digest('hex');
Python
import hashlib
# 哈希字符串
h = hashlib.sha256(b'hello world').hexdigest()
print(h) # b94d27b9934d3e08a52e52d7da7dabfac484efe04294e576a55df5b2b1e63d4c
# 哈希文件(流式处理,支持大文件)
def sha256_file(filepath):
h = hashlib.sha256()
with open(filepath, 'rb') as f:
for chunk in iter(lambda: f.read(8192), b''):
h.update(chunk)
return h.hexdigest()
print(sha256_file('/path/to/file'))

Shell(Linux/macOS)
# 哈希字符串
echo -n "hello world" | sha256sum
# 哈希文件
sha256sum filename.iso
# macOS
shasum -a 256 filename.iso
Go
import (
"crypto/sha256"
"fmt"
)
h := sha256.Sum256([]byte("hello world"))
fmt.Printf("%x\n", h)
常见使用场景
1. 文件完整性校验
软件发布者会随下载提供 SHA-256 校验和。下载后,验证文件未被损坏或篡改:
sha256sum ubuntu-24.04-desktop-amd64.iso
# 应与 ubuntu.com 上的校验和一致
2. HMAC 签名
HMAC-SHA256(基于哈希的消息认证码)验证消息是否由持有共享密钥的人发送。用于 GitHub webhooks、AWS Signature V4、JWT HS256。
const crypto = require('crypto');
// 签名
const signature = crypto
.createHmac('sha256', 'your-secret-key')
.update(JSON.stringify(payload))
.digest('hex');
// 验证(时序安全比较)
const expected = crypto
.createHmac('sha256', 'your-secret-key')
.update(JSON.stringify(incomingPayload))
.digest('hex');
const isValid = crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expected, 'hex')
);

3. 内容可寻址存储
Git 使用 SHA-1(并正在迁移到 SHA-256)来寻址对象——每个提交、树和 blob 都有一个唯一标识其内容的哈希。相同内容始终获得相同哈希,从而实现去重。
4. 数据去重
在存储上传文件之前,先对其哈希,检查该哈希是否已存在于存储中。如果存在,则存储引用而非副本。
def store_file(data: bytes) -> str:
file_hash = hashlib.sha256(data).hexdigest()
if not storage.exists(file_hash):
storage.save(file_hash, data)
return file_hash # 返回哈希作为文件 ID
5. 确定性 ID
SHA-256 从内容中创建稳定、抗碰撞的 ID——适用于缓存、去重和分布式系统,无需中央计数器即可派生 ID。
6. 承诺方案
在一方承诺某个值但不透露它的协议中(例如拍卖、抛硬币),他们发布 sha256(value + nonce)。之后他们透露值和 nonce;任何人都可以验证承诺。
SHA-256 与其他哈希函数对比
| 函数 | 输出 | 速度 | 碰撞状态 | 密码哈希 |
|---|---|---|---|---|
| MD5 | 128 位 | 非常快 | 已攻破 | ❌ 切勿使用 |
| SHA-1 | 160 位 | 快 | 已攻破(SHAttered) | ❌ 切勿使用 |
| SHA-256 | 256 位 | 快 | 安全 | ❌ 太快 |
| SHA-512 | 512 位 | 快 | 安全 | ❌ 太快 |
| bcrypt | 可变 | 设计上慢 | 不适用 | ✅ 用于密码 |
| Argon2id | 可变 | 慢 + 内存硬 | 不适用 | ✅ 推荐 |
切勿使用 SHA-256 存储密码。 其速度是弱点——攻击者使用 GPU 每秒可尝试数十亿个密码。应使用 bcrypt、Argon2id 或 PBKDF2。
常见问题
两个文件能否有相同的 SHA-256 哈希?
理论上可以(生日悖论),但尚未发现实际碰撞。任意两个随机输入的概率为 1/2^128——实际上不可能。
SHA-256 和 SHA-2 相同吗? SHA-2 是一个家族。SHA-256 是其中一个成员(256 位输出)。其他成员:SHA-224、SHA-384、SHA-512、SHA-512/224、SHA-512/256。
应该使用 SHA-256 还是 SHA-512? SHA-512 输出更大,在 64 位系统上稍快。为兼容性和标准用例选择 SHA-256。当 256 位抗碰撞性不够时(例如,非常长期文档的数字签名),使用 SHA-512。
SHA-256 用于比特币吗? 是的。比特币在挖矿(工作量证明)和交易 ID 中使用双重 SHA-256(double-SHA256)。
→ 使用 Hash Text Tool 即时生成 SHA-256 和其他哈希。