3. 代码签名实践:用 OpenSSL 和 SignTool 搞定二进制签名
代码签名这事儿,说白了就是给你的软件发一张「身份证」。我见过不少团队,发布二进制文件直接裸奔,用户下载后根本不知道这文件是谁写的、有没有被篡改过。嗯,这很危险。
今天我们就动手做一遍。用 OpenSSL 生成自签名证书,再用 SignTool 给一个 exe 或 dll 打上签名,最后验证签名是否完整。整个过程我带你走一遍,顺便聊聊我在项目中踩过的坑。
3.1 整体流程概览
先看一张图,把今天要做的事串起来。你心里有个谱,后面操作就不容易乱。
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 证书频繁更换很麻烦。
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
-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 常见问题与避坑
做代码签名这几年,我遇到过不少问题。挑几个典型的说说。
自签名证书默认不被信任,杀毒软件会认为「未知发布者」。解决办法:要么用受信任CA的证书,要么在测试环境把自签名证书安装到受信任根证书存储区。
检查 PFX 文件路径和密码。另外,确认 PFX 里包含私钥。可以用
openssl pkcs12 -info -in code.pfx 查看 PFX 内容。
导出 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
跑完这个脚本,你的二进制文件就有了完整的数字签名。用户下载后,右键 → 属性 → 数字签名,就能看到签名信息。
嗯,代码签名这件事,说白了就是给软件加一把锁。钥匙在你手里,用户拿到的是带锁的箱子。只要箱子没被撬开,用户就知道这软件是原装的。这就是信任的基础。