首页
/ protobuf C 运行时强命名密钥解析:csharp/keys 下两个 .snk 文件的分工与使用

protobuf C 运行时强命名密钥解析:csharp/keys 下两个 .snk 文件的分工与使用

2026-09-05 11:12:25作者:田桥桑Industrious

本文以 csharp/keys/README.md 为线索,讲解 protobuf C# 运行时(Google.Protobuf)强命名机制中签名密钥与公钥文件各自的职责,并结合仓库中的项目文件与程序集元数据源码,说明这两个密钥在构建、测试与 InternalsVisibleTo 授权链路中的具体用法,读完后可独立理解并核验 .NET 强命名在本项目中的完整实现。

密钥目录与文档定位

protobuf 的 C# 实现位于 csharp/ 目录,其强命名密钥集中存放于 csharp/keys/,该目录包含三个文件:

  • Google.Protobuf.snk:完整密钥对文件,用于为 Google.Protobuf 程序集提供(赋予)强命名;
  • Google.Protobuf.public.snk:仅含公钥的文件,用于校验 Google.Protobuf 程序集的强命名;
  • README.md:对两个文件职责的一句话说明。

README.md 原文给出的关键信息有三点:其一,public.snk 是"校验强命名用的公钥";其二,Google.Protobuf.snk 是"提供强命名用的签名密钥";其三,"按照 Microsoft 的指引,签名密钥应当检入(check in)代码仓库"。最后一点常被误解为安全隐患,实际上它正是 .NET 强命名在开源项目中的常规做法,本文后文会展开说明。

强命名与 .snk 文件:两个文件的本质区别

强命名(Strong Name)是 .NET 程序集的身份标识机制:构建时编译器用私钥对程序集清单(含名称、版本、公钥、文化等信息)签名,生成 PublicKeyToken;运行时与工具链(如 GAC、InternalsVisibleTo 校验)可用公钥验证程序集确实由持有对应私钥的一方构建,且未被篡改。

.snk(SNiKey)是 .NET 键交换格式的二进制密钥文件。从 csharp/keys 下两个文件的实际大小即可区分其类型:

