SHA 系列
SHA(Secure Hash Algorithm)是一族单向哈希函数,安全性高于 MD5,是目前主流的哈希算法。 搜索关键字:SHA1、SHA256、sha-256、CryptoJS.SHA256、createHash('sha256')、crypto.subtle.digest 用魔法数字区分 SHA 版本:SHA-1 初始向量为 0x67452301(与 MD5 相同!),通过后续常量区分;SHA-256 特征为 0x6a09e667,SHA-512 为 0x6a09e667f3bcc908,全局搜索这些常量可快速定位。 Hook crypto.s
官方文档:https://csrc.nist.gov/publications/detail/fips/180/4/final
适用场景:JS 逆向中识别、提取、还原 SHA 系列哈希逻辑
SHA(Secure Hash Algorithm)是一族单向哈希函数,安全性高于 MD5,是目前主流的哈希算法。
各版本对比
| 算法 | 输出位数 | 输出长度(hex) | 安全性 | 说明 |
|---|---|---|---|---|
| SHA-1 | 160 位 | 40 字符 | 已不推荐 | 仍大量存在于老系统 |
| SHA-224 | 224 位 | 56 字符 | 较好 | SHA-256 的截断版 |
| SHA-256 | 256 位 | 64 字符 | 良好 | 最常用,推荐 |
| SHA-384 | 384 位 | 96 字符 | 很好 | SHA-512 的截断版 |
| SHA-512 | 512 位 | 128 字符 | 极好 | 性能略低于 SHA-256 |
Python 实现
import hashlib
# SHA-1
def sha1(data: str) -> str:
return hashlib.sha1(data.encode('utf-8')).hexdigest()
# SHA-224
def sha224(data: str) -> str:
return hashlib.sha224(data.encode('utf-8')).hexdigest()
# SHA-256
def sha256(data: str) -> str:
return hashlib.sha256(data.encode('utf-8')).hexdigest()
# SHA-384
def sha384(data: str) -> str:
return hashlib.sha384(data.encode('utf-8')).hexdigest()
# SHA-512
def sha512(data: str) -> str:
return hashlib.sha512(data.encode('utf-8')).hexdigest()
# 示例输出
text = 'hello'
print('SHA-1: ', sha1(text)) # aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d
print('SHA-256:', sha256(text)) # 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
print('SHA-512:', sha512(text)) # 9b71d224bd62f3785d96d46ad3ea3d73...
# 对字节数据哈希
def sha256_bytes(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
# 输出为 Base64
import base64
def sha256_b64(data: str) -> str:
raw = hashlib.sha256(data.encode()).digest() # 返回 bytes,非 hex
return base64.b64encode(raw).decode()
# 通用封装
def sha_hash(data: str, algorithm: str = 'sha256', output: str = 'hex') -> str:
h = hashlib.new(algorithm, data.encode('utf-8'))
if output == 'hex':
return h.hexdigest()
elif output == 'base64':
return base64.b64encode(h.digest()).decode()
elif output == 'bytes':
return h.digest()
JavaScript 实现
使用 CryptoJS
const CryptoJS = require('crypto-js');
const text = 'hello';
// SHA-1
const sha1 = CryptoJS.SHA1(text).toString();
// SHA-256
const sha256 = CryptoJS.SHA256(text).toString();
// SHA-512
const sha512 = CryptoJS.SHA512(text).toString();
// SHA-224
const sha224 = CryptoJS.SHA224(text).toString();
// SHA-384
const sha384 = CryptoJS.SHA384(text).toString();
// 输出为 Base64
const sha256Base64 = CryptoJS.SHA256(text).toString(CryptoJS.enc.Base64);
// 对 WordArray(字节数组)哈希
const wordArray = CryptoJS.enc.Utf8.parse(text);
const hash = CryptoJS.SHA256(wordArray).toString();
console.log('SHA-1: ', sha1);
console.log('SHA-256:', sha256);
console.log('SHA-512:', sha512);
使用 Node.js 内置 crypto
const crypto = require('crypto');
function shaHash(data, algorithm = 'sha256', encoding = 'hex') {
return crypto.createHash(algorithm).update(data, 'utf8').digest(encoding);
}
console.log(shaHash('hello', 'sha1')); // 40位 hex
console.log(shaHash('hello', 'sha256')); // 64位 hex
console.log(shaHash('hello', 'sha512')); // 128位 hex
console.log(shaHash('hello', 'sha256', 'base64')); // Base64 输出
// 对 Buffer(字节数组)哈希
const buf = Buffer.from('hello', 'utf8');
const hashFromBuffer = crypto.createHash('sha256').update(buf).digest('hex');
使用 Web Crypto API(浏览器原生)
// 异步,只能在 HTTPS 环境使用
async function sha256(message) {
const msgBuffer = new TextEncoder().encode(message);
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
// 支持的算法:'SHA-1', 'SHA-256', 'SHA-384', 'SHA-512'
async function shaHash(data, algorithm = 'SHA-256') {
const encoded = new TextEncoder().encode(data);
const hashBuffer = await crypto.subtle.digest(algorithm, encoded);
return Array.from(new Uint8Array(hashBuffer))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
// 输出为 Base64
async function sha256Base64(message) {
const msgBuffer = new TextEncoder().encode(message);
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
return btoa(String.fromCharCode(...new Uint8Array(hashBuffer)));
}
逆向中常见变体
| 变体 | 说明 |
|---|---|
| 标准 SHA-256 | 64位小写 hex |
| 大写输出 | .toString().toUpperCase() |
| Base64 输出 | 对哈希结果 Base64 编码 |
| 哈希后再哈希 | sha256(sha256(data)) |
| 加盐 | sha256(salt + data + salt) |
| 带时间戳 | sha256(data + timestamp) |
快速识别
| 结果长度(hex) | 对应算法 |
|---|---|
| 40 | SHA-1 |
| 56 | SHA-224 |
| 64 | SHA-256 |
| 96 | SHA-384 |
| 128 | SHA-512 |
搜索关键字:SHA1、SHA256、sha-256、CryptoJS.SHA256、createHash('sha256')、crypto.subtle.digest
最佳实践
用魔法数字区分 SHA 版本:SHA-1 初始向量为 0x67452301(与 MD5 相同!),通过后续常量区分;SHA-256 特征为 0x6a09e667,SHA-512 为 0x6a09e667f3bcc908,全局搜索这些常量可快速定位。
Hook crypto.subtle.digest 捕获原生调用:浏览器原生 SHA 通常通过 crypto.subtle.digest 异步调用,在页面加载前注入 Hook:
const _digest = crypto.subtle.digest.bind(crypto.subtle)
crypto.subtle.digest = function(algorithm, data) {
console.log('[SHA]', algorithm, new TextDecoder().decode(data))
return _digest(algorithm, data)
}
SHA-1 与 SHA-256 通过输出长度区分:SHA-1 输出 40 字符十六进制,SHA-256 输出 64 字符,SHA-512 输出 128 字符;若目标签名长度不对应,检查是否做了 Base64 输出或截取。
标准 SHA 验证用 Python hashlib:hashlib.sha256(b'input').hexdigest() 是最快的交叉验证手段;若字符串含非 ASCII,先用 input.encode('utf-8') 与 JS 侧的 TextEncoder 对齐。
魔改 SHA 先对比空字符串输出:SHA-256('') 标准结果为 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855,与目标对比定位魔改点(初始向量、轮常量、字节序)。
常见陷阱
陷阱:SHA-1 与 MD5 混淆(魔法数字相同)
现象: 找到 0x67452301 特征常量,但按 MD5 还原后结果不对。
原因: SHA-1 和 MD5 共享同一组初始哈希向量,仅凭 0x67452301 无法区分;SHA-1 输出 160 位,MD5 输出 128 位。
解决: 查看输出长度(40 字符 = SHA-1,32 字符 = MD5);查找 0x5a827999、0x6ed9eba1(SHA-1 轮常量)来最终确认。
陷阱:crypto.subtle.digest 返回 ArrayBuffer 而非字符串
现象: 拿到 crypto.subtle.digest 结果后直接用 +"" 或 toString() 得到 [object ArrayBuffer]。
原因: Web Crypto API 返回的是 Promise<ArrayBuffer>,需要先 await 再转为十六进制字符串。
解决: [...new Uint8Array(await crypto.subtle.digest('SHA-256', data))].map(b => b.toString(16).padStart(2,'0')).join('')
陷阱:HMAC-SHA256 被误认为纯 SHA256
现象: 同样的输入用标准 SHA-256 计算结果不同。
原因: 签名逻辑在 SHA-256 外加了 HMAC 密钥,输入数据和密钥一起参与哈希计算,结果必然不同。
解决: 搜索 createHmac、HmacSHA256、sign 关键字,找到密钥参数后改用 HMAC 计算。