老王的“明记饭店”有一道镇店名菜“智能东坡肉”,每天限量一百份,食客排队两小时就为这一口。可有一天,对门“山寨楼”买通配送司机,在冷链车半路上把整箱“智能东坡肉”掉了包。老王尝一口就发现味道不对——肉没问题,但掺了“违禁调料”。追查时更傻眼:采购单没盖章、仓库没交接记录、配送车没封条、门店没验货流程。每一道环节都被动了手脚,却没有一道环节能发现。 AI 模型的处境和这道菜一模一样:从数据采集、训练调参、打包发布到部署上线,要经过无数双手。攻击者可以在预训练权重里投毒、在依赖库里塞后门、在网络传输中替换模型文件——没有“封条”、“配料表”和“交接记录”,你的模型可能早已不是你以为的那个。这篇文章就讲 MLOps 供应链安全的三大武器:模型签名(防拆封条)、SBOM(配料表)、Provenance(交接记录),以及把它们串成防线的验证流程、工具生态与落地实践。

岗位关联

模型投毒与供应链攻击是 AI 安全战场最隐蔽的威胁。掌握签名、SBOM、Provenance 三大机制,是 AI 安全工程师保障模型完整性与可追溯性的核心技能,也是通过安全审计的必备知识。

目录
  1. 可视化演示
  2. 供应链风险:最弱一环决定整体安危
  3. 模型签名:给保温箱贴上防拆封条
  4. SBOM清单:一张黑纸白字的配料表
  5. 来源溯源:从农场到餐桌的交接记录
  6. 验证流程:收货员的“三查三验”
  7. 工具生态:官方认证的印章局
  8. 落地实践:把防线搬进真实流水线
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示AI模型数字签名验证机制、SBOM软件物料清单构成及Provenance溯源链路,切换模式理解MLOps供应链安全的核心防护策略。

本节配有交互动效(进入视口后自动加载,约 3KB)。

模型数字签名与完整性验证 签名方用私钥对模型文件哈希值加密生成签名,验证方用公钥解密比对 非对称加密保证模型权重文件的完整性和来源真实性,防篡改 Sign(hash(M), privKey) → signature | Verify(pubKey, M, sig) → ✓/✗ 数字签名是模型供应链安全的第一道防线

供应链风险:最弱一环决定整体安危

明记饭店的掉包案让老王彻夜难眠。他复盘那条“菜篮子供应链”:采购员从农场进货,仓库管理员验收,配送司机运输,门店厨师接收——四个环节,每一道都有机会被做手脚,却没有一道能发现异常。就像一根铁链,断掉一个环,整条链就废了。老王总结出一个朴素的道理:防线的强度不取决于最强环节,而取决于最弱环节。

你的 AI 模型就是那箱“智能东坡肉”。一个模型从数据采集、清洗、训练、量化、打包、上传模型仓库,到被下游服务拉取、加载、部署——每一个步骤都有“人手”经过。攻击者不需要攻破你最后的生产服务器,他只要在供应链的任意一环动手脚就能得手。这也解释了为什么近年 AI 安全事故频繁发生在大模型上线之前:投毒往往发生在你“看不见的上游”。

现实中主要有三类攻击。一是模型投毒:攻击者把预训练权重替换成带后门的版本,或往训练数据里掺入“毒样本”(data poisoning),让模型在特定触发词下输出错误结果。二是依赖包劫持:用与你项目相似的名字上传恶意 PyPI 包(如“typo-squatting“),或直接攻破上游仓库篡改包内容,等你 pip install 时后门已经植入。三是中间人替换:利用明文 HTTP 传输或缓存投毒,在模型文件传输途中”掉包”。整体风险可以这样描述:

老王听完分析一拍桌子:与其去堵每一道防不胜防的暗门,不如给每个环节配上“可视化的真凭实据”——防拆封条、黑纸白字的配料表、签字画押的交接记录。这三样东西,正是模型签名、SBOM 与 Provenance。

