做东西上线发布的时候,最怕发出去的包被人改了内容,或者混了恶意代码,这时候不仅帮不了人,还会坑到用的人。那怎么保证发出去的东西是对的,而且没人动过?核心就是两个东西:数字签名和SBOM管理,把它们做进发布的强制校验里,就能搭起一条没法篡改的交付链路。

一、为什么要在发布时做这俩校验?

我之前在一家互联网公司做运维工具,就踩过坑。那次我们要给全公司发布一个批量备份员工本地数据的工具,结果后来有人反馈,用了工具之后本地的文件被删了。查了半天才发现,有人把我们的发布包改了,加了恶意代码,然后偷偷发在了内部群里。那个改包的人没什么恶意,就是想恶作剧,但后果很严重,很多同事找不回数据,还怪我们的工具不靠谱。

从那之后我们就想,怎么避免这种事?总不能每次发包都找几个人手动检查内容吧?工作量太大还容易漏。后来我们找到两个靠谱的办法,把它们做成发布的强制校验,从此再也没出过这种事。

二、先搞懂这俩到底是啥(别搞复杂)

很多人一听数字签名、SBOM就头疼,觉得是高大上的技术,其实用通俗的话讲,就是俩非常实用的小工具。

2.1 数字签名:相当于你的专属私章

你平时盖合同用私章,私章只有你有,别人没法伪造。数字签名也是一样的:你有一对密钥,一个是私钥(相当于你的私章,只有你能拿),一个是公钥(相当于私章的样子,所有人都能看到)。你要发文件的时候,用私钥“盖个章”,别人拿到文件和这个章的印子(签名),用你的公钥就能验证两个事:一是这个章是不是你盖的(证明是你发的),二是文件有没有被人改过(如果改了,章就对不上)。

2.2 SBOM:相当于软件的配料表

你点外卖的时候,商家会给你一张配料表,写清楚有什么菜、调料、分量,甚至食材的品牌。软件的SBOM(软件物料清单)就是这个意思,列清楚这个软件用到的所有东西:比如你自己写的代码、引用的第三方库、甚至Python的标准库,还有每个东西的版本号。万一软件出了问题,你能顺着配料表查到是哪个“配料”出了问题,也能避免用了不该用的恶意组件。

三、具体落地的步骤(用Python实现,简单好懂)

我们就拿一个小例子来说明:要发布一个批量整理本地文件的Python工具,打包成zip包。接下来的所有操作都是围绕这个工具展开,用单一的Python技术栈,不会混别的东西。

3.1 第一步:生成安全的密钥对

首先得有私钥和公钥,用Python的Crypto库就能生成,2048位的密钥足够安全,日常使用完全够。

from Crypto.PublicKey import RSA

# 生成RSA2048位密钥对,私钥仅自己保存,公钥发布给所有需要使用的人
key = RSA.generate(2048)
# 导出私钥,实际项目中要加密存储,这里仅做示例
private_key = key.export_key(passphrase="你的私钥密码,实际项目要设置")
with open("private_key.pem", "wb") as f:
    f.write(private_key)
# 导出公钥,公开给所有人用于校验
public_key = key.publickey().export_key()
with open("public_key.pem", "wb") as f:
    f.write(public_key)

这段代码会在本地生成两个文件:private_key.pem(私钥)和public_key.pem(公钥),私钥一定要藏好,不能丢也不能随便给人。

3.2 第二步:给发布包做数字签名

现在我们有了一个发布包,比如叫file_sort.zip,接下来用私钥给它签名,生成一个签名文件file_sort.sig

from Crypto.Signature import pkcs1_15
from Crypto.Hash import SHA256

# 读取私钥,这里用刚才生成的私钥文件
with open("private_key.pem", "rb") as f:
    private_key = RSA.import_key(f.read(), passphrase="你的私钥密码")

# 读取待发布的包文件
with open("file_sort.zip", "rb") as f:
    package_data = f.read()

# 用SHA256算法生成包的哈希值,再用私钥签名
hash_obj = SHA256.new(package_data)
signature = pkcs1_15.new(private_key).sign(hash_obj)

# 把签名保存到单独的文件里,和发布包一起发布
with open("file_sort.sig", "wb") as f:
    f.write(signature)

签名做完之后,别人拿到你的file_sort.zipfile_sort.sig,就能校验是不是你发的,有没有被改过。

3.3 第三步:生成SBOM

SBOM其实就是个清单,我们手动给刚才的工具做个简单的SBOM,列清楚所有用到的组件,做成JSON格式:

{
  "bomFormat": "SimpleSBOM",
  "toolName": "批量文件整理工具",
  "version": "1.0.0",
  "releaseDate": "2024-05-20",
  "components": [
    {"name": "os", "type": "Python标准库", "version": "内置"},
    {"name": "shutil", "type": "Python标准库", "version": "内置"},
    {"name": "file_sort.py", "type": "自定义代码", "version": "1.0.0"},
    {"name": "requests", "type": "第三方依赖库", "version": "2.31.0"}
  ]
}

这个SBOM里,我们列了用到的标准库osshutil,自己写的核心代码file_sort.py,还有第三方库requests及其版本。实际项目中可以用专门的工具生成,不用手动写,手动写只是为了方便理解。

