logo
0
0
WeChat Login

chisel 一个轻量级的二进制文件配置数据包

Go Version License Test Coverage

chisel 是一个轻量级 Go 包,用于在编译后的二进制文件中嵌入和提取配置数据。通过字节级替换技术,支持在不重新编译的情况下为不同客户端生成定制化二进制文件。

适用场景

  • 📱 客户端动态配置(编译后注入服务器地址、授权文件、指纹等)
  • 🔧 代理工具配置
  • 🖥️ IoT 设备固件
  • 🏢 企业软件定制化交付

核心特性

特性说明
泛型支持完全类型安全,支持任意配置结构体
统一模式默认内置占位符,支持自定义占位符与大小
签名防篡改Ed25519 签名验证(默认开启)
混淆保护AES-CTR 混淆,降低明文可读性(默认开启)
完整性校验CRC32 快速完整性校验
压缩支持gzip 压缩(可选)
零依赖仅依赖 Go 标准库,无第三方依赖
可配置支持自定义占位符、块大小、混淆与签名器
流式接口提供流式 API(当前实现会读取全量输入)

注意:混淆仅用于降低可读性,不提供真正保密性。

警告:默认签名密钥与混淆密钥为内置示例值,仅用于演示与测试。 生产环境务必使用 GenerateEd25519KeyPairGenerateObfuscationKey 生成并替换为自有密钥。

安装

go get cnb.cool/zishuo/chisel

注:需要 Go 1.23 或更高版本(支持泛型)

推荐调用路径

chisel 采用单包主入口设计。

import "cnb.cool/zishuo/chisel"

推荐按这三层心智使用:

层级适用场景推荐 API
直接函数90% 日常场景chisel.Extract / chisel.Generate
可复用对象批量处理、长期复用chisel.NewExtractor / chisel.NewGenerator
高级选项自定义占位符、压缩、签名与混淆chisel.With*

原则只有一句:能直接调用函数,就不要先创建对象。

快速开始

客户端代码

package main import ( "context" "log" "cnb.cool/zishuo/chisel" ) // Config 配置结构 type Config struct { Host string `json:"host"` Port int `json:"port"` Secure bool `json:"secure"` } func main() { // 默认使用根包统一入口 cfg, err := chisel.Extract[Config](context.Background()) if err != nil { log.Fatal(err) } protocol := "http" if cfg.Secure { protocol = "https" } log.Printf("✅ 服务器: %s://%s:%d", protocol, cfg.Host, cfg.Port) }

服务端代码