P攻破=1−∏i=1n(1−pi)P_{\text{攻破}} = 1 - \prod_{i=1}^{n} (1 - p_i)
这个公式就是“木桶效应”的数学版:每个环节的被攻破概率是 p_i,“环节安全”的概率是 (1-p_i);所有环节都安全,整体才安全。环节越多、单环节越弱,整体被攻破的概率就越大。
  1. 单环节攻破概率p_i 表示供应链上第 i 个环节(数据、训练、打包、传输、部署)被攻破的概率。
    pip_i
  2. 单环节安全概率1 - p_i 是该环节不出事的概率,来自概率论中“对立事件”的补集运算。
    1−pi1 - p_i
  3. 全链路安全概率∏ 连乘表示“所有环节都安全”这件事同时成立的概率——只要有一环失守,乘积就会变小甚至归零。
    ∏i=1n(1−pi)\prod_{i=1}^{n} (1 - p_i)
  4. 整体风险用 1 减去“全安全概率”,得到“至少一个环节被攻破”的概率,这就是供应链的整体风险。
    P攻破=1−∏i=1n(1−pi)P_{\text{攻破}} = 1 - \prod_{i=1}^{n} (1 - p_i)

模型签名:给保温箱贴上防拆封条

老王请来一位防伪专家设计“克重封条”:封条上印着这一箱菜的总克重(比如 12.34kg),并且封条必须由主厨用专属钢印压上。任何人都能查验封条,但只有主厨能贴。谁要是拆箱换货,封条要么折断,要么克重对不上。老王问:钢印会不会太重、验起来太麻烦?专家说:我们只给“重量指纹”盖章,不称整箱菜。

所谓“重量指纹”,在计算机世界就是哈希函数(hash function):它能把任意大小的模型文件(几个 GB 的 .pth 或 .onnx)压缩成一个固定长度的指纹,比如 SHA-256 输出 64 位十六进制字符。文件只要改动一个比特,指纹就会面目全非。数字签名做的事情,就是用发布者的私钥对这个“指纹”加密,得到签名 σ;任何人都能用发布者的公钥解开签名、比对指纹,从而确认两件事:文件没被改过、确实是这位发布者签的。生成签名的过程:

实践中,cosign、sigstore-python 等工具可以直接对模型文件签名。模型格式无关紧要——无论是 PyTorch 的 .pth、ONNX 还是 Hugging Face 仓库里的权重,签名只是对“字节流”的哈希做加密。封条一贴,谁再掉包,下游一验便知。但老王很快意识到:封条只能证明“这箱菜没被换过”,却证明不了“这箱菜本身有没有毒”——于是他想到了配料表。

σ=Encsk(H(m))\sigma = Enc_{sk}\big( H(m) \big)
签名不是把整个模型加密,而是只加密“模型的指纹”。这样既保证签名体积小、验证速度快,又让任何细微改动都会反映在指纹上——这正是“克重封条”的精妙之处。
  1. 计算指纹对模型文件 m 取哈希,得到固定长度的摘要 H(m)。这一步相当于给整箱菜称重,得到“克重码”。
    H(m)H(m)
  2. 私钥加密Enc_sk 表示用签名者的私钥 sk 做加密运算。只有持有私钥的主厨才能“压钢印”,别人压不出来。
    Encsk(⋅)Enc_{sk}(\cdot)
  3. 签名产物σ 是最终签名,随模型一起发布。它是“私钥加密后的指纹”,验证时只需用公钥解开再比对。
    σ\sigma

SBOM清单:一张黑纸白字的配料表

封条解决“这箱菜还是不是原来那箱”,但解决不了“这箱菜含什么”。老王又接到顾客投诉:有人对花生过敏,吃完当场送医院。后厨翻遍整个厨房都找不到配料表——厨师只知道“放了秘制酱料”,说不清酱料里有啥。老王于是立了新规矩:每道菜必须附一张“黑纸白字的配料表”,每味调料、供应商、批次号、进货日期写得清清楚楚。

这张配料表,对应软件领域的 SBOM(Software Bill of Materials,软件物料清单)。对 AI 模型而言,“配料”包括:模型文件本身、训练用的 Python 依赖包(torch、transformers、numpy 等及各自版本)、基础模型或预训练权重、tokenizer 词表、甚至推理运行环境。一份标准 SBOM 可以看成一个“组件集合”——每个组件记录三样东西:包名 p_i、版本号 v_i、内容哈希 h_i(用于精确校验该组件是否被动过手脚):

