花了几天时间我写了一个自用的压缩工具,可以加密成任意后缀的格式文件,且无法用其他软件打开。
架构设计
下面说一下架构设计
+-----------------+
| 主菜单 |
+--------+--------+
|
v
+--------+--------+
| 用户输入路径/密码 |
+--------+--------+
|
v
+--------+--------+
| 文件遍历与信息收集|
+--------+--------+
|
v
+--------+--------+ +----------------+
| 任务分发协程 | --> | 压缩&加密协程 |
+--------+--------+ +----------------+
| |
| +----------------+
v v
+--------------------+
| 数据写入与偏移管理 |
+--------------------+
|
v
+--------+--------+ +----------------+
| 结果收集协程 | <-- | 条目打包写头部 |
+-----------------+ +----------------+
解压方向:结构对称 + 验证 HMAC + 解密 + 解压 + CRC 校验
流程
输入1
1.用户输入文件或目录路径
2.用户输入压缩密码(可为空,空则不加密)
3.用户选择压缩算法
- 1:LZ4(速度优先)
- 2:Zstd(压缩率和速度平衡)
- 其他或空:自动选择
4.遍历输入路径,收集所有文件的路径、相对路径和大小信息
5.如果用户未指定压缩算法,根据文件总大小自动选择:
- 小于10MB -> LZ4
- 大于等于10MB -> Zstd
6.生成32字节随机盐(salt)
7.根据用户输入密码和盐,通过PBKDF2-HMAC-SHA256算法派生32字节加密密钥
8.创建输出文件,预留空间写入文件头和文件条目(FileEntry)信息
9.启动一个工作协程进行流式文件压缩与加密处理
- 该协程读取文件内容
- 使用选定的压缩算法(LZ4或Zstd)流式压缩
- 将压缩后的数据分块(1MB为单位)
- 为每块生成随机nonce
- 使用ChaCha20-Poly1305 AEAD算法加密每块数据
- 将加密块写入输出文件的指定偏移
- 更新当前写入偏移,记录加密块数量和最后一块大小
- 计算并记录每个文件的CRC32校验值
10. 启动结果收集协程,接收压缩协程返回的文件条目结果,保存至内存结构中
11. 主协程顺序发送所有文件任务到压缩协程
12. 所有文件压缩加密完成后,主协程计算整个文件头及文件条目的HMAC值进行完整性保护
13. 将计算好的HMAC写入文件头部
14. 将完整的文件头和文件条目写回输出文件头部指定位置,完成文件封装
15. 输出压缩完成统计信息(文件数、压缩率、耗时、速度、输出路径等)
16. 结束压缩流程,等待用户下一步操作
Mermaid流程图
输入2
解压流程(输入 2)
1. 用户交互阶段
- 提示用户输入
.xs
压缩包路径 - 提示用户输入解压密码
2. 读取文件头
- 使用 binary.Read 解析
FileHeader
结构 - 验证 MagicNumber 是否为 “xiaosheng”
- 派生解密密钥(PBKDF2)
3. 校验文件头完整性
- 读取所有
FileEntry
- 构造 header + entries 的 binary 数据
- 用 HMAC-SHA256 校验是否被篡改(密码正确性)
4. 创建输出目录
- 基于输入路径创建一个
xxx_output
文件夹
5. 初始化进度条
6. 遍历每个文件条目进行解压
对于每个 FileEntry:
– 创建输出子目录和目标文件
– 打开源文件定位到加密数据 Offset
– 分块读取每个加密块:
– nonce + ciphertext 解密(chacha20poly1305)
– 解密后写入临时文件(压缩数据)
– 解压缩(LZ4/Zstd)临时文件到目标文件
– 校验 CRC32 和解压后大小
7. 完成
- 打印成功信息(文件数、耗时、输出目录)
Mermaid流程图
核心设计理念
1. 安全性优先
- 密钥派生:使用PBKDF2(100,000次迭代)将用户密码强化为256位密钥
- 加密算法:ChaCha20-Poly1305提供AEAD(认证加密)保护
- 完整性验证:双重保护 – HMAC验证头部,CRC32验证数据
- 随机性:每次压缩生成新的32字节盐值,每个数据块使用独立nonce
2. 性能优化
- 流式处理:避免一次性加载整个文件到内存
- 并发设计:多文件并行处理,充分利用多核CPU
- 缓冲区管理:64KB I/O缓冲区平衡性能和内存使用
- 算法选择:根据文件大小自动选择最优压缩算法
3. 可扩展性
- 版本控制:主/次版本号支持格式升级
- 算法抽象:压缩和加密算法类型字段,便于添加新算法
- 模块化设计:压缩、加密、I/O操作相互独立
关键设计决策
为什么选择ChaCha20-Poly1305?
- 性能:在没有AES硬件加速的设备上比AES-GCM快3-5倍
- 安全性:由Daniel J. Bernstein设计,经过广泛审查
- 简单性:实现简单,不易出错
- 移动友好:在ARM处理器上表现优异
为什么使用分块加密?
- 内存控制:1MB块大小避免大文件导致内存溢出
- 并行潜力:块可以独立解密,支持未来并行解密
- 错误隔离:单个块损坏不影响其他块
- 流式处理:支持边读边处理,无需等待完整文件
为什么采用临时文件?
// 压缩流程:源文件 -> 临时文件(压缩) -> 最终文件(加密)
tempFile, err := os.CreateTemp("", "xs_compress_*.tmp")
- 解耦操作:压缩和加密分离,代码更清晰
- 错误恢复:失败时不会损坏输出文件
- 内存友好:避免在内存中存储整个压缩数据
并发模型
Producer-Consumer模式
文件收集 -> 任务队列 -> Worker池 -> 结果收集
| | | |
顺序生成 缓冲通道 并发处理 顺序组装
关键同步点
- 文件偏移量:互斥锁保护,确保连续性
- 任务分发:通道实现自然的背压控制
- 结果收集:按索引存储,保持原始顺序
数据完整性保证
三层保护
- 文件级CRC32:检测数据传输错误
- 块级AEAD:每个加密块包含认证标签
- 头部HMAC:防止元数据篡改
验证流程
解压时验证顺序:
1. 魔数验证 -> 确认文件格式
2. HMAC验证 -> 确认密码正确且头部完整
3. AEAD验证 -> 每个块的完整性
4. CRC32验证 -> 最终数据正确性
用户体验考虑
1. 智能默认值
- 自动选择压缩算法
- 空密码时仍可压缩(不推荐)
- 输出文件名自动生成
2. 进度反馈
- 实时进度条显示
- 包含速度、剩余时间估算
- 处理完成后的详细统计
3. 错误处理
- 明确的错误信息
- 部分失败时继续处理其他文件
- 验证失败时给出具体原因
性能特征
理论性能
- 压缩速度:主要受限于磁盘I/O和压缩算法
- LZ4模式:接近磁盘读取速度(500+ MB/s)
- Zstd模式:200-300 MB/s(取决于CPU)
- 加密开销:ChaCha20约10-20%额外开销
实测优化点
- 缓冲区大小:64KB在大多数系统上最优
- 块大小:1MB平衡了内存使用和加密开销
- 并发度:默认使用所有CPU核心
安全性分析
威胁模型
- 被动攻击者:无法读取加密内容
- 主动攻击者:HMAC和AEAD防止篡改
- 暴力破解:PBKDF2显著增加破解成本
密码强度建议
- 最小长度:12个字符
- 包含:大小写字母、数字、特殊字符
- 避免:字典词汇、个人信息
未来改进方向
- 性能提升
- 并行解密支持
- SIMD加速压缩
- 零拷贝优化
- 功能增强
- 增量备份支持
- 密钥文件选项
- 云存储集成
- 算法支持
- Brotli压缩
- AES-GCM加密选项
- 后量子密码算法预备
源码部分
文件头
type FileHeader struct {
MagicNumber [9]byte // "xiaosheng" - 文件格式标识
VersionMajor byte // 主版本号
VersionMinor byte // 次版本号
CompressionType byte // 压缩算法类型
EncryptionType byte // 加密算法类型
Timestamp int64 // 创建时间戳
FileCount uint32 // 文件数量
HeaderSize uint32 // 头部总大小
DataOffset uint64 // 数据开始偏移
Salt [32]byte // 密钥派生盐值
HeaderHMAC [32]byte // 头部完整性校验
}
设计理由
1. 魔数 (9字节)
- 作用:快速识别文件格式
- “xiaosheng”:意为“小生”
- 9字节而非8字节:避免对齐问题,确保唯一性
2. 版本信息 (2字节)
- 主版本号:不兼容的格式变更
- 次版本号:向后兼容的功能添加
- 便于未来升级和兼容性处理
3. 算法标识 (2字节)
- CompressionType:支持多种压缩算法
- EncryptionType:支持多种加密算法
- 预留扩展空间,便于添加新算法
4. 元数据 (20字节)
- Timestamp:记录创建时间
- FileCount:快速获知文件数量
- HeaderSize:便于跳过头部读取数据
- DataOffset:直接定位数据区域
5. 安全相关 (64字节)
- Salt (32字节):
- 每次压缩生成随机盐值
- 防止彩虹表攻击
- 确保相同密码产生不同密钥
- HeaderHMAC (32字节):
- 保护头部完整性
- 验证密码正确性
- 防止头部被篡改
文件布局
┌─────────────────┐
│ FileHeader │ 64 bytes
├─────────────────┤
│ FileEntry 1 │ variable
├─────────────────┤
│ FileEntry 2 │ variable
├─────────────────┤
│ ... │
├─────────────────┤
│ FileEntry N │ variable
├─────────────────┤
│ Compressed Data │
│ (Encrypted) │
└─────────────────┘
优势
- 固定大小头部:便于读取和解析
- 完整性保护:HMAC防止篡改
- 扩展性:版本号支持未来升级
- 安全性:盐值+HMAC双重保护
- 高效定位:DataOffset直接定位数据
密钥派生(PBKDF2)机制分析
实现代码
func deriveKey(password string, salt []byte) []byte {
iterations := 100000 // 迭代次数
keyLen := 32 // 256位密钥
dk := pbkdf2([]byte(password), salt, iterations, keyLen)
return dk
}
func pbkdf2(password, salt []byte, iterations, keyLen int) []byte {
h := hmac.New(sha256.New, password)
h.Write(salt)
h.Write([]byte{0, 0, 0, 1}) // 块索引
result := h.Sum(nil)
prev := result
// 迭代计算
for i := 1; i < iterations; i++ {
h.Reset()
h.Write(prev)
prev = h.Sum(nil)
for j := range result {
result[j] ^= prev[j] // XOR累积
}
}
return result[:keyLen]
}
作用和意义
1. 密码强化
- 将弱密码转换为强密钥
- 100,000次迭代大幅增加破解成本
- 每次迭代约需0.1ms,总计10秒破解一个密码
2. 防彩虹表攻击
// 每次压缩生成随机盐值
salt := make([]byte, 32)
if _, err := rand.Read(salt); err != nil {
return err
}
- 32字节随机盐值
- 相同密码产生不同密钥
- 彩虹表无法预计算
3. 密钥长度标准化
- 输入:任意长度密码
- 输出:固定256位密钥
- 适配ChaCha20-Poly1305要求
4. 计算成本控制
迭代次数 | 单次耗时 | 安全性 | 用户体验 |
---|---|---|---|
1,000 | 0.1秒 | 低 | 极好 |
10,000 | 1秒 | 中 | 好 |
100,000 | 10秒 | 高 | 可接受 |
1,000,000 | 100秒 | 极高 | 差 |
5. HMAC-SHA256优势
- 抗长度扩展攻击
- 经过密码学验证
- 硬件加速支持
安全性分析
攻击成本计算
假设攻击者有1000个GPU,每个GPU可以进行10^9次哈希/秒:
– 单个密码尝试:100,000次迭代
– 每秒可尝试密码:10^9 / 100,000 = 10,000个
– 1000 GPU每秒:10^7个密码
– 破解8位字母数字密码:62^8 / 10^7 ≈ 6年
与直接使用密码对比
方面 | 直接使用密码 | PBKDF2派生 |
---|---|---|
密钥强度 | 依赖密码强度 | 始终256位 |
抗暴力破解 | 弱 | 强(10万倍成本) |
抗彩虹表 | 无 | 有(随机盐值) |
密钥分发 | 简单 | 简单 |
0 条评论