package main import ( "context" "log" "cnb.cool/zishuo/chisel" ) type Config struct { Host string `json:"host"` Port int `json:"port"` Secure bool `json:"secure"` } func main() { // 创建配置 cfg := Config{ Host: "api.example.com", Port: 443, Secure: true, } // 生成配置化的客户端二进制 err := chisel.Generate(context.Background(), "client.template", "client", cfg) if err != nil { log.Fatal(err) } log.Println("✅ 客户端生成成功!") }

使用步骤

  1. 编写客户端代码,编译得到 client.template
  2. 编写服务端生成器,指定要注入的配置
  3. 运行服务端生成器,得到 client 可执行文件
  4. 直接运行 client,自动读取配置

API 文档

生成(Generator)

服务端可直接使用根包入口,或显式构造可复用的 Generator

// 一次性生成(推荐) err := chisel.Generate(ctx, templatePath, outputPath, config) if err != nil { log.Fatal(err) } // 创建可复用生成器 gen, err := chisel.NewGenerator[ConfigType]( chisel.WithMaxSize(64 * 1024), // 自定义块大小 chisel.WithCompressor(compressor), // 启用压缩 chisel.WithPlaceholder(placeholder),// 自定义占位符 chisel.WithSigner(signer), // 自定义签名器 chisel.WithObfuscator(obfuscator), // 自定义混淆器 ) if err != nil { log.Fatal(err) } // 流式处理大文件 err = gen.GenerateStream(ctx, inputReader, outputWriter, config)

说明:

  • Generate 会尽量保留模板文件的权限位,并通过临时文件 + rename 降低输出文件被部分写入的风险
  • Generate / GenerateStream 会在关键阶段检查 context.Context,支持取消

提取(Extractor)

客户端可直接使用根包入口,或显式构造可复用的 Extractor

// 直接提取(推荐) cfg, err := chisel.Extract[ConfigType](context.Background()) if err != nil { log.Fatal(err) } // 创建可复用提取器 ext, err := chisel.NewExtractor[ConfigType]() if err != nil { log.Fatal(err) } // 传入自定义占位符(字符串) cfg, err := ext.ExtractFrom(context.Background(), userPlaceholder) // 从任意 Reader 中扫描首个合法配置块 cfg, err := ext.ExtractFromReader(context.Background(), reader, chisel.PlaceholderSize())

说明:

  • ExtractFromReader 会扫描输入流中的首个合法配置块,而不是命中第一个 MagicMarker 就立即返回
  • 提取链路同样会检查 context.Context,在取消后尽早停止

错误处理

cfg, err := ext.Extract(ctx) if err != nil { switch err { case chisel.ErrNoConfig: log.Println("配置未被注入(占位符未替换)") case chisel.ErrInvalidMagic: log.Println("魔术标记无效") case chisel.ErrSignatureMismatch: log.Println("签名校验失败,配置可能被篡改") default: log.Fatalf("提取失败: %v", err) } }

辅助生成 API

// 生成 Ed25519 密钥对 pub, priv, err := chisel.GenerateEd25519KeyPair() // 生成混淆密钥 obfKey, err := chisel.GenerateObfuscationKey(32) // 生成占位符字节 placeholder, err := chisel.GeneratePlaceholder(chisel.MagicMarker, 64*1024)

高级功能

1. 启用压缩

gzip 压缩可显著减少配置大小,尤其是配置中包含重复数据时。

// 服务端:启用 gzip 压缩 gen, err := chisel.NewGenerator[Config]( chisel.WithCompressor(chisel.NewGZipCompressor()), ) if err != nil { log.Fatal(err) } err := gen.Generate(ctx, template, output, cfg) // 客户端:自动检测并解压(无需额外配置) ext, err := chisel.NewExtractor[Config]() if err != nil { log.Fatal(err) } cfg, err := ext.Extract(ctx) // 自动解压(如果配置被压缩)

2. 流式处理

处理大文件时使用流式接口:

注意:当前实现会将输入读入内存以查找占位符,适用于大多数场景。

templateFile, err := os.Open("client.template") if err != nil { log.Fatal(err) } defer templateFile.Close() outputFile, err := os.Create("client") if err != nil { log.Fatal(err) } defer outputFile.Close() gen, err := chisel.NewGenerator[Config]() if err != nil { log.Fatal(err) } err = gen.GenerateStream(ctx, templateFile, outputFile, cfg) if err != nil { log.Fatal(err) }

3. 自定义块大小

根据需要调整配置块大小:

// 增加块大小以容纳更大的配置 gen, err := chisel.NewGenerator[Config]( chisel.WithMaxSize(64 * 1024), ) if err != nil { log.Fatal(err) } // 减小块大小以节省空间 gen, err := chisel.NewGenerator[Config]( chisel.WithMaxSize(16 * 1024), ) if err != nil { log.Fatal(err) }

4. 自定义占位符

placeholder, err := chisel.GeneratePlaceholder(chisel.MagicMarker, 128*1024) if err != nil { log.Fatal(err) } // 生成器指定占位符 gen, err := chisel.NewGenerator[Config]( chisel.WithPlaceholder(placeholder), ) if err != nil { log.Fatal(err) } // 客户端提取时传入相同占位符 ext, err := chisel.NewExtractor[Config]() if err != nil { log.Fatal(err) } cfg, err := ext.ExtractFromBytes(ctx, placeholder)

说明:

  • GeneratePlaceholder 返回 []byte,因为占位符本质上是二进制块
  • 若占位符来自运行时生成结果,优先使用 ExtractFromBytes
  • ExtractFrom(string) 更适合源码内静态字符串占位符(如 const / var

工作原理

架构概览

编译时: ┌─────────────────────┐ │ 客户端源代码 │ │ + 占位符(32KB) │ └──────────┬──────────┘ │ go build ▼ ┌─────────────────────┐ │ 客户端二进制 │ │ - 包含占位符 │ └─────────────────────┘ 生成时: ┌──────────────┐ ┌──────────────────┐ ┌──────────────────┐ │ 配置结构体 │──────▶│ 服务端生成器 │──────▶│ 配置化客户端 │ └──────────────┘ │ - 序列化配置 │ │ - 占位符被替换 │ │ - 压缩(可选) │ │ - 配置已注入 │ │ - 混淆 │ └──────────────────┘ │ - 签名 │ └──────────────────┘ 运行时: ┌─────────────────────┐ │ 配置化客户端 │ └──────────┬──────────┘ │ 启动 ▼ ┌─────────────────────┐ │ 提取器 │ │ - 查找配置块 │ │ - 验证签名 │ │ - 反混淆 │ │ - 反序列化 │ └──────────┬──────────┘ │ ▼ ┌─────────────────────┐ │ 配置对象 │ │ (立即可用) │ └─────────────────────┘

配置块格式

┌─────────────────────────────────────────────────────────────┐ │ 配置块(32KB,可配置) │ ├─────────────────────────────────────────────────────────────┤ │ Header(20 字节) │ │ ├─ Magic (8B):BINCFG\x00\x02 │ │ ├─ Length (4B):数据块长度 │ │ ├─ Checksum (4B):CRC32 校验和 │ │ └─ Flags (4B):压缩/混淆/签名标记 │ ├─────────────────────────────────────────────────────────────┤ │ 数据块(可变长度,<= 块大小 - 20) │ │ ├─ 签名 (64B):Ed25519 签名 │ │ └─ 混淆数据: │ │ ├─ IV (16B):AES-CTR IV │ │ └─ JSON 数据(可选压缩) │ ├─────────────────────────────────────────────────────────────┤ │ 填充(0xFF,填充至块大小) │ └─────────────────────────────────────────────────────────────┘

安全特性

特性实现说明
签名Ed25519防篡改校验(默认)
混淆AES-CTR降低明文可读性(默认)
校验和CRC32快速检测损坏

混淆不提供真正保密性,仅用于降低明文可读性。

性能特性

性能基准

操作时间大小
Header 序列化< 1μs20 字节
配置序列化 (JSON)< 100μs取决于配置
混淆 (AES-CTR)< 100μs小配置
gzip 压缩< 10ms1MB 数据
完整生成流程< 50ms典型场景
完整提取流程< 50ms典型场景

二进制大小

客户端模板 (包含 32KB 占位符):~4.05MB - Go 二进制基础大小:~4.0MB - 占位符占用:32KB

测试报告

测试覆盖

当前主包覆盖率: 75.7%

核心模块: ✅ config.go (Header, Flags) ✅ crypto.go (Ed25519, AES-CTR) ✅ compress.go (gzip) ✅ placeholder.go (32KB) ✅ generator_api.go ✅ extractor_api.go

常见问题

Q: 数据能防篡改吗?

可以。默认开启 Ed25519 签名校验,任何修改都会导致验证失败。

Q: 数据能防止被读取吗?

混淆只能降低明文可读性,不能提供真正保密性。

Q: 配置会超过 32KB 怎么办?

// 自定义更大的占位符 placeholder, _ := chisel.GeneratePlaceholder(chisel.MagicMarker, 128*1024) // 使用自定义占位符 gen, err := chisel.NewGenerator[Config]( chisel.WithPlaceholder(placeholder), ) if err != nil { log.Fatal(err) }

设计决策

为什么使用 Ed25519 签名?

  • ✅ 防篡改校验简单可靠
  • ✅ 公钥可内置在客户端
  • ✅ Go 标准库内置,无依赖

为什么使用 AES-CTR 混淆?

  • ✅ 实现简单
  • ✅ 可避免明文直接暴露
  • ⚠️ 不提供真正保密性

贡献指南

欢迎提交 Issue 和 Pull Request!

许可证

MIT License - 详见 LICENSE 文件

相关文档

版本: v0.2.0 最后更新: 2025-10-29

About

二进制配置嵌入包 - 在编译后的二进制文件中嵌入和提取配置

Language
Go97.4%
Shell2.6%