有了配料表,安全团队就能在部署前自动比对新披露的 CVE(公共漏洞库):比如 transformers 4.36.0 被爆出高危漏洞,SBOM 一查就知道哪些模型在用,必须升级依赖、重新打包。需要特别提醒的是:SBOM 最好和签名绑在一起签名——否则攻击者偷偷改了依赖列表,光有配料表也等于白写。老王把这条记进手册:配料表要盖章,才算数。

B={(pi, vi, hi)}i=1nB = \left\{ (p_i,\ v_i,\ h_i) \right\}_{i=1}^{n}
SBOM 是“机器可读的配料表”,行业格式主要有 SPDX 和 CycloneDX 两种。它的价值在于:当某个依赖库爆出漏洞时,扫一遍 B 就能立刻知道哪些模型在“吃”这个有问题的配料。
  1. 组件三元组每个 (p_i, v_i, h_i) 描述一个依赖组件——包名回答“是什么”,版本号回答“哪个批次”,哈希回答“真伪如何”。
    (pi, vi, hi)(p_i,\ v_i,\ h_i)
  2. 集合枚举下标 i 从 1 遍历到 n,n 是模型全部依赖的数量,一个都不能少。
    {⋯ }i=1n\{ \cdots \}_{i=1}^{n}
  3. 整体清单B 就是整张配料表,机器可读、可扫描、可审计,漏洞库一更新就能自动比对。
    BB

来源溯源:从农场到餐桌的交接记录

封条和配料表都齐了,老王还差最后一样:交接记录。每一箱菜从农场出发,谁经手、什么时间、冷链车温度曲线如何,必须记在“交接本”上。而且这本子用的是“连环页”——每一页末尾压着上一页的页码,还要盖骑缝章。谁想撕页、改页、插页,都会在骑缝处留下破绽。

这就是 Provenance(来源追踪) 的思想。对模型而言,Provenance 记录的是“这个模型是怎么来的”:训练数据集的哈希、训练代码的 git commit、基础模型版本、超参数、训练环境、发起人身份。所有这些信息组成一条记录。关键在于链条结构:每条新记录都包含前一条记录的哈希,形成一本“连环账”:

工业界把 Provenance 做成了标准:SLSA(Supply-chain Levels for Software Artifacts,软件制品供应链等级)定义了从“无记录”到“完整可审计”的四个安全等级;in-toto 规范定义了“谁被允许对什么做什么”的元数据签名。训练时记录的数据血缘(data lineage)——哪个数据集、哪个版本、怎么清洗的——都可以沉淀为可验证的 attestation(证明书)。老王把“交接本”挂在厨房门口后,再有人质疑“这菜是不是用了走私的食材”,翻记录本就能当场对质。

Rn=(dn, H(Rn−1))R_n = \big( d_n,\ H(R_{n-1}) \big)
第 n 条溯源记录 R_n 由“本次内容 d_n”加上“上一条记录的哈希 H(R_{n-1})”组成。想篡改中间任何一条,后面所有记录的哈希都会对不上,篡改立刻暴露。这就是“骑缝章”在数字世界的样子。
  1. 本次内容d_n 是第 n 个环节的信息,如数据哈希、代码 commit、操作者、时间戳等。
    dnd_n
  2. 上一环的指纹H(R_{n-1}) 是前一条记录的哈希,把当前记录“焊”在前一条之后,形成环环相扣。
    H(Rn−1)H(R_{n-1})
  3. 链式绑定R_n 同时包含 d_n 和前驱哈希。任何一环被改动,后续记录的哈希校验全部失败。
    Rn=(dn, H(Rn−1))R_n = \big( d_n,\ H(R_{n-1}) \big)

验证流程:收货员的“三查三验”

封条、配料表、交接记录三样法宝齐了,老王把它们编成一套收货验货制度:每天食材到店,值班厨师必须三查——查封条是否完好、查配料表是否齐全、查交接记录是否连续、签名是否有效。任何一项不通过,整箱拒收,责任追到上一环。老王的原话是:“我不是把每个环节的人都当坏人,而是让好人不用背锅、坏人无处下手。”

MLOps 里的验证流程,就是把签名、SBOM、Provenance 串成流水线上的“收货验货制度”。具体来说:在 CI/CD 流水线的构建阶段生成模型包;发布阶段计算哈希、生成签名和 SBOM,并把 Provenance 记录写入溯源链;部署阶段(拉取或加载模型时)自动执行验证。验证的核心是一个三条件联合公式:

