国密算法在软件供应链安全中的实践:从代码签名到制品验证的完整方案
前言:为什么软件供应链需要国密?
2023年以来,软件供应链攻击呈爆发式增长:
- Log4Shell(2021年):通过Java日志库实现远程代码执行
- SolarWinds攻击(2020年):通过在更新包中植入恶意代码实现APT入侵
- XZ Utils后门事件(2024年3月):在开源库中植入SSH后门,影响全球数百万服务器
对于国密改造场景,我们面临双重挑战:
- 合规要求:等保2.0、密评要求软件交付物具备完整性保护
- 安全需求:传统RSA/SHA签名体系存在供应链风险,需要国产密码算法替代
一、软件供应链安全架构
1.1 攻击面分析
软件供应链涉及以下环节:
CODE
源码仓库 → 构建流水线 → 制品仓库 → 部署环境
↓ ↓ ↓ ↓
代码签名 制品签名 完整性校验 运行时验证每个环节都可能被攻击:
| 环节 | 攻击方式 | 典型案例 |
|---|---|---|
| 源码仓库 | 篡改commit、注入恶意代码 | GitHub账号劫持 |
| 构建流水线 | 替换构建工具、注入恶意依赖 | SolarWinds |
| 制品仓库 | 替换已发布包、注入后门 | PyPI包污染 |
| 部署环境 | 镜像替换、配置篡改 | XZ Utils后门 |
1.2 国密化防御架构
针对上述攻击面,国密算法可提供以下保护:
CODE
┌─────────────────────────────────────────────────────────┐
│ 软件供应链安全架构 │
├─────────────┬─────────────┬─────────────┬───────────────┤
│ 代码签名 │ 制品签名 │ SBOM签名 │ 运行时验证 │
│ SM2+SM3 │ SM2+SM3 │ SM2签名 │ SM3校验 │
├─────────────┼─────────────┼─────────────┼───────────────┤
│ Git commit │ Docker镜像 │ CycloneDX │ 内存完整性 │
│ tag签名 │ 签名 │ JSON签名 │ 校验 │
└─────────────┴─────────────┴─────────────┴───────────────┘二、SM2代码签名实践
2.1 Git提交签名原理
Git支持使用GPG或SSH密钥签名commit和tag。国密场景下,我们可以使用SM2算法实现类似的签名机制。
核心流程:
- 开发者生成SM2密钥对
- 使用私钥签名commit对象
- 将签名附加到commit元数据中
- 验证时使用公钥校验签名有效性
2.2 完整实现代码
PYTHON
"""
国密Git提交签名工具
用法: python3 gm_git_sign.py sign <commit_hash>
python3 gm_git_sign.py verify <commit_hash>
"""
from gmssl import sm2, func
from gmssl.sm3 import sm3_hash
from gmssl.func import bytes_to_list
import hashlib
import base64
import json
import subprocess
from typing import Optional, Tuple
from pathlib import Path
from datetime import datetime
class GMSignGit:
"""国密Git提交签名工具"""
def __init__(self, private_key: Optional[str] = None,
public_key: Optional[Tuple[str, str]] = None):
"""
初始化签名工具
:param private_key: 16进制私钥(64位十六进制字符串)
:param public_key: (x, y) 公钥坐标
"""
self.signer = None
if private_key and public_key:
self.signer = sm2.CryptSM2(
private_key=private_key,
public_key=f"{public_key[0]}{public_key[1]}"
)
@classmethod
def generate_keypair(cls) -> Tuple[str, Tuple[str, str]]:
"""
生成SM2密钥对
注意:生产环境应使用HSM或安全熵源
"""
import os
# 使用系统随机数生成私钥
private_key = func.bytes_to_hex(os.urandom(32))
# 实际项目中应使用gmssl的ECC功能推导公钥
# 这里返回占位符,实际实现需要完整的椭圆曲线运算
public_key = ("placeholder_x", "placeholder_y")
return private_key, public_key
def sign_commit(self, commit_hash: str, message: str) -> str:
"""
签名Git commit
:param commit_hash: commit哈希值
:param message: 签名消息
:return: Base64编码的签名
"""
if not self.signer:
raise ValueError("私钥未初始化")
# 构造待签名数据
# 格式:commit_hash || message
data = f"{commit_hash}:{message}".encode('utf-8')
# SM2签名(gmssl内部自动进行SM3哈希和ZA预处理)
signature = self.signer.sign(data)
return base64.b64encode(signature).decode('utf-8')
def verify_commit(self, commit_hash: str, message: str,
signature_b64: str) -> bool:
"""
验签Git commit
:param commit_hash: commit哈希值
:param message: 签名消息
:param signature_b64: Base64编码的签名
:return: 验签结果
"""
if not self.signer:
raise ValueError("公钥未初始化")
data = f"{commit_hash}:{message}".encode('utf-8')
signature = base64.b64decode(signature_b64)
return self.signer.verify(signature, data)
def main():
import sys
import os
if len(sys.argv) < 3:
print("用法:")
print(" python3 gm_git_sign.py sign <commit_hash> <message>")
print(" python3 gm_git_sign.py verify <commit_hash> <message> <signature>")
print(" python3 gm_git_sign.py keygen")
sys.exit(1)
command = sys.argv[1]
if command == "keygen":
private_key, public_key = GMSignGit.generate_keypair()
print(f"私钥: {private_key}")
print(f"公钥: ({public_key[0]}, {public_key[1]})")
print("\n⚠️ 请安全保存私钥,建议存入HSM")
elif command == "sign":
commit_hash = sys.argv[2]
message = sys.argv[3] if len(sys.argv) > 3 else ""
# 从环境变量读取私钥
private_key = os.environ.get('GM_PRIVATE_KEY')
public_key_x = os.environ.get('GM_PUBLIC_KEY_X')
public_key_y = os.environ.get('GM_PUBLIC_KEY_Y')
if not all([private_key, public_key_x, public_key_y]):
print("错误: 请设置环境变量 GM_PRIVATE_KEY, GM_PUBLIC_KEY_X, GM_PUBLIC_KEY_Y")
sys.exit(1)
signer = GMSignGit(private_key, (public_key_x, public_key_y))
signature = signer.sign_commit(commit_hash, message)
print(f"签名: {signature}")
print(f"\n将此签名添加到commit元数据:")
print(f" git commit -S -m \"{message}\"")
elif command == "verify":
commit_hash = sys.argv[2]
message = sys.argv[3]
signature = sys.argv[4]
public_key_x = os.environ.get('GM_PUBLIC_KEY_X')
public_key_y = os.environ.get('GM_PUBLIC_KEY_Y')
if not all([public_key_x, public_key_y]):
print("错误: 请设置环境变量 GM_PUBLIC_KEY_X, GM_PUBLIC_KEY_Y")
sys.exit(1)
signer = GMSignGit(public_key=(public_key_x, public_key_y))
result = signer.verify_commit(commit_hash, message, signature)
print(f"验签结果: {'通过 ✓' if result else '失败 ✗'}")
if __name__ == "__main__":
main()2.3 CI/CD集成示例
YAML
# .gitlab-ci.yml - 国密代码签名流水线
stages:
- build
- sign
- verify
variables:
GM_PRIVATE_KEY: $CI_REGISTRY_PASSWORD # 从密钥管理服务获取
GM_PUBLIC_KEY_X: $SIGNING_PUBLIC_KEY_X
GM_PUBLIC_KEY_Y: $SIGNING_PUBLIC_KEY_Y
build_job:
stage: build
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
sign_job:
stage: sign
script:
- pip install gmssl
- python3 scripts/gm_git_sign.py sign $CI_COMMIT_SHA "Build $CI_PIPELINE_ID"
artifacts:
paths:
- signature.sig
verify_job:
stage: verify
script:
- python3 scripts/gm_git_sign.py verify $CI_COMMIT_SHA "Build $CI_PIPELINE_ID" $SIGNATURE
only:
- main
- master三、SM3制品完整性验证
3.1 数字指纹与完整性校验
SM3哈希算法可用于生成软件制品的数字指纹。根据实测,空字符串的SM3哈希值为:
CODE
SM3("") = 1ab21d8355cfa17f8e61194831e81a8f22bec8c728fefb747ed035eb5082aa2b这是通过gmssl库实测得到的正确值,而非网络上流传的错误版本。
3.2 完整实现代码
PYTHON
"""
国密软件制品完整性校验工具
用法: python3 gm_integrity_check.py hash <file>
python3 gm_integrity_check.py verify <file> <expected_hash>
"""
from gmssl.sm3 import sm3_hash
from gmssl.func import bytes_to_list
import hashlib
import sys
from pathlib import Path
from datetime import datetime
import json
class GMIntegrityChecker:
"""国密完整性校验工具"""
@staticmethod
def compute_sm3(filepath: str) -> str:
"""
计算文件的SM3哈希值
:param filepath: 文件路径
:return: SM3哈希值(64位十六进制字符串)
"""
path = Path(filepath)
if not path.exists():
raise FileNotFoundError(f"文件不存在: {filepath}")
# 分块读取大文件(避免内存溢出)
h = sm3_hash(bytes_to_list(b''))
with open(filepath, 'rb') as f:
while True:
chunk = f.read(8192)
if not chunk:
break
h = sm3_hash(bytes_to_list(chunk))
return h
@staticmethod
def verify_sm3(filepath: str, expected_hash: str) -> bool:
"""
验证文件SM3哈希
:param filepath: 文件路径
:param expected_hash: 期望的SM3哈希值
:return: 验证结果
"""
actual_hash = GMIntegrityChecker.compute_sm3(filepath)
return actual_hash == expected_hash.lower()
@staticmethod
def generate_manifest(files: list, output_path: str):
"""
生成软件制品清单(包含SM3哈希)
:param files: 文件列表
:param output_path: 输出路径
"""
manifest = {
"version": "1.0",
"algorithm": "SM3",
"created_at": datetime.now().isoformat(),
"files": {}
}
for filepath in files:
try:
sm3_hash_value = GMIntegrityChecker.compute_sm3(filepath)
manifest["files"][filepath] = {
"sm3": sm3_hash_value,
"size": Path(filepath).stat().st_size
}
except Exception as e:
manifest["files"][filepath] = {
"error": str(e)
}
with open(output_path, 'w') as f:
json.dump(manifest, f, indent=2)
print(f"制品清单已生成: {output_path}")
return manifest
def main():
import argparse
parser = argparse.ArgumentParser(description='国密软件制品完整性校验工具')
parser.add_argument('command', choices=['hash', 'verify', 'manifest'])
parser.add_argument('file', help='文件路径')
parser.add_argument('--expected', help='期望的SM3哈希值(verify模式)')
parser.add_argument('--output', '-o', help='输出文件路径(manifest模式)')
args = parser.parse_args()
checker = GMIntegrityChecker()
if args.command == 'hash':
hash_value = checker.compute_sm3(args.file)
print(f"SM3({args.file}) = {hash_value}")
elif args.command == 'verify':
if not args.expected:
print("错误: verify模式需要--expected参数")
sys.exit(1)
result = checker.verify_sm3(args.file, args.expected)
print(f"验证结果: {'通过 ✓' if result else '失败 ✗'}")
sys.exit(0 if result else 1)
elif args.command == 'manifest':
if not args.output:
print("错误: manifest模式需要-o参数指定输出路径")
sys.exit(1)
import glob
files = glob.glob(args.file)
checker.generate_manifest(files, args.output)
if __name__ == "__main__":
main()3.3 SBOM与密码学Attestation
Software Bill of Materials (SBOM) 是软件供应链安全的重要组成部分。使用国密算法可以为SBOM提供密码学保护。
SBOM格式选择:
- CycloneDX:轻量级,适合软件组件清单
- SPDX:功能丰富,适合合规场景
- 自定义格式:结合国密算法需求定制
四、SM4加密SBOM敏感信息
4.1 敏感信息保护需求
SBOM可能包含敏感信息:
- 内部组件名称和版本
- 私有仓库URL
- 许可证信息
- 漏洞详情
4.2 完整实现代码
PYTHON
"""
国密SBOM敏感信息加密工具
"""
from gmssl import sm4
from gmssl.func import bytes_to_list, list_to_bytes
import base64
import json
from typing import Optional
class GMSBOMEncryptor:
"""国密SBOM加密工具"""
@staticmethod
def encrypt_field(value: str, key: bytes) -> str:
"""
加密单个字段值
:param value: 明文值
:param key: SM4密钥(16字节)
:return: Base64编码的密文
"""
if len(key) != 16:
raise ValueError("SM4密钥长度必须为16字节")
# SM4加密(ECB模式,PKCS7填充)
cipher = sm4.CryptSM4()
cipher.set_encrypt_key(key)
plaintext = value.encode('utf-8')
# PKCS7填充
padding_len = 16 - (len(plaintext) % 16)
padded = plaintext + bytes([padding_len] * padding_len)
ciphertext = cipher.crypt_ecb(bytes_to_list(padded))
return base64.b64encode(list_to_bytes(ciphertext)).decode('utf-8')
@staticmethod
def decrypt_field(encrypted_value: str, key: bytes) -> str:
"""
解密字段值
:param encrypted_value: Base64编码的密文
:param key: SM4密钥(16字节)
:return: 解密后的明文
"""
if len(key) != 16:
raise ValueError("SM4密钥长度必须为16字节")
ciphertext = base64.b64decode(encrypted_value)
# SM4解密
cipher = sm4.CryptSM4()
cipher.set_decrypt_key(key)
plaintext_list = cipher.crypt_ecb(bytes_to_list(ciphertext))
plaintext = list_to_bytes(plaintext_list)
# 去除PKCS7填充
padding_len = plaintext[-1]
plaintext = plaintext[:-padding_len]
return plaintext.decode('utf-8')
@staticmethod
def encrypt_sbom_sensitive(sbom: dict, key: bytes) -> dict:
"""
加密SBOM中的敏感字段
:param sbom: SBOM文档
:param key: SM4密钥(16字节)
:return: 加密后的SBOM
"""
sbom_copy = json.loads(json.dumps(sbom)) # 深拷贝
# 定义敏感字段路径
sensitive_fields = [
('metadata', 'component', 'name'),
('metadata', 'component', 'version'),
]
# 加密敏感字段
for path in sensitive_fields:
obj = sbom_copy
for key_name in path[:-1]:
obj = obj.get(key_name, {})
if path[-1] in obj:
obj[path[-1]] = GMSBOMEncryptor.encrypt_field(
obj[path[-1]], key
)
return sbom_copy
def main():
import argparse
parser = argparse.ArgumentParser(description='国密SBOM加密工具')
parser.add_argument('command', choices=['encrypt', 'decrypt'])
parser.add_argument('--input', '-i', required=True, help='输入文件')
parser.add_argument('--output', '-o', required=True, help='输出文件')
parser.add_argument('--key', '-k', required=True, help='SM4密钥(16字节十六进制)')
args = parser.parse_args()
encryptor = GMSBOMEncryptor()
with open(args.input) as f:
data = json.load(f)
key = bytes.fromhex(args.key)
if args.command == 'encrypt':
encrypted_sbom = encryptor.encrypt_sbom_sensitive(data, key)
with open(args.output, 'w') as f:
json.dump(encrypted_sbom, f, indent=2, ensure_ascii=False)
print(f"加密完成: {args.output}")
elif args.command == 'decrypt':
# 简化:直接解密(实际应只解密敏感字段)
with open(args.output, 'w') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
print(f"解密完成: {args.output}")
if __name__ == "__main__":
main()五、完整CI/CD流水线集成
5.1 GitLab CI/CD配置示例
YAML
# .gitlab-ci.yml - 国密软件供应链安全流水线
stages:
- build
- sign
- sbom
- verify
- deploy
variables:
GM_SM2_PRIVATE_KEY: $CI_REGISTRY_PASSWORD
GM_SM2_PUBLIC_KEY_X: $SIGNING_PUB_KEY_X
GM_SM2_PUBLIC_KEY_Y: $SIGNING_PUB_KEY_Y
GM_SM4_KEY: $SBOM_ENCRYPTION_KEY
build:
stage: build
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
artifacts:
paths:
- build/
sign-artifacts:
stage: sign
script:
- pip install gmssl
- python3 scripts/gm_git_sign.py sign $CI_COMMIT_SHA "Build $CI_PIPELINE_ID"
artifacts:
paths:
- signature.sig
only:
- main
- master
generate-sbom:
stage: sbom
script:
- pip install gmssl
- python3 scripts/generate_sbom.py --project myapp --component mylib:1.0.0:library
- python3 scripts/gm_sbom_sign.py sign --key signing-key.pem --output sbom-signed.json
artifacts:
paths:
- sbom-signed.json
only:
- main
- master
verify-integrity:
stage: verify
script:
- python3 scripts/gm_integrity_check.py verify build/app.jar $EXPECTED_SM3_HASH
- python3 scripts/gm_sbom_verify.py verify --pub-key verification-key.pem --input sbom-signed.json
only:
- main
- master
deploy:
stage: deploy
script:
- kubectl apply -f k8s/deployment.yaml
only:
- main
- master
needs:
- sign-artifacts
- generate-sbom
- verify-integrity5.2 GitHub Actions配置示例
YAML
# .github/workflows/gm-supply-chain.yml
name: 国密软件供应链安全
on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 安装国密依赖
run: pip install gmssl
- name: 构建制品
run: docker build -t ${{ secrets.REGISTRY }}/${{ github.repository }}:${{ github.sha }} .
- name: 上传制品
run: docker push ${{ secrets.REGISTRY }}/${{ github.repository }}:${{ github.sha }}
sign:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v4
- name: 安装国密依赖
run: pip install gmssl
- name: 签名制品
env:
GM_PRIVATE_KEY: ${{ secrets.GM_PRIVATE_KEY }}
GM_PUBLIC_KEY_X: ${{ secrets.GM_PUBLIC_KEY_X }}
GM_PUBLIC_KEY_Y: ${{ secrets.GM_PUBLIC_KEY_Y }}
run: |
python3 scripts/gm_git_sign.py sign ${{ github.sha }} "Build ${{ github.run_id }}"
- name: 上传签名
uses: actions/upload-artifact@v4
with:
name: signature
path: signature.sig
verify:
runs-on: ubuntu-latest
needs: [sign]
steps:
- uses: actions/checkout@v4
- name: 安装国密依赖
run: pip install gmssl
- name: 验证制品签名
env:
GM_PUBLIC_KEY_X: ${{ secrets.GM_PUBLIC_KEY_X }}
GM_PUBLIC_KEY_Y: ${{ secrets.GM_PUBLIC_KEY_Y }}
run: |
python3 scripts/gm_git_sign.py verify ${{ github.sha }} \
"Build ${{ github.run_id }}" \
${{ steps.sign.outputs.signature }}六、工程踩坑记录
坑1:SM2签名性能问题
现象:在CI/CD流水线中,SM2签名操作成为瓶颈,单个commit签名耗时超过2秒。
原因:
- gmssl库的SM2实现未优化大数运算
- 每次签名都重新初始化密钥对象
PYTHON
# 错误做法:每次签名都创建新对象
for commit in commits:
signer = sm2.CryptSM2(private_key=private_key, public_key=public_key)
signature = signer.sign(data)
# 正确做法:复用签名对象
signer = sm2.CryptSM2(private_key=private_key, public_key=public_key)
for commit in commits:
signature = signer.sign(data)坑2:SBOM格式兼容性
现象:生成的SBOM无法被主流工具识别。
原因:
- CycloneDX规范版本不一致
- 缺少必需的字段
PYTHON
# 确保SBOM符合CycloneDX 1.4规范
sbom = {
'bomFormat': 'CycloneDX',
'specVersion': '1.4',
'version': 1,
'metadata': {
'timestamp': datetime.now().isoformat(),
'tools': [{'name': 'gm-sbom-generator'}]
},
'components': [...]
}坑3:密钥管理复杂性
现象:CI/CD流水线中硬编码密钥导致安全风险。
解决方案:
- 使用HashiCorp Vault或AWS KMS管理国密密钥
- 流水线运行时动态获取密钥
- 密钥使用后立即销毁
七、总结
本文介绍了国密算法在软件供应链安全中的完整实践方案:
- SM2代码签名:保护Git提交和构建产物
- SM3完整性校验:确保软件制品未被篡改
- SBOM密码学保护:使用SM2签名和SM4加密保护SBOM
- CI/CD集成:提供GitLab和GitHub的完整配置示例