339 lines
9.8 KiB
Markdown
339 lines
9.8 KiB
Markdown
# RC4 加密与密钥链技术文档
|
||
|
||
---
|
||
|
||
## 概述
|
||
|
||
本文档详细描述了一个基于 **RC4 流加密算法** 和动态密钥生成机制(`KeyChain`)的 Python 实现。该系统支持数据加解密、自动密钥轮换、时间敏感的密钥管理,并具备一定的容错能力以应对时钟偏差。
|
||
|
||
主要特性包括:
|
||
|
||
- 使用 RC4 算法进行流加密
|
||
- 引入随机盐值(salt)和 SHA-1 密钥扩展增强安全性
|
||
- 支持 Base64 编码输出
|
||
- 动态密钥生成(基于时间窗口)
|
||
- 容忍一定时间偏移的解密尝试(前向/后向周期补偿)
|
||
|
||
---
|
||
|
||
## 依赖模块
|
||
|
||
```python
|
||
import time
|
||
import datetime
|
||
import random
|
||
import base64
|
||
from hashlib import sha1
|
||
```
|
||
|
||
> 注:`random` 模块当前未使用,可能是预留或冗余导入。
|
||
|
||
---
|
||
|
||
## 核心类说明
|
||
|
||
### 1. `RC4` 类 —— RC4 加密器
|
||
|
||
#### 初始化方法:`__init__(self, data_coding='utf8')`
|
||
|
||
初始化 RC4 加密实例。
|
||
|
||
| 参数 | 类型 | 描述 |
|
||
|--------------|--------|------|
|
||
| `data_coding` | str | 数据编码格式,默认为 `'utf8'`,用于字符串与字节之间的转换 |
|
||
|
||
##### 属性:
|
||
- `bcoding`: 内部字节编码,固定为 `'iso-8859-1'`(确保字节范围兼容)
|
||
- `dcoding`: 外部数据编码(默认 UTF-8)
|
||
- `salt`: 固定盐值 `b'AFUqx9WZuI32lnHk'`(16 字节),用于密钥强化
|
||
|
||
---
|
||
|
||
#### 私有方法:`_crypt(self, data: bytes, key: bytes) -> bytes`
|
||
|
||
执行标准 RC4 加密/解密操作。
|
||
|
||
##### 参数:
|
||
- `data`: 待处理的字节数据
|
||
- `key`: 密钥(bytes)
|
||
|
||
##### 返回:
|
||
- 加密或解密后的字节串(经 `'iso-8859-1'` 编码)
|
||
|
||
##### 算法流程:
|
||
1. 初始化长度为 256 的 S-box。
|
||
2. 使用密钥调度算法(KSA)打乱 S-box。
|
||
3. 使用伪随机生成算法(PRGA)逐字节异或加密。
|
||
|
||
> ⚠️ 注意:此实现中返回的是 `.join(chr(...))` 后再 `.encode('iso-8859-1')`,需注意字符边界问题。
|
||
|
||
---
|
||
|
||
#### 方法:`encode_bytes(self, bdata: bytes, key: bytes) -> bytes`
|
||
|
||
对字节数据执行带盐值的加密。
|
||
|
||
##### 流程:
|
||
1. 计算密钥摘要:`sha1(key + self.salt).digest()`
|
||
2. 使用上述密钥对 `bdata` 执行 `_crypt`
|
||
3. 将原始 salt 前缀附加到密文前(共 16 字节)
|
||
|
||
##### 返回:
|
||
- `salt + encrypted_data`(bytes)
|
||
|
||
---
|
||
|
||
#### 方法:`encode(self, data: str/bytes, key: str, encode=base64.b64encode) -> str`
|
||
|
||
高层加密接口,支持字符串输入和最终编码。
|
||
|
||
##### 参数:
|
||
- `data`: 明文(字符串或字节)
|
||
- `key`: 密钥(字符串)
|
||
- `encode`: 可选编码函数(如 `base64.b64encode`),设为 `None` 则不编码
|
||
|
||
##### 行为:
|
||
- 若 `data` 是字符串,则按 `dcoding` 编码为字节
|
||
- 调用 `encode_bytes` 加密
|
||
- 若指定 `encode` 函数,则对结果进行编码(如 Base64)
|
||
- 返回编码后的字符串(UTF-8 解码)
|
||
|
||
> ✅ 示例:
|
||
> ```python
|
||
> rc = RC4()
|
||
> code = rc.encode("hello", "mykey")
|
||
> ```
|
||
|
||
---
|
||
|
||
#### 方法:`decode_bytes(self, data: bytes, key: bytes) -> bytes`
|
||
|
||
解密由 `encode_bytes` 生成的数据。
|
||
|
||
##### 步骤:
|
||
1. 提取前 16 字节作为 salt(但实际未使用!应为 bug?)
|
||
2. 使用 `sha1(key + self.salt)` 生成密钥
|
||
3. 对剩余部分调用 `_crypt` 解密
|
||
|
||
> ❗⚠️ Bug 提示:虽然提取了 salt,但在密钥生成中仍使用固定的 `self.salt`,而非从输入读取的 salt。这导致无法正确处理不同 salt 的情况。
|
||
|
||
##### 返回:
|
||
- 解密后的原始数据(bytes)
|
||
|
||
---
|
||
|
||
#### 方法:`decode(self, data: str/bytes, key: str, decode=base64.b64decode) -> str`
|
||
|
||
高层解密接口。
|
||
|
||
##### 参数:
|
||
- `data`: 已加密并可选编码过的数据
|
||
- `key`: 解密密钥
|
||
- `decode`: 解码函数(默认 Base64)
|
||
|
||
##### 行为:
|
||
- 若 `data` 是字符串,先用 `dcoding` 编码成字节
|
||
- 若 `decode` 存在,先解码(如 Base64 解码)
|
||
- 调用 `decode_bytes` 解密
|
||
- 结果以 `dcoding` 解码为字符串返回
|
||
|
||
> ✅ 示例:
|
||
> ```python
|
||
> text = rc.decode(code, "mykey")
|
||
> ```
|
||
|
||
---
|
||
|
||
### 2. `KeyChain` 类 —— 时间同步密钥链
|
||
|
||
提供基于时间窗口的动态密钥管理和自动密钥匹配功能,适用于分布式环境下的安全通信。
|
||
|
||
#### 初始化:`__init__(seed_str, crypter=None, keylen=23, period=600, threshold=60, time_delta=0)`
|
||
|
||
| 参数 | 类型 | 默认值 | 说明 |
|
||
|-------------|----------|-----------|------|
|
||
| `seed_str` | str/bytes | - | 种子字符串,用于派生密钥 |
|
||
| `crypter` | RC4 实例 | None | 自定义加密器,若无则创建默认 RC4 |
|
||
| `keylen` | int | 23 | 生成密钥的字符长度 |
|
||
| `period` | int | 600 | 时间窗口周期(秒),即每 10 分钟换一次密钥 |
|
||
| `threshold` | int | 60 | 容差阈值(秒),判断是否接近周期边界 |
|
||
| `time_delta` | int | 0 | 时间偏移量(可用于调试或校准) |
|
||
|
||
##### 内部属性:
|
||
- `keypool`: 缓存已生成的密钥 `{indicator: key}`
|
||
- `timezone`: 设置为 GMT 时区
|
||
- `indicator`: 当前时间所属的时间段起点(如每 600 秒一个区块)
|
||
|
||
---
|
||
|
||
#### 方法:`get_timestamp(self) -> int`
|
||
|
||
获取当前 Unix 时间戳(减去 `time_delta`)
|
||
|
||
---
|
||
|
||
#### 方法:`get_indicator(ts=None) -> int`
|
||
|
||
计算时间指示器(所在周期的起始时间)
|
||
|
||
例如:
|
||
- `period=600` → 每 10 分钟一个周期
|
||
- `ts=1712345678` → indicator = `(1712345678 // 600) * 600`
|
||
|
||
##### 返回:
|
||
- 当前时间所属周期的起始时间戳
|
||
|
||
---
|
||
|
||
#### 方法:`genKey(indicator) -> bytes`
|
||
|
||
根据 `indicator` 从 `seed_str` 生成唯一密钥。
|
||
|
||
##### 算法逻辑:
|
||
1. 若缓存中已有对应密钥,直接返回
|
||
2. 否则使用循环算法从 `seed_str` 中按规则选取字符构造密钥:
|
||
- 初始值 `v = indicator`
|
||
- 每次取 `j = v % keylen` 作为索引
|
||
- 从 `seed_str[j]` 取字符追加
|
||
- 更新 `v = v - (j + k1)*m + keylen`
|
||
- 直到生成 `keylen` 长度的密钥
|
||
3. 缓存密钥,并清理过期条目(早于 `indicator - period` 的)
|
||
|
||
> 🔐 安全性:密钥依赖时间和种子,难以预测
|
||
|
||
---
|
||
|
||
#### 方法:`encode_bytes(bdata: bytes) -> bytes`
|
||
|
||
使用当前时间对应的密钥加密数据。
|
||
|
||
##### 流程:
|
||
1. 获取当前 `indicator`
|
||
2. 生成对应密钥 `key`
|
||
3. 构造明文:`key + bdata`(将密钥本身也加密传输)
|
||
4. 使用 `crypter.encode_bytes(data, key)` 加密
|
||
|
||
> 📦 输出结构:`salt + encrypted(key + bdata)`
|
||
|
||
---
|
||
|
||
#### 方法:`decode_bytes(data: bytes) -> bytes or None`
|
||
|
||
智能解密:尝试用当前、上一周期、下一周期的密钥解密。
|
||
|
||
##### 步骤:
|
||
1. 获取当前 `indicator` 和密钥
|
||
2. 尝试解密
|
||
3. 验证解密后数据前缀是否等于密钥(完整性校验)
|
||
4. 如果失败且接近周期底部(即将切换),尝试上一周期密钥
|
||
5. 如果接近顶部(刚过切换点),尝试下一周期密钥
|
||
|
||
##### 返回:
|
||
- 成功:解密后的原始数据(不含密钥头)
|
||
- 失败:`None`
|
||
|
||
---
|
||
|
||
#### 方法:`encode(text: str) -> bytes`
|
||
|
||
加密字符串版本,内部转为 UTF-8 字节后调用 `encode_bytes`
|
||
|
||
---
|
||
|
||
#### 方法:`decode(data: bytes) -> str or None`
|
||
|
||
解密并返回 UTF-8 字符串;若失败返回 `None`
|
||
|
||
---
|
||
|
||
## 辅助函数
|
||
|
||
### `password(pwdtxt: str, key=pwdkey) -> str or None`
|
||
|
||
使用固定密钥对密码文本加密并验证。
|
||
|
||
##### 步骤:
|
||
1. 加密 `pwdtxt`
|
||
2. 立即解密验证一致性
|
||
3. 一致则返回加密结果(Base64 编码字符串),否则返回 `None`
|
||
|
||
> ✅ 用途:确保加密过程可靠
|
||
|
||
---
|
||
|
||
### `unpassword(code: str, key=pwdkey) -> str or None`
|
||
|
||
解密由 `password()` 生成的密文。
|
||
|
||
##### 返回:
|
||
- 明文字符串 或 `None`
|
||
|
||
---
|
||
|
||
## 使用示例
|
||
|
||
```python
|
||
if __name__ == '__main__':
|
||
data = b"231r3 feregrenerjk gkht324g8924gnfw ;ejkvwkjerv"
|
||
key = b'123456'
|
||
|
||
rc4 = RC4()
|
||
kc = KeyChain('in the heaven, we are equal', rc4)
|
||
|
||
print("原始数据:", data)
|
||
|
||
# 加密
|
||
encoded_data = kc.encode_bytes(data)
|
||
print("加密后数据:", encoded_data, "长度:", len(encoded_data))
|
||
|
||
# 解密
|
||
decoded_data = kc.decode_bytes(encoded_data)
|
||
print("解密数据:", decoded_data)
|
||
print("解密成功:", decoded_data == data)
|
||
```
|
||
|
||
---
|
||
|
||
## 安全性分析
|
||
|
||
| 特性 | 评估 |
|
||
|------|------|
|
||
| **RC4 算法** | 已知存在弱点(如偏差输出),不推荐用于高安全场景 |
|
||
| **Salt 固定** | `self.salt` 固定可能导致重放攻击风险 |
|
||
| **密钥派生** | 自定义算法非标准,缺乏密码学证明 |
|
||
| **密钥包含在明文中加密** | 有一定防篡改作用,但仍可能被暴力破解 |
|
||
| **时间同步机制** | 适合短期令牌、API 认证等场景 |
|
||
| **Base64 支持** | 方便文本传输 |
|
||
|
||
> ✅ 推荐用途:轻量级认证令牌、会话加密、临时凭证
|
||
> ❌ 不推荐:金融交易、长期存储加密
|
||
|
||
---
|
||
|
||
## 已知问题与改进建议
|
||
|
||
| 问题 | 描述 | 建议 |
|
||
|------|------|-------|
|
||
| ❗ Salt 未真正参与密钥派生 | `decode_bytes` 忽略传入 salt,始终用固定 salt | 应使用传入 salt 生成密钥 |
|
||
| ⚠️ `_crypt` 返回方式不安全 | 使用 `chr()` 可能超出 ASCII 范围引发错误 | 改为直接构建字节数组 |
|
||
| ⚠️ RC4 已过时 | 存在已知漏洞(如 WEP 攻击) | 替换为 AES-CTR 或 ChaCha20 |
|
||
| 🔒 自定义密钥生成算法 | 非标准化,可能存在碰撞或弱密钥 | 使用 HMAC-SHA256 等标准 KDF |
|
||
| 💤 时间依赖性强 | 严重依赖系统时间准确性 | 增加强时间验证或 NTP 校准提示 |
|
||
|
||
---
|
||
|
||
## 总结
|
||
|
||
本模块实现了一套基于 RC4 和时间窗口密钥链的安全通信原型,具备以下优点:
|
||
|
||
- 简洁易集成
|
||
- 支持自动密钥轮换
|
||
- 具备时间容忍机制
|
||
- 可用于短生命周期数据保护
|
||
|
||
建议在低风险场景中使用,并考虑升级至更现代的加密算法(如 AES-GCM 或 ChaCha20-Poly1305)以提升安全性。
|
||
|
||
---
|
||
|
||
> 文档版本:v1.0
|
||
> 最后更新:2025-04-05 |