文件 大小 内容 用途
Google.Protobuf.snk 596 字节 完整 RSA 密钥对(含私钥) 构建期签名(AssemblyOriginatorKeyFile
Google.Protobuf.public.snk 160 字节 仅公钥 验证/比对公钥、供 InternalsVisibleTo 等场景引用

Google.Protobuf.public.snk 的文件头字节(00 24 00 00 04 80 00 00 94 00 00 00 06 02 ... R S A 1)可以确认它遵循标准公钥 BLOB 布局:24 即十进制 36,表示公钥体长度为 36 字节,随后是标志位、模长与 RSA1 算法标识符。这与 AssemblyInfo.cs 中硬编码公钥字符串的十六进制前缀 002400000480000094000000...52534131(即 RSА1 的 ASCII)完全一致,两处证据互相印证:public.snk 就是该公钥的二进制形态。而 596 字节的 Google.Protobuf.snk 则从尺寸判断为包含私钥的完整密钥对文件。

仓库中如何引用签名密钥:四个项目文件

签名配置通过 MSBuild 属性 SignAssemblyAssemblyOriginatorKeyFile 完成,仓库中共有四处项目文件引用同一密钥:

  1. 主运行时库 csharp/src/Google.Protobuf/Google.Protobuf.csproj
<AssemblyOriginatorKeyFile>../../keys/Google.Protobuf.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>

该项目的 VersionPrefix 为 3.37.0,目标框架为 netstandard2.0;net8.0(见同文件第 8、11 行),因此发布到 NuGet 的 Google.Protobuf 程序集均带强命名。

  1. 测试项目 csharp/src/Google.Protobuf.Test/Google.Protobuf.Test.csproj:同样设置 SignAssembly=true 并指向 ../../keys/Google.Protobuf.snk

  2. 测试用生成代码项目 csharp/src/Google.Protobuf.Test.TestProtos/Google.Protobuf.Test.TestProtos.csproj:同样指向 csharp/keys 下的签名密钥。

  3. 旧版本兼容性测试 csharp/compatibility_tests/v3.0.0/src/Google.Protobuf.Test/Google.Protobuf.Test.csproj:与当前测试项目保持相同的签名配置。

从源码结构看,测试项目与运行时库使用同一个密钥对签名并非偶然:测试程序集需要访问 Google.Protobuf 的内部成员(internal API),而 .NET 规定——当授予 InternalsVisibleTo 权限的程序集为强命名程序集时,被授权方也必须强命名,且授权声明中必须指明被授权方的公钥。这直接引出了下一节的实现细节。

InternalsVisibleTo 与公钥:AssemblyInfo.cs 中的条件编译

csharp/src/Google.Protobuf/Properties/AssemblyInfo.cs 展示了公钥在运行时代码中的落点:

#if SIGNING_DISABLED
[assembly: InternalsVisibleTo("Google.Protobuf.Test")]
#else
[assembly: InternalsVisibleTo("Google.Protobuf.Test, PublicKey=" +
    "002400000480000094000000060200000024000052534131000400000100010025800fbcfc63a1" +
    ...)]
#endif

这里的条件编译是理解两个 .snk 文件分工的关键:

  • 默认构建(未定义 SIGNING_DISABLED)走 #else 分支,InternalsVisibleTo 显式携带 PublicKey。这段硬编码公钥正是从 Google.Protobuf.public.snk 提取的十六进制表示,与上文文件头字节 0024... 开头完全对应。由于测试程序集也使用该密钥签名,其公钥与声明中的公钥匹配,编译器与运行时才会放行内部成员访问。
  • 若以 SIGNING_DISABLED 构建(本地调试或免签名场景),则退化为不带公钥的普通 InternalsVisibleTo,避免未签名程序集因公钥不匹配而编译失败。

从源码结构看,仓库中仅在 AssemblyInfo.cs 一处定义 SIGNING_DISABLED 条件;即该开关为构建时按需定义的 MSBuild 常量,而非提交在仓库中的默认配置。

为什么签名密钥可以检入仓库

README.md 明确援引 Microsoft 的指引:签名密钥应当 check in。其技术含义需要说清楚,以免误读:

  1. 强命名不等于代码签名(Authenticode)。强命名只验证"程序集由持有该密钥的一方构建且内容未被修改",并不建立发布者的数字信任链;因此将密钥随开源仓库公开,与 .NET Core 时代起微软对开源项目取消强制强命名的立场一致,不构成安全漏洞。
  2. 可复现构建的前提。任何克隆仓库的构建者都能用同一私钥签出公钥与 Token 完全一致的强命名程序集,测试中的 InternalsVisibleTo 公钥校验因此永远成立——这正是四份项目文件统一引用 csharp/keys/Google.Protobuf.snk 的意义。
  3. 职责分离仍然清晰。public.snk 只承担"验证"侧用途(校验公钥、Token 比对),签名侧独占 snk;两者不互相替代。

实践要点与历史脉络

结合当前仓库,核验这套强命名机制时可以关注以下几点:

  • 看构建声明:主库 Google.Protobuf.csprojSignAssembly=true + AssemblyOriginatorKeyFile 是签名的唯一来源;构建产物(netstandard2.0 与 net8.0 两个目标)的强命名 Token 应一致。
  • 看公钥一致性AssemblyInfo.cs 中的硬编码公钥、Google.Protobuf.public.snk 的 BLOB 内容、以及 Google.Protobuf.snk 派生的公钥,三者必须一致,否则强命名测试程序集将无法获得内部访问权限。
  • 看历史沿革csharp/CHANGES.txt 在 0.9.x 时期的发布记录中提到 "Changed to using secure .snk for releases",说明 C# 运行时的正式发布产物很早就改用受管的 .snk 签名流程;当前仓库的密钥目录即是该流程的延续。
  • 适用前提:本文结论基于当前仓库中 3.37.0 版本、netstandard2.0;net8.0 目标框架的构建配置;若以 SIGNING_DISABLED 编译,则 AssemblyInfo.cs 中的无公钥分支生效,公钥校验链路随之失效,属于调试用途的降级构建。

综上,csharp/keys 目录下两个 .snk 文件虽小,却完整承载了 protobuf C# 运行时"签名—验证—内部授权"链条的密钥基础:Google.Protobuf.snk 负责构建期签名,Google.Protobuf.public.snk 负责公钥校验,四份项目文件的统一签名配置与 AssemblyInfo.cs 中的条件编译公钥共同保证了运行时库与测试库之间的强命名内部可见性。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
504
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384