Git 加密源码 Diff 解密与导出指南
1. 背景
在某些源码加密环境中,代码文件以及 Git 历史提交中的 Blob 内容会以加密形式存在。
常见现象:
- Visual Studio 中可以正常查看源码或比较差异;
git diff/git show在命令行中可能显示:
Binary files a/xxx.cs and b/xxx.cs differ
- 原因通常不是 Git Diff 本身失效,而是 Git 读取到的是加密后的字节流;
- 加密软件可能只允许特定、已授权的进程读取明文。
目标是:
- 不修改 Git 自身的 Diff 算法;
- 让 Git 在比较历史 Blob 时,通过已授权的辅助程序获得明文;
- 最终导出正常的 unified diff;
- 避免
Binary files differ; - 正确处理中文和 UTF-8 编码。
2. 推荐方案概览
推荐使用 Git 官方支持的 textconv 自定义 Diff Driver。
整体链路:
Git Blob(加密内容)
│
▼
Git 生成临时文件
│
▼
已授权的 DecryptCat.exe
│
├─ 读取临时文件
├─ 获得解密后的源码
└─ 明文写到 stdout
│
▼
Git textconv
│
▼
Git 按文本生成 Diff
│
▼
change.diff
这里的重点是:
Git 仍负责 Diff;辅助程序只负责把“输入文件”转换成“可比较的明文文本”。
不要把调试信息写到 stdout,否则调试文本也会进入 Diff。
3. 前置条件
实施前应确认:
- 当前机器上的加密系统允许某个正式授权/白名单辅助程序读取源码明文;
- 该辅助程序读取 Git 生成的临时文件时,也能够得到明文;
- 使用此方案符合组织的源码保护、安全和审计要求。
建议让辅助程序只具备最小能力:
输入:一个文件路径
输出:该文件对应的明文内容到 stdout
它不需要:
- 修改源文件;
- 修改 Git 对象;
- 创建长期明文副本;
- 承担 Git Diff 逻辑。
4. 编写 DecryptCat.exe
4.1 最小 C# 实现
示例:
using System.Text;
internal class Program
{
static int Main(string[] args)
{
if (args.Length != 1)
{
Console.Error.WriteLine("Usage: DecryptCat.exe <file>");
return 1;
}
try
{
string path = args[0];
// 如果透明加密系统允许当前进程读取明文,
// 则这里得到的 content 应为解密后的源码。
string content = File.ReadAllText(path);
// stdout 统一使用 UTF-8,无 BOM。
Console.OutputEncoding = new UTF8Encoding(false);
// 只能把源码内容写到 stdout。
Console.Write(content);
return 0;
}
catch (Exception ex)
{
// 错误和日志必须写到 stderr。
Console.Error.WriteLine(ex);
return 2;
}
}
}
4.2 stdout 和 stderr 的区别
Git textconv 会把 stdout 当成源码文本。
正确:
Console.Write(content);
Console.Error.WriteLine("debug message");
错误:
Console.WriteLine("正在读取文件...");
Console.Write(content);
上面的“正在读取文件...”会被 Git 当成源码的一部分参与 Diff。
因此规则是:
源码正文 -> stdout
日志/错误 -> stderr
4.3 UTF-8 严格模式
如果项目明确规定所有源码都是 UTF-8,可使用:
using System.Text;
internal class Program
{
static int Main(string[] args)
{
if (args.Length != 1)
return 1;
try
{
var utf8 = new UTF8Encoding(
encoderShouldEmitUTF8Identifier: false,
throwOnInvalidBytes: true);
string content = File.ReadAllText(args[0], utf8);
Console.OutputEncoding = new UTF8Encoding(false);
Console.Write(content);
return 0;
}
catch (Exception ex)
{
Console.Error.WriteLine(ex);
return 2;
}
}
}
优点:
- 输出明确为 UTF-8;
- 不输出 BOM;
- 如果输入不是合法 UTF-8,会直接报错,而不是默默生成乱码。
如果历史源码可能存在 GBK、ANSI、UTF-16 等编码,则需要另外增加编码识别逻辑。
5. 先测试 DecryptCat
不要一开始就接 Git。
先测试工作区文件:
C:\Tools\GitTextConv\DecryptCat.exe D:\Repo\src\Test.cs
终端应该显示正常源码,例如:
public class Test
{
string message = "中文测试";
}
然后验证输出:
C:\Tools\GitTextConv\DecryptCat.exe D:\Repo\src\Test.cs
重点检查:
- 英文正常;
- 中文正常;
- 没有额外日志混入;
- 没有乱码。
6. 配置 Git Attributes
推荐优先使用:
.git/info/attributes
而不是仓库根目录的:
.gitattributes
原因:
- 只影响本地仓库;
- 不会被提交;
- 不会影响其他开发者。
例如 C# 项目:
*.cs diff=decryptsrc
C/C++ 项目:
*.c diff=decryptsrc
*.cpp diff=decryptsrc
*.h diff=decryptsrc
*.hpp diff=decryptsrc
多个类型:
*.cs diff=decryptsrc
*.java diff=decryptsrc
*.ts diff=decryptsrc
*.js diff=decryptsrc
*.c diff=decryptsrc
*.cpp diff=decryptsrc
*.h diff=decryptsrc
*.hpp diff=decryptsrc
也可以限定目录:
src/** diff=decryptsrc
7. 注册 Git textconv Driver
进入仓库:
cd D:\Repo
配置:
git config --local diff.decryptsrc.textconv C:/Tools/GitTextConv/DecryptCat.exe
检查:
git config --local --get diff.decryptsrc.textconv
预期:
C:/Tools/GitTextConv/DecryptCat.exe
8. 检查 Attributes 是否匹配
执行:
git check-attr diff -- src/Test.cs
正常应该显示:
src/Test.cs: diff: decryptsrc
如果是:
src/Test.cs: diff: unspecified
说明 .git/info/attributes 没有正确匹配。
常见原因:
- 文件后缀没有配置;
- 路径规则写错;
- 修改了错误仓库的
.git/info/attributes; - 当前目录不是预期 Git 仓库。
9. 验证 Git Diff
选择一个确定发生过源码修改的文件:
git diff --textconv HEAD~1 HEAD -- src/Test.cs
原本可能显示:
Binary files a/src/Test.cs and b/src/Test.cs differ
配置正确后,应该显示类似:
diff --git a/src/Test.cs b/src/Test.cs
--- a/src/Test.cs
+++ b/src/Test.cs
@@ -10,7 +10,8 @@
public void Foo()
{
- OldMethod();
+ NewMethod();
+ AnotherMethod();
}
如果这里已经正常,说明核心方案已经成功。
10. 导出 Diff
10.1 比较两个 Commit
git diff --no-color --textconv OLD_COMMIT NEW_COMMIT
例如:
git diff --no-color --textconv HEAD~1 HEAD
10.2 指定文件
git diff --no-color --textconv OLD_COMMIT NEW_COMMIT -- src/Test.cs
10.3 指定目录
git diff --no-color --textconv OLD_COMMIT NEW_COMMIT -- src/
10.4 导出某次提交自身的改动
可以使用:
git diff --no-color --textconv COMMIT^ COMMIT
例如:
git diff --no-color --textconv abc123^ abc123
也可以使用:
git show --no-color --textconv abc123
如果希望所有导出逻辑统一,建议采用:
git diff A B
这种形式。
11. 推荐使用 --output 导出文件
在 PowerShell 环境下,不建议优先使用:
git diff ... > change.diff
因为某些 PowerShell 版本的重定向会介入文本编码处理,容易导致中文乱码。
推荐直接让 Git 写文件:
git diff `
--no-color `
--textconv `
--output=change.diff `
HEAD~1 `
HEAD
指定文件:
git diff `
--no-color `
--textconv `
--output=change.diff `
HEAD~1 `
HEAD `
-- src/Test.cs
指定目录:
git diff `
--no-color `
--textconv `
--output=change.diff `
OLD_COMMIT `
NEW_COMMIT `
-- src/
推荐最终统一成:
git diff --no-color --textconv --output=<文件> <旧Commit> <新Commit> -- <路径>
12. 中文乱码排查
乱码通常可能发生在以下链路:
加密文件
│
▼
DecryptCat 读取
│
▼
DecryptCat stdout
│
▼
Git textconv
│
▼
Git diff 输出
│
▼
PowerShell / 文件
建议逐层定位。
12.1 第一步:检查 DecryptCat
执行:
C:\Tools\GitTextConv\DecryptCat.exe D:\Repo\src\Test.cs
如果这里中文已经乱码:
涓枃娴嬭瘯
那么问题在:
File.ReadAllText()的输入编码;- stdout 编码;
- 原始文件本身不是 UTF-8。
此时与 Git 无关。
12.2 第二步:检查 Git textconv
执行:
git diff --textconv HEAD~1 HEAD -- src/Test.cs
如果终端中文正常:
- string text = "旧中文";
+ string text = "新中文";
说明:
- DecryptCat 正常;
- Git textconv 正常。
如果终端这里已经乱码,则继续检查 DecryptCat 输出编码。
12.3 第三步:检查文件导出
如果终端正常,但:
git diff ... > change.diff
之后文件乱码,优先改成:
git diff --no-color --textconv --output=change.diff HEAD~1 HEAD
然后用 VS Code 打开,检查右下角编码是否为:
UTF-8
13. 推荐的完整操作顺序
1. 准备已授权 DecryptCat.exe
│
▼
2. DecryptCat 单独读取源码
│
├─ 英文是否正常?
├─ 中文是否正常?
└─ stdout 是否只有源码?
│
▼
3. 配置 .git/info/attributes
│
▼
4. 配置 diff.decryptsrc.textconv
│
▼
5. git check-attr 验证匹配
│
▼
6. git diff --textconv 验证
│
├─ 是否仍显示 Binary files differ?
└─ 中文是否正常?
│
▼
7. 使用 --output 导出 .diff
14. 一套可直接复制的配置
假设:
仓库:
D:\Repo
解密程序:
C:\Tools\GitTextConv\DecryptCat.exe
进入仓库:
cd D:\Repo
在:
D:\Repo\.git\info\attributes
加入:
*.cs diff=decryptsrc
*.cpp diff=decryptsrc
*.h diff=decryptsrc
执行:
git config --local diff.decryptsrc.textconv C:/Tools/GitTextConv/DecryptCat.exe
验证:
git check-attr diff -- src/Test.cs
测试:
git diff --textconv HEAD~1 HEAD -- src/Test.cs
最终导出:
git diff `
--no-color `
--textconv `
--output=change.diff `
HEAD~1 `
HEAD `
-- src/
15. 常见问题排查
问题 1:仍然显示 Binary files differ
检查:
git check-attr diff -- src/Test.cs
应该是:
src/Test.cs: diff: decryptsrc
然后检查:
git config --local --get diff.decryptsrc.textconv
确认程序路径正确。
再直接执行 DecryptCat:
C:\Tools\GitTextConv\DecryptCat.exe <测试文件>
问题 2:DecryptCat 能读工作区,但 Git Diff 不行
可能说明:
解密软件可以解密正常项目路径中的文件,但不能解密 Git 为历史 Blob 创建的临时文件。
此时 textconv 本身没有问题,而是解密边界限制导致。
需要进一步确认加密系统是否依赖:
- 文件路径;
- 文件标签;
- ADS;
- 文件系统元数据;
- 加密驱动状态;
- 受信任目录。
如果临时文件无法被正式授权的辅助程序解密,则需要使用加密系统提供的受支持接口、CLI 或 SDK,而不是继续调整 Git 的文本判断参数。
问题 3:直接使用 git diff --text 仍然无效
例如:
git diff --text
--text 只是告诉 Git:
强制把内容当作文本比较。
它不能把密文变成明文。
因此:
--text
解决的是:
Git 的 Binary 判断
而:
textconv + DecryptCat
解决的是:
Git 实际拿到的内容仍然是密文
两者不是同一个问题。
问题 4:中文乱码
按以下顺序排查:
DecryptCat 直接输出
↓
git diff --textconv
↓
git diff --output=change.diff
↓
编辑器打开 change.diff
不要一次跨多层定位。
16. 关于 cachetextconv
不建议在加密源码环境主动启用:
diff.decryptsrc.cachetextconv = true
因为 textconv 的结果是解密后的源码。
在源码保护环境中,通常应避免增加不必要的明文缓存和持久化范围。
17. 安全建议
建议保持以下原则:
- 使用组织正式授权的解密程序或白名单机制;
- 辅助程序只负责“文件 → stdout”;
- 不把明文写入长期临时目录;
- 不在 stdout 写调试日志;
- 不开启不必要的 textconv 明文缓存;
.git/info/attributes优先于共享.gitattributes;- 导出的
.diff本身包含明文源码,应按源码安全策略管理; - 如果加密系统提供官方 SDK、CLI 或 Git 集成,应优先使用官方能力。
18. Textconv 的限制
textconv 生成的 Diff 主要用于:
- 人工 Code Review;
- 导出差异;
- 代码审计;
- AI 分析;
- 查看历史变更。
它不保证一定可以直接:
git apply change.diff
因为 Git 比较的是“转换后的文本”,而不是仓库中的原始 Blob 字节。
如果最终需求是:
生成一个能够重新
git apply的 Patch
则需要单独设计明文仓库或可逆转换流程。
19. 最终推荐命令
日常查看:
git diff --textconv HEAD~1 HEAD
指定文件:
git diff --textconv HEAD~1 HEAD -- src/Test.cs
导出目录:
git diff `
--no-color `
--textconv `
--output=change.diff `
OLD_COMMIT `
NEW_COMMIT `
-- src/
推荐将这一条作为标准 Diff 导出命令:
git diff --no-color --textconv --output=<diff文件> <旧Commit> <新Commit> -- <指定路径>
20. 核心结论
整个方案可以概括为:
不要让 Git 自己“解密”
│
▼
让 Git 通过 textconv
调用一个已授权的明文读取程序
│
▼
程序把源码写到 stdout
│
▼
Git 使用正常文本 Diff
│
▼
通过 --output 导出 UTF-8 Diff
如果出现问题,优先按以下四项定位:
A. attributes 是否匹配?
B. textconv 是否真的执行?
C. DecryptCat 是否真正拿到明文?
D. stdout / 导出文件编码是否正确?
通常只要这四层都正常,就可以稳定地把加密 Git 历史中的代码差异导出为可读文本 Diff。