一个EXE有两条数字签名,代码签名证书该怎么看?
打开一个EXE的属性,数字签名列表里竟然有两条记录:名字一样,日期不同,有时连算法也不一样。这是不是重复签了,或者其中一张代码签名证书有问题?先别急着删。一个文件可以带有多条Authenticode签名,判断是否正常,要看每条记录对应什么证书、验证结果如何,不能只数有几个名字。
还有一种情况更容易看错:列表只有一行,点开详情却出现好几家机构。有的名称来自证书路径,有的属于时间戳服务,先认清当前窗口展示哪一层信息,再判断它与软件发布者的关系。排查对象先限定为普通Windows EXE,签名方式是嵌入式Authenticode。

多出来的名字,先看它出现在哪个窗口
先回到EXE属性的“数字签名”选项卡,逐行选中记录,再打开“详细信息”。后续看到的证书要与选中的那一行对应,查第二条时就重新选中第二条,不要沿用第一条已经打开的详情窗口。
再打开该条记录的证书。如果在“证书路径”里看到几个上下排列的名称,先认清签名者证书与CA证书的层级。验证时,要沿着这些签发关系检查能否接到认可的受信任根。这里展开的是一条文件签名所用的信任路径,把每一层都算成一次软件签署,数量自然会数错。
如果详情切换到了时间戳,先看清其中的证书属于哪个服务。为这条记录提供时间证明的机构,承担的职责与软件开发企业不同。遇到两边有效期不一致,也要分别抄在各自名下,别用时间戳那一栏的日期,去安排发布者证书的续期。
实际排查时,可以把所选列表记录、对应证书和时间戳分别截图,给截图注明当前选的是哪一条。第二条也按这个顺序整理。否则几张窗口截图混在一起,很容易把第一条的证书和第二条的时间戳凑成一组。
公司名相同,证书也可能不是同一张
假设两条签名都显示“某某软件有限公司”,其中一条用旧证书,另一条用换发后的证书,光凭公司名称就分不出来。名字可以相同,证书序列号、有效期、公钥或指纹却可能不同。
做个按签名条目分行的小表会清楚些:每行填入签名者、证书颁发者、序列号和有效期,证书指纹连同算法名称一起保存。两行名称相同,就继续比较其余标识;把不同算法算出的指纹直接相比,也得不出是不是同一张证书的结论。
接着,把每个算法名称对回它所在的项目:文件摘要、证书自身的签署,还是时间戳摘要。不要看到一处写着SHA-256,就把另外两处也填成SHA-256。两条记录的算法标签不同,只能先记下差别,还需要分别验签,不能据此判断代码签名证书出了错。
过去有软件为了兼容不同系统,保留过采用不同算法的签名。但不能拿这个历史做法解释所有双签文件,更没必要为了多一条记录,专门寻找旧SHA-1代码签名证书。现有文件为什么这样签,需要结合证书信息和构建记录确认。
想知道两条是否都有效,用“全部验证”
图形窗口适合辨认信息,开发人员想把结果留完整,可以使用Windows SDK里的SignTool。对普通EXE采用常规Authenticode验证策略,查看全部签名的命令是:
signtool verify /pa /all /v “app.exe”
这里的/pa指定常规Authenticode验证策略,/all验证多签名文件里的全部签名,/v输出详细信息。双引号里填写待查EXE的路径,目录带空格时保留这对引号。不要只截最后一句成功或失败,要把每条签名对应的证书、时间戳和错误信息一起留下。
这里保留/pa是有原因的:SignTool verify没有指定这个选项时,默认采用Windows驱动验证策略。检查普通EXE和检查驱动的目的不同,比较两份日志前,要先确认它们采用了什么策略,不能只比较最后的结果。
如果把命令放进发布脚本,还要接收退出码:0表示成功,1表示失败,2表示完成但带有警告。退出码概括的是整次命令的状态,不能代替每条签名的详细结果。脚本若只判断“不是1就继续”,就会把带警告的情况也直接放过去。收到2时,让脚本保留日志并提示经办人检查,读清警告再安排后续发布;也别只因出现warning,就认定签名已经损坏。
有个很容易写错的参数:verify /a不是“全部验证”的简写。它会先尝试搜索目录数据库中的签名,找不到相应目录签名时再尝试嵌入式签名。要查这个EXE里的全部签名,使用的是/all,少了两个字母,检查方式就变了。
如果需要单独追查某条记录,可以使用verify /ds Index指定签名位置。例如,日志中已经确认存在索引1,才针对它运行:
signtool verify /pa /ds 1 /v “app.exe”
工具索引需要靠日志对应,资源管理器的显示行号不能直接拿来填写/ds。先在详细输出里找到索引,再核对签名者、证书标识等信息,确认查的是同一条。排查记录里写清索引,别人接手后才能复查到原来的位置。
一条通过、一条失败,先别替整份文件下结论
遇到一好一坏,可以给失败项写一行带上下文的说明,例如“索引1使用某证书,在这台电脑按/pa检查时未能建立可信链”。接下来查的是这条记录的链路和信任来源;日志若指出受签名保护的内容校验不通过,则要对照原始构建产物,检查待查EXE的内容。照着具体报错找原因,才知道该查证书还是查产物。
把待查EXE复制到独立目录,后续命令都指向这份副本。app.exe这样的名称会被不同版本反复使用,同事只按名字取文件,很容易拿错构建版本。再为这份副本计算整文件SHA-256,把哈希与验证输出放在一起。这个哈希用于辨认文件,不能拿来替代Authenticode验签,两者计算和判断的对象并不完全相同。
同样,某台电脑验证通过,也不能据此认定所有Windows环境都会得到同样的验证结果。受信任根、吊销检查、网络可用性和验证策略都可能影响判断。要解决客户机器上的问题,就记录那台机器的系统版本、使用的工具和具体错误,再用同一份文件复查。
签名数量也回答不了“Windows最终会怎样处理”这个问题。有一条成功,不足以保证所有目标环境都放行;有一条失败,也不能直接断言整个程序已被恶意修改。准备对外发布时,还要在目标系统上检查实际运行情况。
准备补签时,先确定原来的记录要不要保留
比如,证书更新后,准备给旧版本再增加一条记录,目标就是保留原签名并追加新签名。开工前先检查工具采用的是追加方式还是会改动现有记录,别把“重新执行签名命令”当成保留旧记录的保证。
使用SignTool时,把/as放在sign子命令下,才是在要求追加。原EXE已有主签名,就在它的基础上增添记录;尚无主签名时,这次生成的记录会取得主签名位置。这个选项属于sign操作,和前面的verify /all完全不是一回事。
签名前把EXE原件单独存好。补签结束后,对新产物执行前面的/all检查,比较处理前后的条目和证书标识,确认需要保留的记录还在、新记录确实增加、各项结果符合发布要求。只统计执行了几次sign,容易漏掉“新签名成功,但原来的记录已经变化”这种问题。
补签后还要分别检查两条记录的时间证明。SignTool的timestamp /tp index可指定要加时间戳的签名位置。不要看到其中一条有时间戳,就当作另外一条也有。有效时间戳有助于代码签名证书过期后的验证,但仍要满足相关验证条件,不能理解成软件从此永久有效。
沟通结果时,把“看到了什么”和“验证了什么”写清楚
给同事反馈时,单说“双签正常”仍然无法复查。把文件哈希、各条记录的证书标识、使用的验证策略和每项结果写在同一份说明里,对方才能按原条件复核。只看过属性窗口,就不要写成已经验证全部签名。
如果两条签名对应不同企业,也别凭名字推断两家企业的合作关系、先后开发顺序或版权归属。发布说明若只列一家企业,就把两条记录的名称、证书标识和文件哈希交给发布方,询问每条记录因何保留。证书能帮助辨认签名身份,商业关系需要别的材料说明。
代码签名证书与EXE多签名常见问题
1、有两条数字签名,就必须购买两张代码签名证书吗?
不一定。签名条目数量与不同证书数量不能直接画等号。先比较每条签名的证书标识,确认是否使用不同证书,再结合构建记录解释成因,不要先按条目数采购。
2、两条签名能看出哪条使用EV代码签名证书吗?
排在前面的那条不一定使用EV代码签名证书。要判断类型,需要检查对应证书信息及签发产品资料。OV代码签名证书和EV代码签名证书即使同时出现,也不会自动组成某种更高等级的签名。
3、把证书导出来检查,能代替验证这份EXE吗?
不能。单独检查证书的有效期和信任状态,不能确认当前EXE里每条签名保护的内容是否完整。要把实际EXE交给验签工具,再将输出中的证书与相应索引对应起来,检查结果才属于正在排查的这份软件。
4、verify /tw能给缺少时间戳的签名补上时间戳吗?
不能。/tw会在缺少时间戳时产生警告,它属于验证选项。需要补时间戳时,改用timestamp操作并指定目标索引,随后对刚处理的那条记录再验一次。
5、sign里的/ac参数会额外追加一条文件签名吗?
不会。/ac本身只把附加证书放入签名块,不负责额外追加一条文件签名。增加证书链材料和追加文件签名是不同操作,需要保留旧签名并追加新记录时,使用的是sign /as。
相关阅读:代码签名证书微软不过审
