3. 代码签名实践:用 OpenSSL 和 SignTool 搞定二进制签名

代码签名这事儿,说白了就是给你的软件发一张「身份证」。我见过不少团队,发布二进制文件直接裸奔,用户下载后根本不知道这文件是谁写的、有没有被篡改过。嗯,这很危险。

今天我们就动手做一遍。用 OpenSSL 生成自签名证书,再用 SignTool 给一个 exe 或 dll 打上签名,最后验证签名是否完整。整个过程我带你走一遍,顺便聊聊我在项目中踩过的坑。

3.1 整体流程概览

先看一张图,把今天要做的事串起来。你心里有个谱,后面操作就不容易乱。

代码签名实践流程 1. 生成CA根证书 openssl req -x509 2. 生成代码签名证书 openssl req + ca 3. 导出为PFX openssl pkcs12 4. 签名二进制文件 signtool sign 5. 验证签名完整性 signtool verify 6. 查看签名详情 signtool verify /v ⚠ 说明 自签名证书仅适用于开发测试环境。生产环境请使用受信任的CA颁发的证书。 Windows默认不信任自签名证书,需要手动安装到受信任根证书存储区。

3.2 生成自签名证书

先准备一个 CA 根证书。为什么需要 CA?因为代码签名证书通常由 CA 签发,自签名也一样,我们模拟这个层级关系。

3.2.1 生成 CA 根证书

# 生成CA私钥
openssl genrsa -out ca.key 2048

# 生成CA自签名证书(有效期10年)
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 \
  -out ca.crt \
  -subj "/C=CN/ST=Beijing/L=Beijing/O=MyCompany/CN=MyCA Root"

这里我习惯用 2048 位 RSA 密钥。4096 更安全,但签名速度慢,日常开发 2048 够用了。记得把 -days 设长一点,CA 证书频繁更换很麻烦。

我的习惯:CA 根证书的有效期我一般设 10 年。代码签名证书设 2-3 年。这样既安全又不用老折腾根证书。

3.2.2 生成代码签名证书(由CA签发)

# 生成代码签名证书的私钥
openssl genrsa -out code.key 2048

# 生成证书签名请求(CSR)
openssl req -new -key code.key -out code.csr \
  -subj "/C=CN/ST=Beijing/L=Beijing/O=MyCompany/CN=MyCodeSign"

# 用CA签发代码签名证书
openssl x509 -req -in code.csr -CA ca.crt -CAkey ca.key \
  -CAcreateserial -out code.crt -days 730 -sha256 \
  -extensions code_sign -extfile <(echo -e "[code_sign]\nkeyUsage=critical,digitalSignature\nextendedKeyUsage=codeSigning")

注意最后一行。我当初第一次做的时候忘了加 extendedKeyUsage=codeSigning,结果签名出来的文件 Windows 一直报「证书无效」。折腾了半天才发现是这个坑。你想想看,证书用途没指定为代码签名,系统当然不认。

3.2.3 导出为 PFX 格式

SignTool 需要 PFX 或 P12 格式的证书文件。我们把私钥和证书打包到一起。

# 导出为PFX(设置导出密码)
openssl pkcs12 -export -out code.pfx -inkey code.key -in code.crt \
  -certfile ca.crt -passout pass:YourPassword123
注意:PFX 的导出密码要记好。签名时要用。另外,-certfile ca.crt 会把 CA 证书也包含进去,这样验证时能自动构建证书链。

3.3 使用 SignTool 对二进制文件签名

SignTool 是 Windows SDK 自带的工具。如果你装了 Visual Studio,一般在 C:\Program Files (x86)\Windows Kits\10\bin\10.0.xxxxx.0\x64\ 下能找到。我建议把它加到 PATH 里,省得每次敲全路径。

3.3.1 基本签名命令

signtool sign /fd SHA256 /a /f code.pfx /p YourPassword123 myapp.exe

参数说明:

参数 含义
/fd SHA256 使用 SHA256 哈希算法。SHA1 已经不安全了,别用。
/a 自动选择最佳签名证书。如果 PFX 里只有一个证书,这个参数省事。
/f 指定 PFX 文件路径。
/p PFX 导出密码。