3.4 第四步:发布前的强制校验

把签名和SBOM都做好之后,发布的时候一定要做两个校验,不通过就不能发出去。这里写个简单的校验脚本,比如我们要发布到公司的内部服务器,校验脚本会自动检查。

3.4.1 签名校验代码

from Crypto.Signature import pkcs1_15
from Crypto.Hash import SHA256

# 读取公开的公钥,公司所有人都用同一个公钥
with open("public_key.pem", "rb") as f:
    public_key = RSA.import_key(f.read())

# 待校验的包和签名文件
package_path = "file_sort.zip"
signature_path = "file_sort.sig"

# 读取文件内容
with open(package_path, "rb") as f:
    package_data = f.read()
with open(signature_path, "rb") as f:
    signature = f.read()

try:
    # 生成包的哈希,用公钥验证签名
    hash_obj = SHA256.new(package_data)
    pkcs1_15.new(public_key).verify(hash_obj, signature)
    print("✅ 签名校验通过:包是合法发布的,未被篡改")
except (ValueError, TypeError):
    print("❌ 签名校验失败:包可能被篡改或来源非法,拒绝发布")
    exit(1)

如果签名校验失败,直接退出,不让发布。

3.4.2 SBOM校验代码

import json

# 读取SBOM文件
with open("sbom.json", "r") as f:
    sbom = json.load(f)

# 定义允许的合法组件列表,只允许指定的组件和版本
allowed_components = {
    "os": {"type": "Python标准库"},
    "shutil": {"type": "Python标准库"},
    "file_sort.py": {"type": "自定义代码", "version": "1.0.0"},
    "requests": {"type": "第三方依赖库", "version": "2.31.0"}
}

# 校验每个组件是否合法
is_valid_sbom = True
for comp in sbom["components"]:
    comp_name = comp["name"]
    if comp_name not in allowed_components:
        is_valid_sbom = False
        print(f"❌ SBOM校验失败:发现不允许的组件 {comp_name}")
    else:
        # 校验组件版本是否正确
        if "version" in comp and allowed_components[comp_name].get("version") != comp["version"]:
            is_valid_sbom = False
            print(f"❌ SBOM校验失败:组件 {comp_name} 版本错误,要求是 {allowed_components[comp_name].get('version')},实际是 {comp['version']}")

if is_valid_sbom:
    print("✅ SBOM校验通过:组件列表合法,无恶意组件")
else:
    exit(1)

两个校验都通过了,才允许发布,这样就能保证发出去的东西是合法的,没被改过。

四、实际能用到的场景

这个方案的适用场景非常多,不止是内部工具,还有很多地方能用到:

4.1 内部工具发布

公司内部给员工用的工具,比如考勤工具、报销工具,必须过这两个校验,防止有人改包加恶意代码,影响员工工作。

4.2 开源包发布

你把自己写的Python包发到PyPI这样的平台上,用这个方案做签名,用户下载你的包之后,能校验是不是你发的,有没有被别人改,不用怕下到恶意包。

4.3 CI/CD流程

把这两个校验放到持续集成持续部署(CI/CD)的流程里,每次提交代码,自动做签名和SBOM校验,过了才能上线,不用人工手动操作,省时间还不会漏。

五、这个方案的优缺点

5.1 优点

一是不可篡改:只要签名校验过,包就一定没被改过,来源也合法;二是可追溯:SBOM能查到所有组件,出问题能快速定位到是哪个部分的问题;三是安全:能避免收到或发布恶意包,减少安全风险。

5.2 缺点

一是多了一点流程:发布的时候要多做两步(签名、SBOM),还要做校验,不过这点麻烦换安全很值得;二是密钥管理要上心:私钥丢了的话,别人就能冒充你发合法的包,所以要存好私钥;三是SBOM要准确:如果漏了某个组件,校验的时候会误判,所以要把所有组件都列进去。

六、落地的时候要注意的坑

6.1 私钥要存安全的地方

不能把私钥提交到代码仓库里,也不能随便给人,最好用硬件密钥(比如加密U盘),或者专门的密钥管理服务,比如HashiCorp Vault。

6.2 SBOM要完整

不管是手动生成还是用工具生成,一定要把所有用到的组件都列进去,包括Python的标准库、第三方库,还有自己写的所有模块,不能漏。

6.3 一定要强制校验

哪怕有人说“我这个包没问题,不用校验了”,也不能通融,发布流程里校验不通过,直接拒绝,不然这个方案就白做了。

6.4 定期更换密钥

私钥用久了要定期换,比如每半年换一次,避免私钥泄露的风险,换了之后要及时更新公钥给所有使用的人。

七、总结

数字签名和SBOM管理,看起来是技术名词,其实就是给软件包盖个专属的章、列个配料表,把它们做进发布的强制校验里,就能搭起一条从发布到使用都没法篡改的交付链路。不管是小工具还是大系统,不管是内部用还是公开发布,这个方案都能帮你避免很多麻烦,保障交付的安全性。对于不同基础的开发者来说,只要跟着示例一步步做,就能轻松落地,不用怕搞复杂的技术。