> ## Content Index
> Fetch the complete content index at: https://blog.vercanti.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# SHA 系列
- URL: https://blog.vercanti.com/sha-xi-lie/
- Published: 2026-08-28T14:35:04.000Z
- Updated: 2026-08-28T14:58:00.000Z
- Description: 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
- Author: yellowdog
- Tags: js逆向, 加密算法

> 官方文档：<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 实现

```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

```javascript
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

```javascript
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（浏览器原生）

```javascript
// 异步，只能在 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：

```javascript
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 计算。

---

## 参见

[MD5](https://blog.vercanti.com/md5/)  
[HMAC](https://blog.vercanti.com/hmac/)  
[魔法数字速查](https://blog.vercanti.com/jia-mi-suan-fa-mo-fa-shu-zi-su-cha/)  
[代码片段大全](https://blog.vercanti.com/js-ni-xiang-hook-yu-dai-ma-pian-duan-da-quan/)