策略上有一条铁律:验证失败必须 fail closed(失败即阻断),而不是 fail open(失败也放行、仅告警)。因为供应链攻击恰恰希望“悄悄替换、无人察觉”——放行加告警等于告诉攻击者“你只要成功一次就行”。同时要管理好信任根:公钥从哪来?通常由公司内部 PKI 或 Sigstore 提供,公钥本身的获取链路也要走 HTTPS 和证书校验,防止“验证者被钓鱼”。这一步老王感触最深:验货员如果眼瞎,再好的封条也白搭。

valid=(H(m′)=?Decpk(σ))∧SBOM通过∧溯源链完整valid = \big( H(m') \stackrel{?}{=} Dec_{pk}(\sigma) \big) \land \text{SBOM通过} \land \text{溯源链完整}
在部署机上重新计算模型文件的哈希 H(m'),与“用公钥解开签名得到的原始哈希”比对;同时校验 SBOM 无高危漏洞、Provenance 链连续无断裂。三个条件全部满足,才允许放行。
  1. 重算哈希对部署机上的模型文件 m' 重新取指纹。m' 强调“当前实际拿到的文件”,可能与原始 m 不同。
    H(m′)H(m')
  2. 解开签名Dec_pk(σ) 表示用发布者的公钥 pk 解密签名,还原出签名时刻的原始指纹,证明“私钥持有者确实签过”。
    Decpk(σ)Dec_{pk}(\sigma)
  3. 比对相等符号 ?= 表示比较两边是否相等——检验“当前文件”与“签名时的文件”是否一致。
    H(m′)=?Decpk(σ)H(m') \stackrel{?}{=} Dec_{pk}(\sigma)
  4. 三条件与∧ 是逻辑“与”,签名比对、SBOM 通过、溯源链完整三者缺一不可,少一个都不放行。
    (⋅)∧(⋅)∧(⋅)(\cdot) \land (\cdot) \land (\cdot)

工具生态:官方认证的印章局

老王发现“专属钢印”有个麻烦:如果只有主厨一个人有钢印,主厨休假就没人能出菜;要是钢印被偷,整个信任体系瞬间崩塌。防伪专家给他支了一招:去找“市监局认证的印章局”。主厨每次需要盖章时,先实名验证身份,印章局签发一张短时有效的临时章,盖完即废;并且每次盖章都登记在公立的“印章台账”上,谁都能查,谁也删不掉。

这就是 Sigstore 的思路——云原生计算基金会(CNCF)孵化的软件签名基础设施,核心特点是 keyless(免密钥):开发者不需要自己保管长期私钥,而是用 OIDC 身份(如 GitHub 账号、公司邮箱)向证书颁发机构 Fulcio 申请短时 X.509 证书,用证书签名,签名记录写入公开透明的日志 Rekor。整个过程可以概括为:

常用工具链包括:cosign 用于对镜像、文件和模型进行签名与验证(cosign sign / cosign verify);sigstore-python 让 Python 生态直接集成;in-toto + attestation 用于记录并验证构建环节的元数据;TUF(The Update Framework)用于保护软件更新源的信任链。这些工具组合起来,覆盖“身份 → 证书 → 签名 → 透明日志 → 验证”的全流程。就像小区物业的“电子门禁 + 访客登记”:访客刷脸换临时门禁卡,进门记录自动上传云端,保安扫码即可确认“登记过、没超时、记录在案”。

cert=Fulcio(身份),σ=Enccert(H(m)),Rekor(σ)cert = Fulcio(\text{身份}),\quad \sigma = Enc_{cert}\big( H(m) \big),\quad Rekor(\sigma)
Fulcio 把“你的在线身份”和“这一次签名”绑定,证书十几分钟就过期,即使临时私钥泄露,攻击窗口也极小;Rekor 则是防篡改的透明账本,任何人都能审计“谁在什么时候签了什么”。
  1. 身份换证书Fulcio(身份) 根据你的 OIDC 身份签发短时证书,证书里写明了你的身份信息,相当于“实名换临时章”。
    Fulcio(身份)→certFulcio(\text{身份}) \to cert
  2. 证书签名Enc_cert 用证书对应的临时私钥对模型哈希加密,等价于“盖临时钢印”。
    σ=Enccert(H(m))\sigma = Enc_{cert}\big( H(m) \big)
  3. 写入账本Rekor(σ) 把签名和证书的哈希记入公开日志,防止“签完不认账”或“有人删日志”。
    Rekor(σ)Rekor(\sigma)

落地实践:把防线搬进真实流水线

老王把整套体系印成《明记供应链安全手册》,明文规定:食材到店没有封条的拒收,没有配料表的拒收,交接记录断页的拒收。三个月过去,再也没有一起掉包事件。山寨楼不死心,又试了一次“狸猫换太子”,结果第一关就被值班厨师的“克重封条”拦下——老王终于睡上了安稳觉。

落地到真实 MLOps 平台,核心是把签名、SBOM、Provenance 嵌进流水线的硬性关卡(quality gate),而不是可选的“锦上添花”。部署决策可以抽象成一个简单的“一票否决”策略:

实操建议有四条。第一,在模型注册表(如 MLflow、Hugging Face Hub、Kubeflow)的发布 API 里强制签名,未签名模型禁止入库。第二,密钥管理走 KMS 或 HSM,私钥不出硬件,定期轮换;签名证书要设有效期监控,防止过期证书被滥用。第三,按风险分级:面向用户的生成模型必须全量验证,内部实验模型可只验签名;分级策略要写进发布规范。第四,定期做红蓝对抗——红队尝试投毒替换模型,蓝队验证防线是否真的能拦住。工具方面优先选用 Sigstore + cosign + in-toto 的开源组合,避免自研加密算法。老王每天晨会第一件事是翻电子交接台账,看有没有缺页、有没有迟到记录。他常说:“信任不是一句口号,而是要让每一环都能被证明。签名证明这个包没被换过,SBOM 证明这包东西都是什么,溯源证明它是怎么走到这里的。”这,就是 MLOps 供应链安全的全部。

Deploy(m)={放行verify(m)=true阻断verify(m)=falseDeploy(m) = \begin{cases} \text{放行} & \text{verify}(m) = \text{true} \\[4pt] \text{阻断} & \text{verify}(m) = \text{false} \end{cases}
部署是一个“一票否决”的闸门:签名验证、SBOM 扫描、溯源审计任一失败,立即阻断,不允许“带病上线再补丁”。
  1. 待部署对象m 是要部署的模型连同它的签名、SBOM、Provenance 记录,缺一不可。
    mm
  2. 验证函数verify(m) 就是第五节讲的三条件联合验证,返回布尔值 true 或 false。
    verify(m)\text{verify}(m)
  3. 分支决策case 语句按验证结果二选一——true 放行进生产,false 阻断并告警回滚。
    {放行verify(m)=true阻断verify(m)=false\begin{cases} \text{放行} & \text{verify}(m) = \text{true} \\ \text{阻断} & \text{verify}(m) = \text{false} \end{cases}

AI 应用场景

模型注册中心接入签名验证

阶段:模型发布与部署

解决的问题:传统的模型注册中心只记录模型文件和版本号,攻击者一旦拿到权限,可以直接覆盖模型对象,下游服务毫无察觉地拉取到被投毒的模型。接入签名验证后,每次发布强制签名,每次拉取自动验签,验证失败即阻断。

具体案例:某金融科技公司用 MLflow 做模型仓库,红队演练发现可绕过权限直接替换存储对象。安全团队引入 cosign 对模型包签名,并在部署流水线中加入 verify 步骤和 SBOM 漏洞扫描。红队再次测试时,替换的模型包因哈希不匹配被拦截,演练报告标记为“防线有效”。

供应链漏洞应急响应(SBOM 驱动)

阶段:依赖管理与风险排查

解决的问题:模型依赖的 Python 包版本散落在多处,漏洞通告出来后,传统方式靠各团队手工排查“哪个模型用了哪个包”,耗时数天且容易漏。SBOM 让排查变成一条 SQL:按组件版本反查所有受影响模型。

具体案例:某 AI 平台在 Log4j 类高危漏洞爆发后,利用 CI 中自动生成的 SBOM 清单,一键生成“受影响模型列表”,并联动自动化流水线完成依赖升级、重新签名、重新发布,整个应急响应在 2 小时内完成,而手工排查的团队普遍需要一周。

合规审计中的模型溯源链

阶段:审计与追责

解决的问题:监管要求证明“这个模型是用合规数据训练的、训练代码经过评审”。普通日志容易被篡改或删除,Provenance 链式记录加签名,让审计员可以独立验证记录的完整性和真实性。

具体案例:某大模型公司在通过数据安全评估时,用 in-toto 生成训练 attestation,将数据哈希、代码 commit、训练启动命令全部链式绑定并签名。审计员拿到证明书后一键验签通过,原本需要两周的材料整理压缩到两天。

Python 演示

python78 行
"""MLOps model signing demo: sign, verify, tamper detect""" import hashlibimport jsonfrom pathlib import Path from cryptography.hazmat.primitives import hashesfrom cryptography.hazmat.primitives.asymmetric import padding, rsa  class ModelSigner:    """Sign and verify a model file with digital signature"""     def __init__(self):        self.private_key = rsa.generate_private_key(            public_exponent=65537, key_size=2048        )        self.public_key = self.private_key.public_key()     def compute_digest(self, model_path):        sha = hashlib.sha256()        with open(model_path, "rb") as f:            for chunk in iter(lambda: f.read(65536), b""):                sha.update(chunk)        return sha.hexdigest()     def sign_model(self, model_path, metadata):        digest = self.compute_digest(model_path)        signature = self.private_key.sign(            digest.encode("utf-8"), padding.PKCS1v15(), hashes.SHA256()        )        sbom = {            "model": str(model_path),            "version": metadata.get("version", "1.0.0"),            "sha256": digest,            "deps": metadata.get("deps", []),        }        return {"sbom": sbom, "signature": signature.hex()}     def verify_signature(self, model_path, signed):        sbom = signed["sbom"]        current = self.compute_digest(model_path)        if current != sbom["sha256"]:            return False, "model hash mismatch, maybe tampered"        try:            self.public_key.verify(                bytes.fromhex(signed["signature"]),                sbom["sha256"].encode("utf-8"),                padding.PKCS1v15(),                hashes.SHA256(),            )            return True, "signature valid, model trusted"        except Exception:            return False, "signature invalid, signer unknown"  def main():    model = Path("model_demo.bin")    model.write_bytes(b"RNN-LSTM-WEIGHTS-v3")     signer = ModelSigner()    signed = signer.sign_model(        model, {"version": "3.0.0", "deps": ["torch==2.1.0", "tokenizers==0.15"]}    )    print("=== SBOM ===")    print(json.dumps(signed["sbom"], indent=2))    print("=== verify before deploy ===")    ok, msg = signer.verify_signature(model, signed)    print(msg, "-> deploy:", "ALLOW" if ok else "BLOCK")     print("=== attacker replaced model file ===")    model.write_bytes(b"RNN-LSTM-WEIGHTS-v3-EVIL")    ok, msg = signer.verify_signature(model, signed)    print(msg, "-> deploy:", "ALLOW" if ok else "BLOCK")  if __name__ == "__main__":    main()
目的:完整演示“构建模型 → 签名 + 生成 SBOM → 部署前验证 → 攻击者篡改后验证失败”的全过程,输出结果为 ALLOW 或 BLOCK。
  1. sha = hashlib.sha256()创建 SHA-256 哈希对象,这是模型“称重机”的核心。
  2. for chunk in iter(lambda: f.read(65536), b""):分块读取大模型文件,避免一次性载入内存。
  3. digest = self.compute_digest(model_path)计算模型文件的 SHA-256 指纹。
  4. signature = self.private_key.sign(...)用私钥对指纹加密,生成“专属钢印”签名。
  5. if current != sbom["sha256"]:重算哈希与 SBOM 中记录的哈希比对,不一致立即判定疑似篡改。
  6. self.public_key.verify(...)用公钥验签,确认签名确实来自持有私钥的发布者。
  7. model.write_bytes(b"RNN-LSTM-WEIGHTS-v3-EVIL")模拟攻击者替换模型文件,验证立即失败。
  8. ok, msg = signer.verify_signature(model, signed)部署前的统一验证入口,返回是否放行。

常见误区

误区给模型算一个 SHA-256 值记下来,验证时比对一下,这不就是签名了吗?我没用私钥,不也检测出了篡改?

正解哈希只能防“意外损坏”,防不了“故意篡改”。攻击者替换模型后,可以顺手把记录的哈希也一起改掉——就像小偷换货后把仓单上的克重也改了。数字签名等于“哈希 + 私钥加密”:只有持有私钥的人能生成有效签名,验证方用公钥就能确认“文件没被动过 + 确实出自签名者”。克重封条必须由主厨用专属钢印压上,才有防伪意义。

为何常见:哈希和签名都输出一串十六进制字符,形式上非常像;很多教程用“校验文件完整性”来简化讲解签名,导致初学者把两者划了等号。

误区打包时生成一份 SBOM 文件存起来,以后就不用管了。反正依赖都写在里面了,一劳永逸。

正解SBOM 是“快照”而不是“遗嘱”。模型发布后,依赖库会更新、会爆出新的 CVE(比如 Log4j 事件),旧 SBOM 无法反映新风险。正确做法是每次发布都重新生成 SBOM,并随模型一起签名;同时定期扫描 SBOM 中的组件版本,发现漏洞立即升级、重新打包、重新签名。

为何常见:人们习惯把文档看作一次性产物,但供应链是动态的。配料表必须跟着菜谱一起更新,过期配料表比没有更危险。

误区验证签名通过等于模型百分百安全,可以直接放心部署。

正解签名只保证“这个包确实是持有私钥的人签发的、内容没被篡改”,但保证不了“签名者的代码本身无毒”。如果私钥泄露、签名证书过期、甚至验证逻辑本身被替换(攻击者同时改了验证脚本),签名就形同虚设。签名是“必要不充分”,还要配合 SBOM 扫描、Provenance 审计和运行时监控,层层设防。

为何常见:安全工具常被当作“银弹”。人们容易把一层防御当成全部防御,忽略了信任根本身也需要保护——钢印再结实,验货员眼瞎也没用。

误区溯源不就是把训练数据、代码版本、操作人记到日志里吗?我们项目一直有日志,早就在做溯源了。

正解普通日志可以事后被篡改或删除,没有任何防伪能力。Provenance 要求链式哈希绑定:每条记录包含上一条记录的哈希,改任一一环,整条链断裂;同时记录要落到防篡改存储(如 Rekor 透明日志)并配合签名。这不只是“记日志”,而是“记账 + 防伪”。

为何常见:日常日志系统太常见,人们容易低估“可被篡改的日志”和“防篡改的溯源链”之间的本质差别——就像随手写的便条和律师见证的合同,法律效力完全不同。

误区cosign、Sigstore 这些是给 Docker 镜像用的,模型文件是 .pth、.onnx,没法用它们签名。

正解cosign 的本质是“对任意二进制 + 附加元数据”签名,模型文件同样适用。MLOps 社区已经广泛用 cosign sign 给模型文件签名,配合 in-toto attestation 记录模型元数据;sigstore-python 更是为 Python/ML 生态量身定做,几行代码就能集成。

为何常见:Sigstore 的官方文档和示例大多围绕容器镜像展开,让大家误以为它是容器专属工具——就像以为“电子秤只能称猪肉”,其实牛肉、羊肉一样能称。

面试题

Q1. 数字签名能够保证模型文件具有哪两个性质?

单选题 · easy

  • A.机密性与可移植性
  • B.完整性与身份认证← 正确答案
  • C.可用性与高性能
  • D.可解释性与公平性

答案:B。数字签名 = 私钥加密哈希。公钥解开签名后比对哈希,能证明“文件没被改过(完整性)”和“确实由持私钥者签发(身份认证)”。C、D 与安全无直接关系;A 中机密性需要的是加密而不是签名。

Q2. SBOM(软件物料清单)的核心作用是什么?

单选题 · easy

  • A.对模型文件进行加密压缩
  • B.列出全部依赖组件与版本← 正确答案
  • C.记录训练时的 GPU 利用率
  • D.提升模型的推理吞吐量

答案:B。SBOM 就像菜谱上的配料表,列出模型依赖的每个包、版本和哈希,方便漏洞排查与合规审计。A 是加密工具的事,C 是资源监控,D 是性能优化,都和“清单”无关。

Q3. 验证模型签名时,验证方需要持有?

单选题 · mid

  • A.签名者的私钥
  • B.签名者的公钥← 正确答案
  • C.模型的训练数据
  • D.模型的完整代码

答案:B。签名用私钥、验证用公钥——钢印只有主厨能盖,但任何人都能用官方图册验钢印。验证方持有私钥会导致整个签名体系崩溃;训练数据和代码与签名验证无关。

Q4. 关于 SHA-256 哈希函数,下列说法正确的是?

单选题 · mid

  • A.不同输入可能大量碰撞输出
  • B.输出固定256位且极难碰撞← 正确答案
  • C.可由哈希值反推出模型内容
  • D.哈希值长度随文件大小变化

答案:B。SHA-256 把任意长度输入映射为固定 256 位输出,设计上“不同输入算出的哈希相同”在计算上不可行(抗碰撞性),且单向不可逆。A 是碰撞攻击,C 违反单向性,D 违背定长输出。

Q5. 关于 Provenance 链式记录,下列描述正确的是?

单选题 · hard

  • A.每条记录包含前一条记录的哈希← 正确答案
  • B.记录顺序可以任意重排不影响安全
  • C.只需记录模型训练的损失数值
  • D.保存在普通文本日志就足够安全

答案:A。链式哈希是防篡改的关键“连环页”:每条记录焊上前一条的哈希,改任何一条都会让后续所有记录的哈希对不上。因此顺序不能乱、内容不能只记损失数值、也不能放在可被删改的普通日志里。

Q6. 模型签名可以抵御以下哪些攻击?(多选)

多选题 · mid

  • A.中间人传输时替换模型文件← 正确答案
  • B.攻击者入侵注册中心后投毒← 正确答案
  • C.签名私钥泄露后的冒名签名
  • D.训练数据未脱敏导致隐私泄露

答案:AB。签名保证“包没被换 + 确实由私钥持有者签发”。A 和 B 都属于替换模型文件,会导致哈希比对失败而被拦截。C 是“信任根失守”,签名防不住私钥泄露;D 是数据隐私问题,与模型完整性无关。

Q7. Sigstore 的 keyless(免密钥)签名机制核心是?

单选题 · hard

  • A.短时证书绑定身份,无需长期私钥← 正确答案
  • B.使用对称密钥加密模型文件内容
  • C.把所有模型存进私有本地仓库
  • D.完全取消身份验证直接匿名签名

答案:A。keyless 通过 OIDC 身份向 Fulcio 申请几分钟有效的短时证书,用证书签名后记录进 Rekor,避免长期私钥的保管难题。B 描述的是加密不是签名,C 与签名无关,D 与“实名换证书”背道而驰。

Q8. 部署前验证发现 SBOM 中某依赖存在高危漏洞,正确处理是?

单选题 · hard

  • A.忽略警告先上线后续再打补丁
  • B.阻断部署升级依赖后重新签名← 正确答案
  • C.直接从 SBOM 中删除该依赖条目
  • D.将漏洞评级改为低危强行通过

答案:B。SBOM 的价值就是让风险“看得见”,发现高危应阻断部署(fail closed),升级依赖后重新构建、重新签名、生成新 SBOM。A 和 D 是自欺欺人;C 删条目等于烧掉配料表,是掩耳盗铃。

Q9. 下列哪些属于典型的 AI 供应链攻击?(多选)

多选题 · hard

  • A.恶意 PyPI 包在安装时植入后门← 正确答案
  • B.泄露的预训练权重被替换上传← 正确答案
  • C.员工误删服务器上的模型文件
  • D.攻破代码仓库篡改训练代码← 正确答案

答案:ABD。供应链攻击指“在上游制造恶意改动,让下游无感知地引入”。A 是依赖投毒,B 是模型仓库投毒,D 是训练代码投毒,均符合定义。C 是误操作事故,不是攻击者的蓄意行为。

Q10. 构建可信模型供应链时,下列做法正确的有哪些?(多选)

多选题 · extreme

  • A.构建产物在流水线中强制签名← 正确答案
  • B.验证失败时放行并仅记录告警
  • C.模型注册中心留存完整溯源链← 正确答案
  • D.部署前校验签名并扫描 SBOM← 正确答案

答案:ACD。A、C、D 分别是签名、溯源、验证三件套的标准落地。B 是 fail-open 策略,会让投毒悄悄潜入生产环境,必须改为“失败即阻断”(fail-closed),这正是验证流程中的铁律。