3.3.2 时间戳签名(强烈建议加)

不加时间戳会怎样?你的证书过期后,签名就失效了。加了时间戳,哪怕证书过期,只要时间戳服务器可信,签名依然有效。

signtool sign /fd SHA256 /a /f code.pfx /p YourPassword123 \
  /tr http://timestamp.digicert.com /td SHA256 myapp.exe

/tr 指定 RFC 3161 时间戳服务器。/td SHA256 指定时间戳的哈希算法。我一般用 DigiCert 的免费时间戳服务器,稳定。

重要:生产环境一定要加时间戳。我见过有团队没加,证书到期后用户安装软件时疯狂弹警告,最后紧急重新签名发布。那场面,挺狼狈的。

3.4 验证签名的完整性

签名完了,怎么确认签名是完整的、没被篡改?用 signtool verify。

3.4.1 基本验证

signtool verify /pa myapp.exe

/pa 表示验证签名策略。如果签名有效,会输出 Successfully verified。如果文件被改过,会报错。

3.4.2 详细验证(推荐)

signtool verify /v /pa myapp.exe

/v 是 verbose 模式。输出信息很详细,包括:

  • 证书链信息
  • 签名算法
  • 时间戳信息
  • 签名是否可信

我每次发布前都会跑一遍这个命令,确认所有细节都对。尤其是证书链,有时候 CA 证书没装全,验证会失败。

3.4.3 验证签名时间戳

signtool verify /v /pa /ts myapp.exe

/ts 会额外显示时间戳的详细信息。你可以看到签名时间、时间戳服务器地址等。

3.5 常见问题与避坑

做代码签名这几年,我遇到过不少问题。挑几个典型的说说。

问题1:签名后文件被杀毒软件报毒
自签名证书默认不被信任,杀毒软件会认为「未知发布者」。解决办法:要么用受信任CA的证书,要么在测试环境把自签名证书安装到受信任根证书存储区。
问题2:签名时提示「找不到证书」
检查 PFX 文件路径和密码。另外,确认 PFX 里包含私钥。可以用 openssl pkcs12 -info -in code.pfx 查看 PFX 内容。
问题3:验证时提示「证书链不完整」
导出 PFX 时忘了加 -certfile ca.crt。重新导出,或者手动把 CA 证书安装到「受信任的根证书颁发机构」存储区。

3.6 完整脚本示例

我把上面所有步骤整合成一个脚本。你直接复制,改一下参数就能用。

#!/bin/bash
# 代码签名完整流程脚本

# 1. 生成CA根证书
openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 \
  -out ca.crt -subj "/C=CN/ST=Beijing/L=Beijing/O=MyCompany/CN=MyCA Root"

# 2. 生成代码签名证书
openssl genrsa -out code.key 2048
openssl req -new -key code.key -out code.csr \
  -subj "/C=CN/ST=Beijing/L=Beijing/O=MyCompany/CN=MyCodeSign"
openssl x509 -req -in code.csr -CA ca.crt -CAkey ca.key \
  -CAcreateserial -out code.crt -days 730 -sha256 \
  -extensions code_sign -extfile <(echo -e "[code_sign]\nkeyUsage=critical,digitalSignature\nextendedKeyUsage=codeSigning")

# 3. 导出PFX
openssl pkcs12 -export -out code.pfx -inkey code.key -in code.crt \
  -certfile ca.crt -passout pass:YourPassword123

# 4. 签名二进制文件
signtool sign /fd SHA256 /a /f code.pfx /p YourPassword123 \
  /tr http://timestamp.digicert.com /td SHA256 myapp.exe

# 5. 验证签名
signtool verify /v /pa myapp.exe

跑完这个脚本,你的二进制文件就有了完整的数字签名。用户下载后,右键 → 属性 → 数字签名,就能看到签名信息。

嗯,代码签名这件事,说白了就是给软件加一把锁。钥匙在你手里,用户拿到的是带锁的箱子。只要箱子没被撬开,用户就知道这软件是原装的。这就是信任的基础。