mirror of
https://github.com/violettoolssite/CFspider.git
synced 2026-04-05 03:09:01 +08:00
破皮版完整实现功能
This commit is contained in:
225
x27cn/README.md
225
x27cn/README.md
@@ -167,9 +167,197 @@ X27CN v2 使用以下加密步骤:
|
||||
3. **位旋转** - 循环左移 5 位
|
||||
4. **状态混合** - 使用累积状态值混淆
|
||||
|
||||
## 代码压缩混淆(v1.2.0 新增)
|
||||
|
||||
除了加密型混淆,X27CN 还提供专业的代码压缩和标识符混淆功能。
|
||||
|
||||
### Python API
|
||||
|
||||
```python
|
||||
import x27cn
|
||||
|
||||
# 压缩 CSS
|
||||
minified_css = x27cn.minify_css('body { color: red; }')
|
||||
# 输出: 'body{color:red}'
|
||||
|
||||
# 压缩 JavaScript(带变量名混淆)
|
||||
minified_js = x27cn.minify_js('function hello() { var name = "world"; return name; }')
|
||||
# 输出: 'function hello(){var _a="world";return _a}'
|
||||
|
||||
# 压缩 HTML(自动处理内联 CSS/JS)
|
||||
minified_html = x27cn.minify_html('<html> <body> </body> </html>')
|
||||
# 输出: '<html><body></body></html>'
|
||||
|
||||
# 使用 Node.js 工具(效果更好,需安装 terser/clean-css)
|
||||
minified = x27cn.minify_js_node(js_code)
|
||||
|
||||
# 压缩文件
|
||||
x27cn.minify_file('app.js') # 生成 app.min.js
|
||||
x27cn.minify_file('style.css', 'dist/style.css')
|
||||
```
|
||||
|
||||
### 高级混淆
|
||||
|
||||
```python
|
||||
import x27cn
|
||||
|
||||
# 标识符混淆(变量名替换为 _$0, _$1, ...)
|
||||
obfuscated = x27cn.obfuscate_identifiers(js_code)
|
||||
|
||||
# 添加死代码(增加逆向难度)
|
||||
obfuscated = x27cn.add_dead_code(js_code, complexity=3)
|
||||
```
|
||||
|
||||
### 命令行
|
||||
|
||||
```bash
|
||||
# 压缩 JavaScript
|
||||
x27cn minify app.js
|
||||
|
||||
# 压缩 CSS
|
||||
x27cn minify style.css dist/style.min.css
|
||||
|
||||
# 不混淆变量名
|
||||
x27cn minify app.js --no-mangle
|
||||
|
||||
# 不使用 Node.js 工具(纯 Python)
|
||||
x27cn minify app.js --no-node
|
||||
|
||||
# 添加死代码
|
||||
x27cn minify app.js --dead-code=3
|
||||
|
||||
# 额外标识符混淆
|
||||
x27cn minify app.js --identifiers
|
||||
```
|
||||
|
||||
### Node.js 工具支持
|
||||
|
||||
如果安装了以下 npm 包,X27CN 会自动使用它们以获得更好的压缩效果:
|
||||
|
||||
```bash
|
||||
npm install -g terser clean-css-cli html-minifier-terser
|
||||
```
|
||||
|
||||
如果未安装,会自动降级到纯 Python 实现。
|
||||
|
||||
### 压缩 vs 加密混淆对比
|
||||
|
||||
| 特性 | `minify` | `obfuscate` |
|
||||
|------|----------|-------------|
|
||||
| 可读性 | 低 | 极低 |
|
||||
| 代码可执行 | 直接执行 | 需解密 |
|
||||
| 密钥需求 | 不需要 | 需要 |
|
||||
| 文件大小 | 显著减小 | 略增大 |
|
||||
| 安全性 | 中等 | 较高 |
|
||||
| 性能影响 | 无 | 有解密开销 |
|
||||
|
||||
**推荐使用场景:**
|
||||
- `minify`: 生产环境部署、减小文件体积、基础代码保护
|
||||
- `obfuscate`: 需要更强保护的敏感代码、API密钥保护
|
||||
|
||||
## 密码安全(v1.3.0 新增)
|
||||
|
||||
X27CN 现在提供安全的密码处理功能,适合用户认证场景。
|
||||
|
||||
### 密码哈希(存储密码)
|
||||
|
||||
```python
|
||||
import x27cn
|
||||
|
||||
# 哈希密码(用于存储)
|
||||
hashed = x27cn.hash_password('mypassword123')
|
||||
# 输出: '$x27cn$100000$base64salt$base64hash'
|
||||
|
||||
# 验证密码
|
||||
if x27cn.verify_password('mypassword123', hashed):
|
||||
print('登录成功')
|
||||
else:
|
||||
print('密码错误')
|
||||
```
|
||||
|
||||
### 密码强度检测
|
||||
|
||||
```python
|
||||
result = x27cn.check_password_strength('abc123')
|
||||
print(result['level']) # 'weak'
|
||||
print(result['score']) # 15
|
||||
print(result['suggestions']) # ['添加大写字母', '添加特殊字符']
|
||||
```
|
||||
|
||||
### 生成安全密码
|
||||
|
||||
```python
|
||||
# 生成 16 位随机密码
|
||||
pwd = x27cn.generate_password(16)
|
||||
|
||||
# 自定义选项
|
||||
pwd = x27cn.generate_password(
|
||||
length=20,
|
||||
include_upper=True,
|
||||
include_lower=True,
|
||||
include_digits=True,
|
||||
include_special=True,
|
||||
exclude_ambiguous=True # 排除 0O1lI 等易混淆字符
|
||||
)
|
||||
```
|
||||
|
||||
### 基于密码的加密
|
||||
|
||||
```python
|
||||
# 使用密码加密数据(比 key 更安全)
|
||||
encrypted = x27cn.encrypt_with_password('敏感数据', 'mypassword')
|
||||
# 输出: <p7d0><xx><xx>...<xxxx><xxxx>... (标准 <xxxx> 格式)
|
||||
|
||||
# 解密
|
||||
decrypted = x27cn.decrypt_with_password(encrypted, 'mypassword')
|
||||
```
|
||||
|
||||
格式说明:
|
||||
- `<p7d0>` - 魔数标识(表示密码加密)
|
||||
- 后续 16 个 `<xx>` - 随机盐值
|
||||
- 剩余部分 - 加密数据
|
||||
|
||||
### 快速哈希
|
||||
|
||||
```python
|
||||
# MD5(不推荐用于密码,仅用于校验)
|
||||
x27cn.md5('hello') # '5d41402abc4b2a76b9719d911017c592'
|
||||
|
||||
# SHA256
|
||||
x27cn.sha256('hello') # '2cf24dba5fb0a30e...'
|
||||
|
||||
# SHA512
|
||||
x27cn.sha512('hello')
|
||||
```
|
||||
|
||||
### 命令行
|
||||
|
||||
```bash
|
||||
# 哈希密码
|
||||
x27cn password hash "mypassword123"
|
||||
|
||||
# 验证密码
|
||||
x27cn password verify "mypassword123" "$x27cn$100000$..."
|
||||
|
||||
# 生成密码
|
||||
x27cn password generate --length=20 --count=5
|
||||
|
||||
# 检查密码强度
|
||||
x27cn password check "abc123"
|
||||
|
||||
# 使用密码加密文件
|
||||
x27cn encrypt secret.txt --password="mypassword"
|
||||
|
||||
# 使用密码解密
|
||||
x27cn decrypt secret.txt.enc --password="mypassword"
|
||||
```
|
||||
|
||||
## 安全说明
|
||||
|
||||
X27CN 设计用于**代码混淆**,不是密码学安全的加密算法。
|
||||
X27CN 提供两种安全级别:
|
||||
|
||||
### 1. 代码混淆(encrypt/obfuscate)
|
||||
设计用于**代码混淆**,不是密码学安全的加密算法。
|
||||
|
||||
适用场景:
|
||||
- 前端代码混淆保护
|
||||
@@ -177,10 +365,39 @@ X27CN 设计用于**代码混淆**,不是密码学安全的加密算法。
|
||||
- 配置文件保护
|
||||
- 防止代码被轻易复制
|
||||
|
||||
不适用场景:
|
||||
- 密码存储(请使用 bcrypt/argon2)
|
||||
- 敏感数据加密(请使用 AES-256)
|
||||
### 2. 密码安全(hash_password/encrypt_with_password)
|
||||
使用行业标准的 **PBKDF2-SHA256** 算法。
|
||||
|
||||
适用场景:
|
||||
- 用户密码存储 ✓
|
||||
- 敏感数据加密 ✓
|
||||
- 配置文件加密 ✓
|
||||
|
||||
安全特性:
|
||||
- PBKDF2-SHA256 密钥派生(100000 次迭代)
|
||||
- 随机盐值防止彩虹表攻击
|
||||
- 恒定时间比较防止时序攻击
|
||||
|
||||
### 不适用场景
|
||||
- 通信加密(请使用 TLS)
|
||||
- 金融级加密(请使用 AES-256-GCM)
|
||||
|
||||
## 完整 API 参考
|
||||
|
||||
| 函数 | 说明 |
|
||||
|------|------|
|
||||
| `encrypt(text, key)` | X27CN 加密 |
|
||||
| `decrypt(text, key)` | X27CN 解密 |
|
||||
| `obfuscate_file(path)` | 文件混淆加密 |
|
||||
| `minify(content)` | 代码压缩 |
|
||||
| `minify_js(js)` | JS 压缩 + 变量混淆 |
|
||||
| `hash_password(pwd)` | 密码哈希 |
|
||||
| `verify_password(pwd, hash)` | 验证密码 |
|
||||
| `generate_password(len)` | 生成随机密码 |
|
||||
| `check_password_strength(pwd)` | 检测密码强度 |
|
||||
| `encrypt_with_password(data, pwd)` | 密码加密数据 |
|
||||
| `decrypt_with_password(data, pwd)` | 密码解密数据 |
|
||||
| `md5(text)` / `sha256(text)` | 快速哈希 |
|
||||
|
||||
## License
|
||||
|
||||
|
||||
7
x27cn/debug_mangle.py
Normal file
7
x27cn/debug_mangle.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from x27cn.minify import _mangle_variables
|
||||
|
||||
js = 'var name="x";var greeting="y"+name;'
|
||||
print("输入:", js)
|
||||
result = _mangle_variables(js)
|
||||
print("输出:", result)
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "x27cn"
|
||||
version = "1.1.0"
|
||||
version = "1.3.0"
|
||||
description = "X27CN 代码混淆加密库 - Code obfuscation and encryption library"
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
|
||||
@@ -40,7 +40,35 @@ from .obfuscate import (
|
||||
obfuscate_inline_css,
|
||||
)
|
||||
|
||||
__version__ = '1.1.0'
|
||||
from .minify import (
|
||||
minify,
|
||||
minify_file,
|
||||
minify_css,
|
||||
minify_js,
|
||||
minify_html,
|
||||
minify_css_node,
|
||||
minify_js_node,
|
||||
minify_html_node,
|
||||
obfuscate_identifiers,
|
||||
add_dead_code,
|
||||
flatten_control_flow,
|
||||
flatten_control_flow_safe,
|
||||
)
|
||||
|
||||
from .password import (
|
||||
hash_password,
|
||||
verify_password,
|
||||
check_password_strength,
|
||||
generate_password,
|
||||
encrypt_with_password,
|
||||
decrypt_with_password,
|
||||
quick_hash,
|
||||
md5,
|
||||
sha256,
|
||||
sha512,
|
||||
)
|
||||
|
||||
__version__ = '1.3.0'
|
||||
__author__ = 'CFspider'
|
||||
__all__ = [
|
||||
# 核心加密
|
||||
@@ -52,12 +80,36 @@ __all__ = [
|
||||
'decrypt_base64',
|
||||
'generate_key',
|
||||
'DEFAULT_KEY',
|
||||
# 文件混淆
|
||||
# 文件混淆(加密型)
|
||||
'obfuscate_html',
|
||||
'obfuscate_js',
|
||||
'obfuscate_css',
|
||||
'obfuscate_file',
|
||||
'obfuscate_inline_js',
|
||||
'obfuscate_inline_css',
|
||||
# 代码压缩混淆
|
||||
'minify',
|
||||
'minify_file',
|
||||
'minify_css',
|
||||
'minify_js',
|
||||
'minify_html',
|
||||
'minify_css_node',
|
||||
'minify_js_node',
|
||||
'minify_html_node',
|
||||
'obfuscate_identifiers',
|
||||
'add_dead_code',
|
||||
'flatten_control_flow',
|
||||
'flatten_control_flow_safe',
|
||||
# 密码安全
|
||||
'hash_password',
|
||||
'verify_password',
|
||||
'check_password_strength',
|
||||
'generate_password',
|
||||
'encrypt_with_password',
|
||||
'decrypt_with_password',
|
||||
'quick_hash',
|
||||
'md5',
|
||||
'sha256',
|
||||
'sha512',
|
||||
]
|
||||
|
||||
|
||||
@@ -5,12 +5,23 @@ X27CN 命令行工具
|
||||
x27cn encrypt <file> [output] [--key=密钥]
|
||||
x27cn decrypt <file> [output] [--key=密钥]
|
||||
x27cn obfuscate <file> [output] [--key=密钥]
|
||||
x27cn minify <file> [output] [--no-mangle] [--no-node]
|
||||
x27cn flatten <file> [output] [--intensity=2] [--safe]
|
||||
x27cn password hash <password>
|
||||
x27cn password verify <password> <hash>
|
||||
x27cn password generate [--length=16]
|
||||
x27cn password check <password>
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from .core import encrypt, decrypt, DEFAULT_KEY
|
||||
from .obfuscate import obfuscate_file
|
||||
from .minify import minify_file, obfuscate_identifiers, add_dead_code, flatten_control_flow, flatten_control_flow_safe
|
||||
from .password import (
|
||||
hash_password, verify_password, generate_password,
|
||||
check_password_strength, encrypt_with_password, decrypt_with_password
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
@@ -18,7 +29,7 @@ def main():
|
||||
prog='x27cn',
|
||||
description='X27CN 代码混淆加密工具'
|
||||
)
|
||||
parser.add_argument('--version', action='version', version='x27cn 1.0.0')
|
||||
parser.add_argument('--version', action='version', version='x27cn 1.3.0')
|
||||
|
||||
subparsers = parser.add_subparsers(dest='command', help='命令')
|
||||
|
||||
@@ -28,6 +39,7 @@ def main():
|
||||
enc_parser.add_argument('output', nargs='?', help='输出文件(可选)')
|
||||
enc_parser.add_argument('--key', '-k', default=DEFAULT_KEY, help='加密密钥')
|
||||
enc_parser.add_argument('--text', '-t', action='store_true', help='将 input 作为文本而非文件')
|
||||
enc_parser.add_argument('--password', '-p', help='使用密码加密(更安全)')
|
||||
|
||||
# decrypt 命令
|
||||
dec_parser = subparsers.add_parser('decrypt', help='解密文本或文件')
|
||||
@@ -35,6 +47,7 @@ def main():
|
||||
dec_parser.add_argument('output', nargs='?', help='输出文件(可选)')
|
||||
dec_parser.add_argument('--key', '-k', default=DEFAULT_KEY, help='解密密钥')
|
||||
dec_parser.add_argument('--text', '-t', action='store_true', help='将 input 作为文本而非文件')
|
||||
dec_parser.add_argument('--password', '-p', help='使用密码解密')
|
||||
|
||||
# obfuscate 命令
|
||||
obf_parser = subparsers.add_parser('obfuscate', help='混淆加密文件(生成自解密代码)')
|
||||
@@ -42,6 +55,47 @@ def main():
|
||||
obf_parser.add_argument('output', nargs='?', help='输出文件(可选)')
|
||||
obf_parser.add_argument('--key', '-k', default=DEFAULT_KEY, help='加密密钥')
|
||||
|
||||
# minify 命令
|
||||
min_parser = subparsers.add_parser('minify', help='压缩混淆文件(不加密)')
|
||||
min_parser.add_argument('input', help='输入文件 (.html/.js/.css)')
|
||||
min_parser.add_argument('output', nargs='?', help='输出文件(可选)')
|
||||
min_parser.add_argument('--no-mangle', action='store_true', help='不混淆变量名')
|
||||
min_parser.add_argument('--no-node', action='store_true', help='不使用 Node.js 工具')
|
||||
min_parser.add_argument('--dead-code', type=int, default=0, help='添加死代码复杂度 (1-5)')
|
||||
min_parser.add_argument('--identifiers', action='store_true', help='额外混淆标识符')
|
||||
|
||||
# flatten 命令
|
||||
flat_parser = subparsers.add_parser('flatten', help='控制流扁平化混淆(仅JS)')
|
||||
flat_parser.add_argument('input', help='输入 JavaScript 文件')
|
||||
flat_parser.add_argument('output', nargs='?', help='输出文件(可选)')
|
||||
flat_parser.add_argument('--intensity', '-i', type=int, default=2, choices=[1, 2, 3],
|
||||
help='扁平化强度 (1=轻, 2=中, 3=强)')
|
||||
flat_parser.add_argument('--safe', '-s', action='store_true', help='使用安全模式(更保守)')
|
||||
|
||||
# password 命令
|
||||
pwd_parser = subparsers.add_parser('password', help='密码工具')
|
||||
pwd_subparsers = pwd_parser.add_subparsers(dest='pwd_command', help='密码子命令')
|
||||
|
||||
# password hash
|
||||
pwd_hash = pwd_subparsers.add_parser('hash', help='哈希密码')
|
||||
pwd_hash.add_argument('password', help='要哈希的密码')
|
||||
pwd_hash.add_argument('--iterations', '-i', type=int, default=100000, help='迭代次数')
|
||||
|
||||
# password verify
|
||||
pwd_verify = pwd_subparsers.add_parser('verify', help='验证密码')
|
||||
pwd_verify.add_argument('password', help='明文密码')
|
||||
pwd_verify.add_argument('hash', help='哈希值')
|
||||
|
||||
# password generate
|
||||
pwd_gen = pwd_subparsers.add_parser('generate', help='生成随机密码')
|
||||
pwd_gen.add_argument('--length', '-l', type=int, default=16, help='密码长度')
|
||||
pwd_gen.add_argument('--no-special', action='store_true', help='不包含特殊字符')
|
||||
pwd_gen.add_argument('--count', '-c', type=int, default=1, help='生成数量')
|
||||
|
||||
# password check
|
||||
pwd_check = pwd_subparsers.add_parser('check', help='检查密码强度')
|
||||
pwd_check.add_argument('password', help='要检查的密码')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.command:
|
||||
@@ -51,12 +105,18 @@ def main():
|
||||
try:
|
||||
if args.command == 'encrypt':
|
||||
if args.text:
|
||||
result = encrypt(args.input, args.key)
|
||||
if args.password:
|
||||
result = encrypt_with_password(args.input, args.password)
|
||||
else:
|
||||
result = encrypt(args.input, args.key)
|
||||
print(result)
|
||||
else:
|
||||
with open(args.input, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
result = encrypt(content, args.key)
|
||||
if args.password:
|
||||
result = encrypt_with_password(content, args.password)
|
||||
else:
|
||||
result = encrypt(content, args.key)
|
||||
if args.output:
|
||||
with open(args.output, 'w', encoding='utf-8') as f:
|
||||
f.write(result)
|
||||
@@ -66,12 +126,18 @@ def main():
|
||||
|
||||
elif args.command == 'decrypt':
|
||||
if args.text:
|
||||
result = decrypt(args.input, args.key)
|
||||
if args.password:
|
||||
result = decrypt_with_password(args.input, args.password)
|
||||
else:
|
||||
result = decrypt(args.input, args.key)
|
||||
print(result)
|
||||
else:
|
||||
with open(args.input, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
result = decrypt(content, args.key)
|
||||
if args.password:
|
||||
result = decrypt_with_password(content, args.password)
|
||||
else:
|
||||
result = decrypt(content, args.key)
|
||||
if args.output:
|
||||
with open(args.output, 'w', encoding='utf-8') as f:
|
||||
f.write(result)
|
||||
@@ -82,6 +148,87 @@ def main():
|
||||
elif args.command == 'obfuscate':
|
||||
output = obfuscate_file(args.input, args.output, args.key)
|
||||
print(f'混淆完成: {output}')
|
||||
|
||||
elif args.command == 'minify':
|
||||
output = minify_file(
|
||||
args.input,
|
||||
args.output,
|
||||
use_node=not args.no_node,
|
||||
mangle=not args.no_mangle
|
||||
)
|
||||
# 后处理
|
||||
if args.dead_code > 0 or args.identifiers:
|
||||
with open(output, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
if args.identifiers and output.endswith('.js'):
|
||||
content = obfuscate_identifiers(content)
|
||||
if args.dead_code > 0 and output.endswith('.js'):
|
||||
content = add_dead_code(content, args.dead_code)
|
||||
with open(output, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
print(f'压缩完成: {output}')
|
||||
|
||||
elif args.command == 'flatten':
|
||||
with open(args.input, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
if args.safe:
|
||||
result = flatten_control_flow_safe(content)
|
||||
else:
|
||||
result = flatten_control_flow(content, intensity=args.intensity)
|
||||
|
||||
import os
|
||||
if args.output:
|
||||
output_path = args.output
|
||||
else:
|
||||
base, ext = os.path.splitext(args.input)
|
||||
output_path = f"{base}.flat{ext}"
|
||||
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
f.write(result)
|
||||
print(f'控制流扁平化完成: {output_path}')
|
||||
|
||||
elif args.command == 'password':
|
||||
if args.pwd_command == 'hash':
|
||||
hashed = hash_password(args.password, iterations=args.iterations)
|
||||
print(hashed)
|
||||
|
||||
elif args.pwd_command == 'verify':
|
||||
if verify_password(args.password, args.hash):
|
||||
print('✓ 密码正确')
|
||||
else:
|
||||
print('✗ 密码错误')
|
||||
sys.exit(1)
|
||||
|
||||
elif args.pwd_command == 'generate':
|
||||
for _ in range(args.count):
|
||||
pwd = generate_password(
|
||||
length=args.length,
|
||||
include_special=not args.no_special
|
||||
)
|
||||
print(pwd)
|
||||
|
||||
elif args.pwd_command == 'check':
|
||||
result = check_password_strength(args.password)
|
||||
level_colors = {
|
||||
'weak': '🔴',
|
||||
'fair': '🟠',
|
||||
'good': '🟡',
|
||||
'strong': '🟢',
|
||||
'excellent': '💚'
|
||||
}
|
||||
print(f"{level_colors.get(result['level'], '')} 强度: {result['level'].upper()} ({result['score']}/100)")
|
||||
print(f" 长度: {result['length']} 字符")
|
||||
print(f" 小写: {'✓' if result['has_lower'] else '✗'}")
|
||||
print(f" 大写: {'✓' if result['has_upper'] else '✗'}")
|
||||
print(f" 数字: {'✓' if result['has_digit'] else '✗'}")
|
||||
print(f" 特殊字符: {'✓' if result['has_special'] else '✗'}")
|
||||
if result['suggestions']:
|
||||
print("\n建议:")
|
||||
for s in result['suggestions']:
|
||||
print(f" - {s}")
|
||||
else:
|
||||
pwd_parser.print_help()
|
||||
|
||||
except FileNotFoundError:
|
||||
print(f'错误: 文件不存在 - {args.input}', file=sys.stderr)
|
||||
|
||||
804
x27cn/x27cn/minify.py
Normal file
804
x27cn/x27cn/minify.py
Normal file
@@ -0,0 +1,804 @@
|
||||
"""
|
||||
X27CN 代码压缩混淆模块
|
||||
|
||||
提供 HTML/CSS/JS 的专业压缩和混淆功能:
|
||||
1. 纯Python实现的压缩(无需额外依赖)
|
||||
2. Node.js工具调用(如果可用,效果更好)
|
||||
"""
|
||||
|
||||
import re
|
||||
import os
|
||||
import json
|
||||
import subprocess
|
||||
import shutil
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
# ============== 纯 Python 压缩实现 ==============
|
||||
|
||||
def minify_css(css: str) -> str:
|
||||
"""
|
||||
压缩 CSS 代码(纯Python实现)
|
||||
|
||||
Args:
|
||||
css: CSS 源代码
|
||||
|
||||
Returns:
|
||||
压缩后的 CSS
|
||||
|
||||
Example:
|
||||
>>> minify_css('body { color: red; }')
|
||||
'body{color:red}'
|
||||
"""
|
||||
# 移除注释
|
||||
css = re.sub(r'/\*[\s\S]*?\*/', '', css)
|
||||
|
||||
# 移除多余空白
|
||||
css = re.sub(r'\s+', ' ', css)
|
||||
|
||||
# 移除选择器和属性周围的空白
|
||||
css = re.sub(r'\s*([{};:,>+~])\s*', r'\1', css)
|
||||
|
||||
# 移除最后的分号
|
||||
css = re.sub(r';}', '}', css)
|
||||
|
||||
# 移除0值的单位
|
||||
css = re.sub(r'(\s|:)0(px|em|rem|%|pt|cm|mm|in|pc|ex|ch|vw|vh|vmin|vmax)', r'\g<1>0', css)
|
||||
|
||||
# 简化颜色
|
||||
css = re.sub(r'#([0-9a-fA-F])\1([0-9a-fA-F])\2([0-9a-fA-F])\3', r'#\1\2\3', css)
|
||||
|
||||
# 移除开头结尾空白
|
||||
css = css.strip()
|
||||
|
||||
return css
|
||||
|
||||
|
||||
def minify_js(js: str, mangle: bool = True) -> str:
|
||||
"""
|
||||
压缩 JavaScript 代码(纯Python实现)
|
||||
|
||||
Args:
|
||||
js: JavaScript 源代码
|
||||
mangle: 是否混淆变量名
|
||||
|
||||
Returns:
|
||||
压缩后的 JavaScript
|
||||
|
||||
Example:
|
||||
>>> minify_js('function test() { return 1; }')
|
||||
'function test(){return 1}'
|
||||
"""
|
||||
# 先混淆变量名(在原始代码上,保持完整语法)
|
||||
if mangle:
|
||||
js = _mangle_variables(js)
|
||||
|
||||
# 保护字符串和正则表达式
|
||||
strings = []
|
||||
def save_string(match):
|
||||
idx = len(strings)
|
||||
strings.append(match.group(0))
|
||||
return f'__STR_{idx}__'
|
||||
|
||||
# 保护单引号、双引号和反引号字符串
|
||||
js = re.sub(r"'(?:[^'\\]|\\.)*'", save_string, js)
|
||||
js = re.sub(r'"(?:[^"\\]|\\.)*"', save_string, js)
|
||||
js = re.sub(r'`(?:[^`\\]|\\.)*`', save_string, js)
|
||||
|
||||
# 移除单行注释
|
||||
js = re.sub(r'//[^\n]*', '', js)
|
||||
|
||||
# 移除多行注释
|
||||
js = re.sub(r'/\*[\s\S]*?\*/', '', js)
|
||||
|
||||
# 压缩空白
|
||||
js = re.sub(r'\s+', ' ', js)
|
||||
|
||||
# 移除操作符周围的空白
|
||||
js = re.sub(r'\s*([{}\[\]();,<>=+\-*/%&|!?:])\s*', r'\1', js)
|
||||
|
||||
# 关键字后保留空格
|
||||
for kw in ['return', 'throw', 'new', 'delete', 'typeof', 'void', 'in', 'instanceof', 'else', 'case', 'var', 'let', 'const', 'function', 'class', 'extends', 'async', 'await', 'yield', 'import', 'export', 'from', 'as', 'of']:
|
||||
js = re.sub(rf'({kw})([a-zA-Z_$])', rf'\1 \2', js)
|
||||
|
||||
# 恢复字符串
|
||||
for i, s in enumerate(strings):
|
||||
js = js.replace(f'__STR_{i}__', s)
|
||||
|
||||
# 移除开头结尾空白
|
||||
js = js.strip()
|
||||
|
||||
return js
|
||||
|
||||
|
||||
def _mangle_variables(js: str) -> str:
|
||||
"""混淆局部变量名"""
|
||||
# 保护字符串
|
||||
strings = []
|
||||
def save_string(match):
|
||||
idx = len(strings)
|
||||
strings.append(match.group(0))
|
||||
return f'__STR_{idx}__'
|
||||
|
||||
js = re.sub(r"'(?:[^'\\]|\\.)*'", save_string, js)
|
||||
js = re.sub(r'"(?:[^"\\]|\\.)*"', save_string, js)
|
||||
js = re.sub(r'`(?:[^`\\]|\\.)*`', save_string, js)
|
||||
|
||||
# 找到函数作用域内的变量声明
|
||||
var_pattern = re.compile(r'\b(var|let|const)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)')
|
||||
|
||||
# 生成短变量名
|
||||
def gen_name(index):
|
||||
chars = 'abcdefghijklmnopqrstuvwxyz'
|
||||
if index < 26:
|
||||
return '_' + chars[index]
|
||||
return '_' + chars[index // 26 - 1] + chars[index % 26]
|
||||
|
||||
# 收集变量
|
||||
var_map = {}
|
||||
index = 0
|
||||
for match in var_pattern.finditer(js):
|
||||
name = match.group(2)
|
||||
if name not in var_map and name not in ['undefined', 'null', 'true', 'false', 'NaN', 'Infinity']:
|
||||
var_map[name] = gen_name(index)
|
||||
index += 1
|
||||
|
||||
# 替换变量(从长到短排序避免部分替换)
|
||||
for old_name in sorted(var_map.keys(), key=len, reverse=True):
|
||||
new_name = var_map[old_name]
|
||||
# 只替换完整单词
|
||||
js = re.sub(rf'\b{re.escape(old_name)}\b', new_name, js)
|
||||
|
||||
# 恢复字符串
|
||||
for i, s in enumerate(strings):
|
||||
js = js.replace(f'__STR_{i}__', s)
|
||||
|
||||
return js
|
||||
|
||||
|
||||
def minify_html(html: str, minify_inline: bool = True) -> str:
|
||||
"""
|
||||
压缩 HTML 代码
|
||||
|
||||
Args:
|
||||
html: HTML 源代码
|
||||
minify_inline: 是否压缩内联的CSS/JS
|
||||
|
||||
Returns:
|
||||
压缩后的 HTML
|
||||
"""
|
||||
# 保护 <pre>、<script>、<style>、<textarea> 内容
|
||||
preserved = []
|
||||
|
||||
def save_block(tag_name):
|
||||
def replacer(match):
|
||||
idx = len(preserved)
|
||||
preserved.append(match.group(0))
|
||||
return f'__BLOCK_{idx}__'
|
||||
return replacer
|
||||
|
||||
# 保存这些标签的内容
|
||||
for tag in ['pre', 'code', 'textarea']:
|
||||
html = re.sub(rf'<{tag}[^>]*>[\s\S]*?</{tag}>', save_block(tag), html, flags=re.IGNORECASE)
|
||||
|
||||
# 处理 script 和 style
|
||||
if minify_inline:
|
||||
# 压缩内联 JS
|
||||
def process_script(match):
|
||||
attrs = match.group(1)
|
||||
content = match.group(2)
|
||||
# 跳过外部脚本
|
||||
if 'src=' in attrs.lower():
|
||||
return match.group(0)
|
||||
minified = minify_js(content, mangle=False)
|
||||
return f'<script{attrs}>{minified}</script>'
|
||||
html = re.sub(r'<script([^>]*)>([\s\S]*?)</script>', process_script, html, flags=re.IGNORECASE)
|
||||
|
||||
# 压缩内联 CSS
|
||||
def process_style(match):
|
||||
attrs = match.group(1)
|
||||
content = match.group(2)
|
||||
minified = minify_css(content)
|
||||
return f'<style{attrs}>{minified}</style>'
|
||||
html = re.sub(r'<style([^>]*)>([\s\S]*?)</style>', process_style, html, flags=re.IGNORECASE)
|
||||
else:
|
||||
# 保护 script 和 style
|
||||
html = re.sub(r'<script[^>]*>[\s\S]*?</script>', save_block('script'), html, flags=re.IGNORECASE)
|
||||
html = re.sub(r'<style[^>]*>[\s\S]*?</style>', save_block('style'), html, flags=re.IGNORECASE)
|
||||
|
||||
# 移除 HTML 注释
|
||||
html = re.sub(r'<!--[\s\S]*?-->', '', html)
|
||||
|
||||
# 压缩空白(但保留单个空格)
|
||||
html = re.sub(r'\s+', ' ', html)
|
||||
|
||||
# 移除标签周围的空白
|
||||
html = re.sub(r'>\s+<', '><', html)
|
||||
|
||||
# 移除属性值周围的引号(可选值)
|
||||
# html = re.sub(r'=\s*"([^"\s]+)"', r'=\1', html)
|
||||
|
||||
# 移除冗余属性
|
||||
html = re.sub(r'\s+type\s*=\s*["\']?text/javascript["\']?', '', html, flags=re.IGNORECASE)
|
||||
html = re.sub(r'\s+type\s*=\s*["\']?text/css["\']?', '', html, flags=re.IGNORECASE)
|
||||
|
||||
# 恢复保护的内容
|
||||
for i, block in enumerate(preserved):
|
||||
html = html.replace(f'__BLOCK_{i}__', block)
|
||||
|
||||
return html.strip()
|
||||
|
||||
|
||||
# ============== Node.js 工具调用 ==============
|
||||
|
||||
def _check_node() -> bool:
|
||||
"""检查 Node.js 是否可用"""
|
||||
return shutil.which('node') is not None
|
||||
|
||||
|
||||
def _check_npm_package(package: str) -> bool:
|
||||
"""检查 npm 包是否安装"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['npm', 'list', package],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
)
|
||||
return result.returncode == 0
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
def minify_css_node(css: str) -> str:
|
||||
"""
|
||||
使用 clean-css (Node.js) 压缩 CSS
|
||||
|
||||
如果 Node.js 不可用,自动降级到纯 Python 实现。
|
||||
"""
|
||||
if not _check_node():
|
||||
return minify_css(css)
|
||||
|
||||
try:
|
||||
# 写入临时文件
|
||||
import tempfile
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.css', delete=False, encoding='utf-8') as f:
|
||||
f.write(css)
|
||||
temp_path = f.name
|
||||
|
||||
# 调用 cleancss
|
||||
result = subprocess.run(
|
||||
['npx', 'cleancss', temp_path],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
os.unlink(temp_path)
|
||||
|
||||
if result.returncode == 0 and result.stdout:
|
||||
return result.stdout
|
||||
else:
|
||||
return minify_css(css)
|
||||
except Exception:
|
||||
return minify_css(css)
|
||||
|
||||
|
||||
def minify_js_node(js: str, mangle: bool = True) -> str:
|
||||
"""
|
||||
使用 Terser (Node.js) 压缩混淆 JavaScript
|
||||
|
||||
如果 Node.js 不可用,自动降级到纯 Python 实现。
|
||||
"""
|
||||
if not _check_node():
|
||||
return minify_js(js, mangle)
|
||||
|
||||
try:
|
||||
import tempfile
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.js', delete=False, encoding='utf-8') as f:
|
||||
f.write(js)
|
||||
temp_path = f.name
|
||||
|
||||
cmd = ['npx', 'terser', temp_path, '--compress', '--format', 'comments=false']
|
||||
if mangle:
|
||||
cmd.append('--mangle')
|
||||
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
os.unlink(temp_path)
|
||||
|
||||
if result.returncode == 0 and result.stdout:
|
||||
return result.stdout
|
||||
else:
|
||||
return minify_js(js, mangle)
|
||||
except Exception:
|
||||
return minify_js(js, mangle)
|
||||
|
||||
|
||||
def minify_html_node(html: str) -> str:
|
||||
"""
|
||||
使用 html-minifier-terser (Node.js) 压缩 HTML
|
||||
|
||||
如果 Node.js 不可用,自动降级到纯 Python 实现。
|
||||
"""
|
||||
if not _check_node():
|
||||
return minify_html(html)
|
||||
|
||||
try:
|
||||
import tempfile
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.html', delete=False, encoding='utf-8') as f:
|
||||
f.write(html)
|
||||
temp_path = f.name
|
||||
|
||||
result = subprocess.run(
|
||||
['npx', 'html-minifier-terser',
|
||||
'--collapse-whitespace',
|
||||
'--remove-comments',
|
||||
'--minify-css', 'true',
|
||||
'--minify-js', 'true',
|
||||
temp_path],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
os.unlink(temp_path)
|
||||
|
||||
if result.returncode == 0 and result.stdout:
|
||||
return result.stdout
|
||||
else:
|
||||
return minify_html(html)
|
||||
except Exception:
|
||||
return minify_html(html)
|
||||
|
||||
|
||||
# ============== 高级混淆功能 ==============
|
||||
|
||||
def obfuscate_identifiers(js: str) -> str:
|
||||
"""
|
||||
混淆 JavaScript 标识符(变量名、函数名)
|
||||
|
||||
Args:
|
||||
js: JavaScript 源代码
|
||||
|
||||
Returns:
|
||||
混淆后的代码
|
||||
"""
|
||||
# 保护字符串和正则
|
||||
protected = []
|
||||
def save_protected(match):
|
||||
idx = len(protected)
|
||||
protected.append(match.group(0))
|
||||
return f'__PROT_{idx}__'
|
||||
|
||||
# 保护字符串
|
||||
js = re.sub(r"'(?:[^'\\]|\\.)*'", save_protected, js)
|
||||
js = re.sub(r'"(?:[^"\\]|\\.)*"', save_protected, js)
|
||||
js = re.sub(r'`(?:[^`\\]|\\.)*`', save_protected, js)
|
||||
|
||||
# 保护正则
|
||||
js = re.sub(r'/(?![/*])(?:[^/\\]|\\.)+/[gimsuy]*', save_protected, js)
|
||||
|
||||
# 收集声明的变量和函数
|
||||
declarations = set()
|
||||
|
||||
# var/let/const 声明
|
||||
for match in re.finditer(r'\b(?:var|let|const)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)', js):
|
||||
declarations.add(match.group(1))
|
||||
|
||||
# function 声明
|
||||
for match in re.finditer(r'\bfunction\s+([a-zA-Z_$][a-zA-Z0-9_$]*)', js):
|
||||
declarations.add(match.group(1))
|
||||
|
||||
# 生成混淆名
|
||||
def gen_name(index):
|
||||
# 使用 _$0, _$1, ... 格式
|
||||
return f'_${index:x}'
|
||||
|
||||
# 过滤保留字和内置对象
|
||||
reserved = {
|
||||
'undefined', 'null', 'true', 'false', 'NaN', 'Infinity',
|
||||
'Object', 'Array', 'String', 'Number', 'Boolean', 'Function',
|
||||
'Symbol', 'BigInt', 'Math', 'Date', 'RegExp', 'Error',
|
||||
'JSON', 'console', 'window', 'document', 'navigator',
|
||||
'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval',
|
||||
'Promise', 'async', 'await', 'class', 'extends', 'super',
|
||||
'this', 'new', 'delete', 'typeof', 'instanceof', 'in',
|
||||
'if', 'else', 'for', 'while', 'do', 'switch', 'case', 'break',
|
||||
'continue', 'return', 'throw', 'try', 'catch', 'finally',
|
||||
'import', 'export', 'default', 'from', 'as', 'of',
|
||||
'arguments', 'eval', 'with', 'debugger',
|
||||
'crypto', 'fetch', 'Response', 'Request', 'Headers', 'URL',
|
||||
'TextEncoder', 'TextDecoder', 'Uint8Array', 'ArrayBuffer',
|
||||
'atob', 'btoa', 'encodeURIComponent', 'decodeURIComponent',
|
||||
}
|
||||
|
||||
# 创建映射
|
||||
name_map = {}
|
||||
idx = 0
|
||||
for name in sorted(declarations, key=len, reverse=True):
|
||||
if name not in reserved and len(name) > 1:
|
||||
name_map[name] = gen_name(idx)
|
||||
idx += 1
|
||||
|
||||
# 替换
|
||||
for old_name, new_name in name_map.items():
|
||||
js = re.sub(rf'\b{re.escape(old_name)}\b', new_name, js)
|
||||
|
||||
# 恢复保护的内容
|
||||
for i, p in enumerate(protected):
|
||||
js = js.replace(f'__PROT_{i}__', p)
|
||||
|
||||
return js
|
||||
|
||||
|
||||
def add_dead_code(js: str, complexity: int = 2) -> str:
|
||||
"""
|
||||
添加无效代码增加逆向难度
|
||||
|
||||
Args:
|
||||
js: JavaScript 源代码
|
||||
complexity: 复杂度 (1-5)
|
||||
|
||||
Returns:
|
||||
添加死代码后的代码
|
||||
"""
|
||||
dead_code_templates = [
|
||||
'var _$d0=function(){return Math.random()>2};',
|
||||
'var _$d1=(function(){var _=[];for(var i=0;i<0;i++)_.push(i);return _})();',
|
||||
'if(typeof _$d2==="undefined")var _$d2=null;',
|
||||
'try{if(false)throw new Error()}catch(_$e){}',
|
||||
'var _$d3=Date.now()%1===2?1:0;',
|
||||
]
|
||||
|
||||
import random
|
||||
dead_codes = random.sample(dead_code_templates, min(complexity, len(dead_code_templates)))
|
||||
|
||||
# 在代码开头添加
|
||||
return ''.join(dead_codes) + js
|
||||
|
||||
|
||||
# ============== 统一接口 ==============
|
||||
|
||||
def minify(
|
||||
content: str,
|
||||
content_type: str = 'auto',
|
||||
use_node: bool = True,
|
||||
mangle: bool = True
|
||||
) -> str:
|
||||
"""
|
||||
统一的压缩混淆接口
|
||||
|
||||
Args:
|
||||
content: 源代码
|
||||
content_type: 类型 ('html', 'css', 'js', 'auto')
|
||||
use_node: 是否尝试使用 Node.js 工具
|
||||
mangle: 是否混淆变量名(仅 JS)
|
||||
|
||||
Returns:
|
||||
压缩后的代码
|
||||
|
||||
Example:
|
||||
>>> minify('body { color: red; }', 'css')
|
||||
'body{color:red}'
|
||||
>>> minify('<html>...</html>', 'html')
|
||||
'<html>...</html>'
|
||||
"""
|
||||
# 自动检测类型
|
||||
if content_type == 'auto':
|
||||
content_lower = content.strip().lower()
|
||||
if content_lower.startswith('<!doctype') or content_lower.startswith('<html'):
|
||||
content_type = 'html'
|
||||
elif '{' in content and (':' in content or '@' in content):
|
||||
# 可能是 CSS
|
||||
if re.search(r'[.#]?[\w-]+\s*\{', content):
|
||||
content_type = 'css'
|
||||
else:
|
||||
content_type = 'js'
|
||||
else:
|
||||
content_type = 'js'
|
||||
|
||||
# 调用对应的压缩函数
|
||||
if content_type == 'html':
|
||||
return minify_html_node(content) if use_node else minify_html(content)
|
||||
elif content_type == 'css':
|
||||
return minify_css_node(content) if use_node else minify_css(content)
|
||||
else: # js
|
||||
result = minify_js_node(content, mangle) if use_node else minify_js(content, mangle)
|
||||
return result
|
||||
|
||||
|
||||
def minify_file(
|
||||
input_path: str,
|
||||
output_path: Optional[str] = None,
|
||||
use_node: bool = True,
|
||||
mangle: bool = True
|
||||
) -> str:
|
||||
"""
|
||||
压缩混淆文件
|
||||
|
||||
Args:
|
||||
input_path: 输入文件路径
|
||||
output_path: 输出文件路径(默认添加 .min 后缀)
|
||||
use_node: 是否使用 Node.js 工具
|
||||
mangle: 是否混淆变量名
|
||||
|
||||
Returns:
|
||||
输出文件路径
|
||||
|
||||
Example:
|
||||
>>> minify_file('app.js')
|
||||
'app.min.js'
|
||||
>>> minify_file('style.css', 'dist/style.css')
|
||||
'dist/style.css'
|
||||
"""
|
||||
# 读取文件
|
||||
with open(input_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# 检测类型
|
||||
ext = os.path.splitext(input_path)[1].lower()
|
||||
if ext in ['.html', '.htm']:
|
||||
content_type = 'html'
|
||||
elif ext == '.css':
|
||||
content_type = 'css'
|
||||
elif ext == '.js':
|
||||
content_type = 'js'
|
||||
else:
|
||||
content_type = 'auto'
|
||||
|
||||
# 压缩
|
||||
minified = minify(content, content_type, use_node, mangle)
|
||||
|
||||
# 输出路径
|
||||
if output_path is None:
|
||||
base, ext = os.path.splitext(input_path)
|
||||
output_path = f"{base}.min{ext}"
|
||||
|
||||
# 写入
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
f.write(minified)
|
||||
|
||||
return output_path
|
||||
|
||||
|
||||
# ============== 控制流扁平化 ==============
|
||||
|
||||
def flatten_control_flow(js: str, intensity: int = 2) -> str:
|
||||
"""
|
||||
控制流扁平化 - 将顺序执行的代码块打乱为 switch-case 结构
|
||||
|
||||
Args:
|
||||
js: JavaScript 源代码
|
||||
intensity: 扁平化强度 (1-3)
|
||||
1: 仅扁平化顶层语句
|
||||
2: 扁平化函数体内语句
|
||||
3: 递归扁平化所有代码块
|
||||
|
||||
Returns:
|
||||
扁平化后的 JavaScript 代码
|
||||
|
||||
Example:
|
||||
原始代码:
|
||||
step1(); step2(); step3();
|
||||
|
||||
扁平化后:
|
||||
var _$s=[2,1,3,0],_$i=0;
|
||||
while(_$s[_$i]!==0){
|
||||
switch(_$s[_$i++]){
|
||||
case 1:step1();break;
|
||||
case 2:step2();break;
|
||||
case 3:step3();break;
|
||||
}
|
||||
}
|
||||
"""
|
||||
import random
|
||||
|
||||
# 保护字符串和正则
|
||||
protected = []
|
||||
def save_protected(match):
|
||||
idx = len(protected)
|
||||
protected.append(match.group(0))
|
||||
return f'__PROT_{idx}__'
|
||||
|
||||
js = re.sub(r"'(?:[^'\\]|\\.)*'", save_protected, js)
|
||||
js = re.sub(r'"(?:[^"\\]|\\.)*"', save_protected, js)
|
||||
js = re.sub(r'`(?:[^`\\]|\\.)*`', save_protected, js)
|
||||
js = re.sub(r'/(?![/*])(?:[^/\\]|\\.)+/[gimsuy]*', save_protected, js)
|
||||
|
||||
def flatten_block(code: str, depth: int = 0) -> str:
|
||||
"""扁平化一个代码块"""
|
||||
if depth >= intensity:
|
||||
return code
|
||||
|
||||
# 分割语句(简化版本,处理基本情况)
|
||||
statements = _split_statements(code)
|
||||
|
||||
if len(statements) < 3:
|
||||
return code
|
||||
|
||||
# 过滤空语句
|
||||
statements = [s.strip() for s in statements if s.strip()]
|
||||
|
||||
if len(statements) < 3:
|
||||
return code
|
||||
|
||||
# 生成随机编号
|
||||
indices = list(range(1, len(statements) + 1))
|
||||
random.shuffle(indices)
|
||||
indices.append(0) # 结束标志
|
||||
|
||||
# 生成唯一变量名
|
||||
var_s = f'_$s{depth}'
|
||||
var_i = f'_$i{depth}'
|
||||
|
||||
# 构建 switch 语句
|
||||
cases = []
|
||||
for i, stmt in enumerate(statements):
|
||||
case_num = i + 1
|
||||
# 递归扁平化函数体
|
||||
if intensity >= 2 and 'function' in stmt:
|
||||
stmt = _flatten_function_body(stmt, depth + 1, intensity)
|
||||
cases.append(f'case {case_num}:{stmt};break;')
|
||||
|
||||
# 组装结果
|
||||
order_array = ','.join(str(i) for i in indices)
|
||||
result = f'var {var_s}=[{order_array}],{var_i}=0;'
|
||||
result += f'while({var_s}[{var_i}]!==0){{'
|
||||
result += f'switch({var_s}[{var_i}++]){{'
|
||||
result += ''.join(cases)
|
||||
result += '}}'
|
||||
|
||||
return result
|
||||
|
||||
def _split_statements(code: str) -> list:
|
||||
"""分割语句(简化版本)"""
|
||||
statements = []
|
||||
current = ''
|
||||
brace_depth = 0
|
||||
paren_depth = 0
|
||||
bracket_depth = 0
|
||||
in_string = None
|
||||
|
||||
i = 0
|
||||
while i < len(code):
|
||||
c = code[i]
|
||||
|
||||
# 检查字符串
|
||||
if c in '"\'`' and (i == 0 or code[i-1] != '\\'):
|
||||
if in_string is None:
|
||||
in_string = c
|
||||
elif in_string == c:
|
||||
in_string = None
|
||||
current += c
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if in_string:
|
||||
current += c
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# 跟踪括号
|
||||
if c == '{':
|
||||
brace_depth += 1
|
||||
elif c == '}':
|
||||
brace_depth -= 1
|
||||
elif c == '(':
|
||||
paren_depth += 1
|
||||
elif c == ')':
|
||||
paren_depth -= 1
|
||||
elif c == '[':
|
||||
bracket_depth += 1
|
||||
elif c == ']':
|
||||
bracket_depth -= 1
|
||||
|
||||
# 分割点
|
||||
if c == ';' and brace_depth == 0 and paren_depth == 0 and bracket_depth == 0:
|
||||
if current.strip():
|
||||
statements.append(current.strip())
|
||||
current = ''
|
||||
elif c == '}' and brace_depth == 0 and paren_depth == 0 and bracket_depth == 0:
|
||||
current += c
|
||||
if current.strip():
|
||||
statements.append(current.strip())
|
||||
current = ''
|
||||
else:
|
||||
current += c
|
||||
|
||||
i += 1
|
||||
|
||||
if current.strip():
|
||||
statements.append(current.strip())
|
||||
|
||||
return statements
|
||||
|
||||
def _flatten_function_body(func_code: str, depth: int, intensity: int) -> str:
|
||||
"""扁平化函数体内容"""
|
||||
# 找到函数体
|
||||
match = re.search(r'(function[^{]*\{)([\s\S]*)(\})\s*$', func_code)
|
||||
if not match:
|
||||
return func_code
|
||||
|
||||
prefix = match.group(1)
|
||||
body = match.group(2)
|
||||
suffix = match.group(3)
|
||||
|
||||
# 扁平化函数体
|
||||
flattened_body = flatten_block(body, depth)
|
||||
|
||||
return prefix + flattened_body + suffix
|
||||
|
||||
# 处理整个代码
|
||||
result = flatten_block(js, 0)
|
||||
|
||||
# 恢复保护的内容
|
||||
for i, p in enumerate(protected):
|
||||
result = result.replace(f'__PROT_{i}__', p)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def flatten_control_flow_safe(js: str) -> str:
|
||||
"""
|
||||
安全的控制流扁平化(仅处理简单函数)
|
||||
|
||||
此版本更保守,只处理明确可以扁平化的代码块,
|
||||
避免破坏复杂语法结构。
|
||||
|
||||
Args:
|
||||
js: JavaScript 源代码
|
||||
|
||||
Returns:
|
||||
扁平化后的代码
|
||||
"""
|
||||
import random
|
||||
|
||||
# 保护字符串、正则、模板字符串
|
||||
protected = []
|
||||
def save_protected(match):
|
||||
idx = len(protected)
|
||||
protected.append(match.group(0))
|
||||
return f'__PROT_{idx}__'
|
||||
|
||||
js = re.sub(r"'(?:[^'\\]|\\.)*'", save_protected, js)
|
||||
js = re.sub(r'"(?:[^"\\]|\\.)*"', save_protected, js)
|
||||
js = re.sub(r'`(?:[^`\\]|\\.)*`', save_protected, js)
|
||||
|
||||
# 找到简单的立即执行函数 (IIFE)
|
||||
def process_iife(match):
|
||||
inner = match.group(1)
|
||||
# 分割为简单语句
|
||||
stmts = [s.strip() for s in inner.split(';') if s.strip()]
|
||||
|
||||
if len(stmts) < 3:
|
||||
return match.group(0)
|
||||
|
||||
# 过滤包含控制流语句的代码
|
||||
for stmt in stmts:
|
||||
if any(kw in stmt for kw in ['if', 'for', 'while', 'switch', 'try', 'function']):
|
||||
return match.group(0)
|
||||
|
||||
# 生成扁平化代码
|
||||
indices = list(range(1, len(stmts) + 1))
|
||||
random.shuffle(indices)
|
||||
indices.append(0)
|
||||
|
||||
order = ','.join(str(i) for i in indices)
|
||||
cases = ''.join('case {}:{};break;'.format(i+1, stmts[i]) for i in range(len(stmts)))
|
||||
|
||||
return '(function(){{var _$f=[{}],_$g=0;while(_$f[_$g]!==0){{switch(_$f[_$g++]){{{}}}}}}}})()'.format(order, cases)
|
||||
|
||||
# 处理 IIFE
|
||||
js = re.sub(r'\(function\(\)\{([^{}]*)\}\)\(\)', process_iife, js)
|
||||
|
||||
# 恢复保护的内容
|
||||
for i, p in enumerate(protected):
|
||||
js = js.replace(f'__PROT_{i}__', p)
|
||||
|
||||
return js
|
||||
|
||||
411
x27cn/x27cn/password.py
Normal file
411
x27cn/x27cn/password.py
Normal file
@@ -0,0 +1,411 @@
|
||||
"""
|
||||
X27CN 密码加密模块
|
||||
|
||||
提供安全的密码哈希和验证功能:
|
||||
- PBKDF2-SHA256 密码哈希(适合密码存储)
|
||||
- 密码强度检测
|
||||
- 基于密码的加密/解密
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import hashlib
|
||||
import hmac
|
||||
import base64
|
||||
import secrets
|
||||
from typing import Tuple, Optional
|
||||
|
||||
|
||||
# ============== 密码哈希 ==============
|
||||
|
||||
def hash_password(
|
||||
password: str,
|
||||
salt: Optional[bytes] = None,
|
||||
iterations: int = 100000
|
||||
) -> str:
|
||||
"""
|
||||
使用 PBKDF2-SHA256 哈希密码
|
||||
|
||||
Args:
|
||||
password: 明文密码
|
||||
salt: 盐值(可选,默认自动生成16字节)
|
||||
iterations: 迭代次数(默认100000,越高越安全但越慢)
|
||||
|
||||
Returns:
|
||||
格式化的哈希字符串: $x27cn$iterations$salt$hash
|
||||
|
||||
Example:
|
||||
>>> hashed = hash_password('mypassword123')
|
||||
>>> print(hashed)
|
||||
'$x27cn$100000$abc123...$def456...'
|
||||
"""
|
||||
if salt is None:
|
||||
salt = os.urandom(16)
|
||||
|
||||
# PBKDF2-SHA256 派生
|
||||
dk = hashlib.pbkdf2_hmac(
|
||||
'sha256',
|
||||
password.encode('utf-8'),
|
||||
salt,
|
||||
iterations,
|
||||
dklen=32
|
||||
)
|
||||
|
||||
# 编码为 base64
|
||||
salt_b64 = base64.b64encode(salt).decode('ascii')
|
||||
hash_b64 = base64.b64encode(dk).decode('ascii')
|
||||
|
||||
return f'$x27cn${iterations}${salt_b64}${hash_b64}'
|
||||
|
||||
|
||||
def verify_password(password: str, hashed: str) -> bool:
|
||||
"""
|
||||
验证密码是否匹配哈希
|
||||
|
||||
Args:
|
||||
password: 待验证的明文密码
|
||||
hashed: hash_password 返回的哈希字符串
|
||||
|
||||
Returns:
|
||||
密码是否正确
|
||||
|
||||
Example:
|
||||
>>> hashed = hash_password('mypassword123')
|
||||
>>> verify_password('mypassword123', hashed)
|
||||
True
|
||||
>>> verify_password('wrongpassword', hashed)
|
||||
False
|
||||
"""
|
||||
try:
|
||||
parts = hashed.split('$')
|
||||
if len(parts) != 5 or parts[1] != 'x27cn':
|
||||
return False
|
||||
|
||||
iterations = int(parts[2])
|
||||
salt = base64.b64decode(parts[3])
|
||||
expected_hash = base64.b64decode(parts[4])
|
||||
|
||||
# 重新计算
|
||||
dk = hashlib.pbkdf2_hmac(
|
||||
'sha256',
|
||||
password.encode('utf-8'),
|
||||
salt,
|
||||
iterations,
|
||||
dklen=32
|
||||
)
|
||||
|
||||
# 使用恒定时间比较防止时序攻击
|
||||
return hmac.compare_digest(dk, expected_hash)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
# ============== 密码强度 ==============
|
||||
|
||||
def check_password_strength(password: str) -> dict:
|
||||
"""
|
||||
检测密码强度
|
||||
|
||||
Args:
|
||||
password: 待检测的密码
|
||||
|
||||
Returns:
|
||||
包含强度信息的字典:
|
||||
- score: 分数 (0-100)
|
||||
- level: 等级 ('weak', 'fair', 'good', 'strong', 'excellent')
|
||||
- suggestions: 改进建议列表
|
||||
|
||||
Example:
|
||||
>>> result = check_password_strength('abc123')
|
||||
>>> print(result['level'])
|
||||
'weak'
|
||||
"""
|
||||
score = 0
|
||||
suggestions = []
|
||||
|
||||
length = len(password)
|
||||
|
||||
# 长度评分
|
||||
if length >= 16:
|
||||
score += 30
|
||||
elif length >= 12:
|
||||
score += 25
|
||||
elif length >= 8:
|
||||
score += 15
|
||||
elif length >= 6:
|
||||
score += 10
|
||||
else:
|
||||
suggestions.append('密码长度至少需要8个字符')
|
||||
|
||||
# 复杂性评分
|
||||
has_lower = bool(re.search(r'[a-z]', password))
|
||||
has_upper = bool(re.search(r'[A-Z]', password))
|
||||
has_digit = bool(re.search(r'[0-9]', password))
|
||||
has_special = bool(re.search(r'[!@#$%^&*(),.?":{}|<>_\-+=\[\]\\\/`~]', password))
|
||||
|
||||
complexity = sum([has_lower, has_upper, has_digit, has_special])
|
||||
score += complexity * 15
|
||||
|
||||
if not has_lower:
|
||||
suggestions.append('添加小写字母')
|
||||
if not has_upper:
|
||||
suggestions.append('添加大写字母')
|
||||
if not has_digit:
|
||||
suggestions.append('添加数字')
|
||||
if not has_special:
|
||||
suggestions.append('添加特殊字符 (!@#$%^&* 等)')
|
||||
|
||||
# 熵评分(字符多样性)
|
||||
unique_chars = len(set(password))
|
||||
if unique_chars >= 10:
|
||||
score += 10
|
||||
elif unique_chars >= 6:
|
||||
score += 5
|
||||
|
||||
# 常见模式扣分
|
||||
common_patterns = [
|
||||
r'123', r'abc', r'qwerty', r'password', r'admin',
|
||||
r'111', r'000', r'aaa', r'(.)\1{2,}' # 连续重复字符
|
||||
]
|
||||
for pattern in common_patterns:
|
||||
if re.search(pattern, password.lower()):
|
||||
score -= 10
|
||||
if pattern == r'(.)\1{2,}':
|
||||
suggestions.append('避免连续重复的字符')
|
||||
else:
|
||||
suggestions.append('避免使用常见模式')
|
||||
break
|
||||
|
||||
# 限制分数范围
|
||||
score = max(0, min(100, score))
|
||||
|
||||
# 确定等级
|
||||
if score >= 80:
|
||||
level = 'excellent'
|
||||
elif score >= 60:
|
||||
level = 'strong'
|
||||
elif score >= 40:
|
||||
level = 'good'
|
||||
elif score >= 20:
|
||||
level = 'fair'
|
||||
else:
|
||||
level = 'weak'
|
||||
|
||||
return {
|
||||
'score': score,
|
||||
'level': level,
|
||||
'suggestions': suggestions,
|
||||
'length': length,
|
||||
'has_lower': has_lower,
|
||||
'has_upper': has_upper,
|
||||
'has_digit': has_digit,
|
||||
'has_special': has_special,
|
||||
}
|
||||
|
||||
|
||||
# ============== 密码生成 ==============
|
||||
|
||||
def generate_password(
|
||||
length: int = 16,
|
||||
include_upper: bool = True,
|
||||
include_lower: bool = True,
|
||||
include_digits: bool = True,
|
||||
include_special: bool = True,
|
||||
exclude_ambiguous: bool = True
|
||||
) -> str:
|
||||
"""
|
||||
生成安全的随机密码
|
||||
|
||||
Args:
|
||||
length: 密码长度(默认16)
|
||||
include_upper: 包含大写字母
|
||||
include_lower: 包含小写字母
|
||||
include_digits: 包含数字
|
||||
include_special: 包含特殊字符
|
||||
exclude_ambiguous: 排除易混淆字符 (0O1lI)
|
||||
|
||||
Returns:
|
||||
随机密码
|
||||
|
||||
Example:
|
||||
>>> pwd = generate_password(20)
|
||||
>>> print(len(pwd))
|
||||
20
|
||||
"""
|
||||
chars = ''
|
||||
required = []
|
||||
|
||||
lower_chars = 'abcdefghijkmnopqrstuvwxyz' if exclude_ambiguous else 'abcdefghijklmnopqrstuvwxyz'
|
||||
upper_chars = 'ABCDEFGHJKMNPQRSTUVWXYZ' if exclude_ambiguous else 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
digit_chars = '23456789' if exclude_ambiguous else '0123456789'
|
||||
special_chars = '!@#$%^&*-_=+?'
|
||||
|
||||
if include_lower:
|
||||
chars += lower_chars
|
||||
required.append(secrets.choice(lower_chars))
|
||||
if include_upper:
|
||||
chars += upper_chars
|
||||
required.append(secrets.choice(upper_chars))
|
||||
if include_digits:
|
||||
chars += digit_chars
|
||||
required.append(secrets.choice(digit_chars))
|
||||
if include_special:
|
||||
chars += special_chars
|
||||
required.append(secrets.choice(special_chars))
|
||||
|
||||
if not chars:
|
||||
raise ValueError('至少需要选择一种字符类型')
|
||||
|
||||
# 生成剩余字符
|
||||
remaining = length - len(required)
|
||||
password_chars = required + [secrets.choice(chars) for _ in range(remaining)]
|
||||
|
||||
# 打乱顺序
|
||||
secrets.SystemRandom().shuffle(password_chars)
|
||||
|
||||
return ''.join(password_chars)
|
||||
|
||||
|
||||
# ============== 基于密码的加密 ==============
|
||||
|
||||
def encrypt_with_password(plaintext: str, password: str) -> str:
|
||||
"""
|
||||
使用密码加密数据
|
||||
|
||||
使用 PBKDF2 派生加密密钥,然后用 X27CN 算法加密。
|
||||
输出格式为 <xxxx> 标准格式,盐值编码在开头。
|
||||
|
||||
Args:
|
||||
plaintext: 明文数据
|
||||
password: 加密密码
|
||||
|
||||
Returns:
|
||||
加密后的密文(<xxxx> 格式,包含盐值)
|
||||
|
||||
Example:
|
||||
>>> encrypted = encrypt_with_password('secret data', 'mypassword')
|
||||
>>> # 输出: <p7d0><salt16bytes><encrypted...>
|
||||
>>> decrypted = decrypt_with_password(encrypted, 'mypassword')
|
||||
>>> print(decrypted)
|
||||
'secret data'
|
||||
"""
|
||||
from .core import encrypt
|
||||
|
||||
# 生成随机盐(16字节)
|
||||
salt = os.urandom(16)
|
||||
|
||||
# 派生密钥
|
||||
key = hashlib.pbkdf2_hmac(
|
||||
'sha256',
|
||||
password.encode('utf-8'),
|
||||
salt,
|
||||
50000,
|
||||
dklen=32
|
||||
)
|
||||
key_str = base64.b64encode(key).decode('ascii')[:32]
|
||||
|
||||
# 加密数据
|
||||
encrypted = encrypt(plaintext, key_str)
|
||||
|
||||
# 将盐值编码为 <xxxx> 格式
|
||||
salt_hex = ''.join(f'<{b:02x}>' for b in salt)
|
||||
|
||||
# 添加魔数标识 <p7d0> 表示密码加密
|
||||
return f'<p7d0>{salt_hex}{encrypted}'
|
||||
|
||||
|
||||
def decrypt_with_password(ciphertext: str, password: str) -> str:
|
||||
"""
|
||||
使用密码解密数据
|
||||
|
||||
Args:
|
||||
ciphertext: encrypt_with_password 返回的密文(<xxxx> 格式)
|
||||
password: 解密密码
|
||||
|
||||
Returns:
|
||||
解密后的明文
|
||||
|
||||
Raises:
|
||||
ValueError: 密码错误或数据损坏
|
||||
|
||||
Example:
|
||||
>>> encrypted = encrypt_with_password('hello', 'pass123')
|
||||
>>> decrypt_with_password(encrypted, 'pass123')
|
||||
'hello'
|
||||
"""
|
||||
from .core import decrypt
|
||||
|
||||
try:
|
||||
# 检查魔数
|
||||
if not ciphertext.startswith('<p7d0>'):
|
||||
raise ValueError('无效的密文格式(缺少密码加密标识)')
|
||||
|
||||
# 移除魔数
|
||||
data = ciphertext[6:] # 去掉 <p7d0>
|
||||
|
||||
# 提取盐值(16字节 = 16个 <xx> 块 = 64字符)
|
||||
salt_part = data[:64] # 16 * 4 = 64
|
||||
encrypted_data = data[64:]
|
||||
|
||||
# 解析盐值
|
||||
salt_bytes = []
|
||||
import re
|
||||
salt_matches = re.findall(r'<([0-9a-fA-F]{2})>', salt_part)
|
||||
if len(salt_matches) != 16:
|
||||
raise ValueError('无效的盐值格式')
|
||||
salt = bytes(int(h, 16) for h in salt_matches)
|
||||
|
||||
# 派生密钥
|
||||
key = hashlib.pbkdf2_hmac(
|
||||
'sha256',
|
||||
password.encode('utf-8'),
|
||||
salt,
|
||||
50000,
|
||||
dklen=32
|
||||
)
|
||||
key_str = base64.b64encode(key).decode('ascii')[:32]
|
||||
|
||||
# 解密
|
||||
return decrypt(encrypted_data, key_str)
|
||||
except ValueError:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise ValueError(f'解密失败(密码可能错误): {e}')
|
||||
|
||||
|
||||
# ============== 快速哈希 ==============
|
||||
|
||||
def quick_hash(data: str, algorithm: str = 'sha256') -> str:
|
||||
"""
|
||||
快速计算字符串哈希值
|
||||
|
||||
Args:
|
||||
data: 要哈希的数据
|
||||
algorithm: 算法 ('md5', 'sha1', 'sha256', 'sha512')
|
||||
|
||||
Returns:
|
||||
十六进制哈希值
|
||||
|
||||
Example:
|
||||
>>> quick_hash('hello')
|
||||
'2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824'
|
||||
"""
|
||||
h = hashlib.new(algorithm)
|
||||
h.update(data.encode('utf-8'))
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def md5(data: str) -> str:
|
||||
"""计算 MD5 哈希"""
|
||||
return quick_hash(data, 'md5')
|
||||
|
||||
|
||||
def sha256(data: str) -> str:
|
||||
"""计算 SHA256 哈希"""
|
||||
return quick_hash(data, 'sha256')
|
||||
|
||||
|
||||
def sha512(data: str) -> str:
|
||||
"""计算 SHA512 哈希"""
|
||||
return quick_hash(data, 'sha512')
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -197,6 +197,14 @@ export default {
|
||||
const userAgent = request.headers.get('User-Agent') || 'null';
|
||||
const upgradeHeader = request.headers.get('Upgrade');
|
||||
|
||||
// 获取原始主机名 (支持 EdgeOne/CDN 回源)
|
||||
// 优先级: 环境变量 > X-Forwarded-Host > Host 头 > url.hostname
|
||||
const originalHost = env.CUSTOM_HOST || env.HOST ||
|
||||
request.headers.get('X-Forwarded-Host') ||
|
||||
request.headers.get('X-Original-Host') ||
|
||||
request.headers.get('Host') ||
|
||||
url.hostname;
|
||||
|
||||
// 获取管理员密码和加密密钥
|
||||
const adminPassword = env.ADMIN || env.admin || env.PASSWORD || env.password ||
|
||||
env.pswd || env.TOKEN || env.KEY || env.UUID || env.uuid || 'cfspider-public';
|
||||
@@ -268,7 +276,7 @@ export default {
|
||||
|
||||
// /x2727admin - 返回加密的密钥提示
|
||||
if (path === 'x2727admin') {
|
||||
const fullUrl = `https://${url.hostname}/x2727admin/${accessKey}`;
|
||||
const fullUrl = `https://${originalHost}/x2727admin/${accessKey}`;
|
||||
|
||||
// 构建提示信息
|
||||
const message = `您的密钥为: ${accessKey}
|
||||
@@ -316,13 +324,13 @@ ${fullUrl}
|
||||
// 密钥验证通过,返回加密的配置信息
|
||||
const colo = request.cf?.colo || 'UNKNOWN';
|
||||
const vlessPath = '/' + userID + (twoProxy ? '?two_proxy=' + encodeURIComponent(twoProxy) : '');
|
||||
const vlessLink = `vless://${userID}@${url.hostname}:443?security=tls&type=ws&host=${url.hostname}&sni=${url.hostname}&path=${encodeURIComponent(vlessPath)}&encryption=none#Node-${colo}`;
|
||||
const vlessLink = `vless://${userID}@${originalHost}:443?security=tls&type=ws&host=${originalHost}&sni=${originalHost}&path=${encodeURIComponent(vlessPath)}&encryption=none#Node-${colo}`;
|
||||
|
||||
const configData = {
|
||||
status: 'online',
|
||||
version: '1.8.7',
|
||||
colo: colo,
|
||||
host: url.hostname,
|
||||
host: originalHost,
|
||||
uuid: userID,
|
||||
vless: vlessLink,
|
||||
two_proxy: twoProxy || null,
|
||||
|
||||
Reference in New Issue
Block a user