Git Diff 解密与导出指南

作者:Chdon 发布时间: 2026-08-18 阅读量:2 评论数:0

Git 加密源码 Diff 解密与导出指南

1. 背景

在某些源码加密环境中,代码文件以及 Git 历史提交中的 Blob 内容会以加密形式存在。

常见现象:

  • Visual Studio 中可以正常查看源码或比较差异;
  • git diff / git show 在命令行中可能显示:
Binary files a/xxx.cs and b/xxx.cs differ
  • 原因通常不是 Git Diff 本身失效,而是 Git 读取到的是加密后的字节流;
  • 加密软件可能只允许特定、已授权的进程读取明文。

目标是:

  1. 不修改 Git 自身的 Diff 算法;
  2. 让 Git 在比较历史 Blob 时,通过已授权的辅助程序获得明文;
  3. 最终导出正常的 unified diff;
  4. 避免 Binary files differ
  5. 正确处理中文和 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. 安全建议

建议保持以下原则:

  1. 使用组织正式授权的解密程序或白名单机制;
  2. 辅助程序只负责“文件 → stdout”;
  3. 不把明文写入长期临时目录;
  4. 不在 stdout 写调试日志;
  5. 不开启不必要的 textconv 明文缓存;
  6. .git/info/attributes 优先于共享 .gitattributes
  7. 导出的 .diff 本身包含明文源码,应按源码安全策略管理;
  8. 如果加密系统提供官方 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。

评论