前提条件
使用Prolog验证数字签名和整个证书链非常容易。
但是,您需要基本了解证书如何签名。一个证书链是一系列证书C0,C1,...,CN。我使用CN来表示根证书。根据使用的约定,您可以相应地反转顺序。
重要的是,证书Ck使用与Ck+1的公钥对应的私钥进行签名。
因此,您代码中的一个问题是,尽管证书是使用与其不同的证书对应的私钥进行签名的,但您错误地使用C的公钥验证C的签名。
一个不同的问题源于一些对正在签署的内容产生混淆。我们签署证书中待签署部分的哈希值,而不是数据本身。因此,我们必须针对该哈希值验证签名。
具体例子:
为了使这个答案自包含,我在这里发布文件包含的证书相关属性的相关数据,即撰写时文件所包含的证书的相关属性。
数据:
第一个证书:
从证书链中的第一个证书中,我们需要签名和待签署部分,它们是:
签名值为:7B86A6E7A86B192579380108B7EADA1C25E288AB46120117DC6A80635324C89713C70206EF3FE13CCA5EDFF43601972CB96658826ADCD68B0FB3AE7F5607D036D6AE8AF824CD96D7F1B4DB58E714343031F292B17E6EEF83872A0FD586CC0D1DA85A677E4AB4C6540E9132B5BC5644533E0388F830B1B6757B7DB88AB82846F08B3B6DCEC7F24319AB7EA56F86592DDDEC4522CAF331C8B81A4E543FBDBF4D661B534BAE546465DB88A525BC82A7B4127F0AFF4A55525927A66A09055743F109E30D90CA074D258166F0E472CB7CCDB0747ADE74F7040CFEEB9A78C3483864C5106D542556C874AF768005A6EC83ADEB2EE32F8E6F7182A362775C2BF40AFA20。
待签名的数据为:30820466A00302010202103F25CAAEE5AA838FA91C051A75FC719D300D06092A864886F70D01010B0500307E310B3009060355040613025553311D301B060355040A131453796D616E74656320436F72706F726174696F6E311F301D060355040B131653796D616E746563205472757374204E6574776F726B312F302D0603550403132653796D616E74656320436C61737320332053656375726520536572766572204341202D204734301E170D3136313030373030303030305A170D3137313033303233353935395A306D310B30090603550406130255533113301106035504080C0A57617368696E67746F6E3110300E06035504070C0753656174746C6531193017060355040A0C10416D617A6F6E2E636F6D2C20496E632E311C301A06035504030C136563686F2D6170692E616D617A6F6E2E636F6D30820122300D06092A864886F70D01010105000382010F003082010A02820101009CAFB306BB910354E76E0406C44F9BE178934D9C83906C09EB4EBC006B1742682DF655610BC0934C2E30751E4D5E8B1BA15EBFEB7C28AD008DA38D7672C0558D4CB71F5FD512CFB92AFF80880394B8AA017C453CCC0BC709CEC698E29480D89D703034312A71DD94CC48619B91F68B8A44739DEEA7159EA334E9E4A93B460FA4AB0886202CD02B49C6283F321C5C4CA91C5AE8827CEB47811ED1871E7C66724BCD58A9EBFF9658B4D5D02046FA6702DCBAF2B6139B190D8735121BA2086C51C8C5724A0044C090688A25C819A5F1B6D4E9390E4DF21AB11263F203E4E9F1BCCC625D29D7C21B7C243E9B775E6E8B4B4F0DAF390748E964B968A9065EDBAA11A30203010001A382020730820203301E0603551D110417301582136563686F2D6170692E616D617A6F6E2E636F6D30090603551D1304023000300E0603551D0F0101FF0404030205A0301D0603551D250416301406082B0601050507030106082B060105
第二个证书:
从第二个证书中,我们只需要公钥来验证之前证书所签发的签名。
公钥(RSA):
key(public_key(rsa("B2D805CA1C742DB5175639C54A520996E84BD80CF1689F9A422862C3A530537E5511825B037A0D2FE17904C9B496771981019459F9BCF77A9927822DB783DD5A277FB2037A9C5325E9481F464FC89D29F8BE7956F6F7FDD93A68DA8B4B82334112C3C83CCCD6967A84211A22040327178B1C6861930F0E5180331DB4B5CEEB7ED062ACEEB37B0174EF6935EBCAD53DA9EE9798CA8DAA440E25994A1596A4CE6D02541F2A6A26E2063A6348ACB44CD1759350FF132FD6DAE1C618F59FC9255DF3003ADE264DB42909CD0F3D236F164A8116FBF28310C3B8D6D855323DF1BD0FBD8C52954A16977A522163752F16F9C466BEF5B509D8FF2700CD447C6F4B3FB0F7", "010001", -, -, -, -, -, -))).
验证
计算哈希值
正如我所说的,哈希值实际上是被签名的内容。关键在于,我指的不是整个证书的哈希值,而是待签名部分的哈希值。这种区别很重要,因为整个证书的哈希值也包括签名,而当证书被签名时,签名当然还不可用。
在SWI-Prolog中,我们可以使用library(crypto)
获取待签名部分的哈希值:
?- to_be_signed(TBS),
hex_bytes(TBS, Bytes),
crypto_data_hash(Bytes, Hash, [algorithm(sha256), encoding(octet)]).
TBS = "3082...EB3B62",
Bytes = [48, 130, 4, 102, 160, 3, 2, 1, 2|...],
Hash = '651bdcdd90251f71a47a5d1bbc6f28486c94d2dc3739dcd58ecb09b3f224ee05'.
我使用
sha256
,因为第一个证书在其
signature_algorithm/1
字段中指示了 (
RSA
和)
SHA256
。
使用 CLP(FD) 约束来验证签名是最简单的方法之一。我们只需要计算 Sig
Exp mod
p。我们插入具体数字,使用
(#=)/2
来评估整数上的算术表达式:
X #= 0x7B86A6E7A86B192579380108B7EADA1C25E288AB46120117DC6A80635324C89713C70206EF3FE13CCA5EDFF43601972CB96658826ADCD68B0FB3AE7F5607D036D6AE8AF824CD96D7F1B4DB58E714343031F292B17E6EEF83872A0FD586CC0D1DA85A677E4AB4C6540E9132B5BC5644533E0388F830B1B6757B7DB88AB82846F08B3B6DCEC7F24319AB7EA56F86592DDDEC4522CAF331C8B81A4E543FBDBF4D661B534BAE546465DB88A525BC82A7B4127F0AFF4A55525927A66A09055743F109E30D90CA074D258166F0E472CB7CCDB0747ADE74F7040CFEEB9A78C3483864C5106D542556C874AF768005A6EC83ADEB2EE32F8E6F7182A362775C2BF40AFA20 mod 0xB2D805CA1C742DB5175639C54A520996E84BD80CF1689F9A422862C3A530537E5511825B037A0D2FE17904C9B496771981019459F9BCF77A9927822DB783DD5A277FB2037A9C5325E9481F464FC89D29F8BE7956F6F7FDD93A68DA8B4B82334112C3C83CCCD6967A84211A22040327178B1C6861930F0E5180331DB4B5CEEB7ED062ACEEB37B0174EF6935EBCAD53DA9EE9798CA8DAA440E25994A1596A4CE6D02541F2A6A26E2063A6348ACB44CD1759350FF132FD6DAE1C618F59FC9255DF3003ADE264DB42909CD0F3D236F164A8116FBF28310C3B8D6D855323DF1BD0FBD8C52954A16977A522163752F16F9C466BEF5B509D8FF2700CD447C6F4B3FB0F7.
其中 #= 是 Prolog 中的算术运算符,表示等式两边的值相等。mod 表示取模运算。
它的结果是:
X = 986236757547332986472011617696226561292849812918563355472727826767720188564083584387121625107510786855734801053524719833194566624465665316622563244215340671405971599343902468620306327831715457360719532421388780770165778156818229863337344187575566725786793391480600129482653072861971002459947277805295727097226389568776499707662505334062639449916265137796823793276300221537201727072401742985542559596685092673521228140822200236743113743661549252453726123450722876929538747702356573783116197523966334991563351853851212597377279504828784716104866621888265058037501385433453379649364782998949981722124880992983641605。
游览:关于CLP(FD)的效率和使用。
你可能会说:“嗯,我真的不需要( #= )/2,我可以一直使用(is)/2,这是我数十年前学到的。” 但是,如果你在这些示例中使用(is)/2,你很容易得到
成千上万倍低效的代码。作为一个简单的基准测试,考虑以下谓词:
signature_pow(Sig, Exp, P, Pow) :-
Pow #= Sig^Exp mod P.
现在我们有了以下查询:
时间( signature_pow(0x7B86A6E7A86B192579380108B7EADA1C25E288AB46120117DC6A80635324C89713C70206EF3FE13CCA5EDFF43601972CB96658826ADCD68B0FB3AE7F5607D036D6AE8AF824CD96D7F1B4DB58E714343031F292B17E6EEF83872A0FD586CC0D1DA85A677E4AB4C6540E9132B5BC5644533E0388F830B1B6757B7DB88AB82846F08B3B6DCEC7F24319AB7EA56F86592DDDEC4522CAF331C8B81A4E543FBDBF4D661B534BAE546465DB88A525BC82A7B4127F0AFF4A55525927A66A09055743F109E30D90CA074D258166F0E472CB7CCDB0747ADE74F7040CFEEB9A78C3483864C5106D542556C874AF768005A6EC83ADEB2EE32F8E6F7182A362775C2BF40AFA20,0x010001,0xB2D805CA1C742DB5175639C54A520996E84BD80CF1689F9A422862C3A530537E5511825B037A0D2FE17904C9B496771981019459F9BCF77A9927822DB783DD5A277FB2037A9C5325E9481F464FC89D29F8BE7956F6F7FDD93A68DA8B4B82334112C3C83CCCD6967A84211A22040327178B1C6861930F0E5180331DB4B5CEEB7ED062ACEEB37B0174EF6935EBCAD53DA9EE9798CA8DAA440E25994A1596A4CE6D02541F2A6A26E2063A6348ACB44CD1759350FF132FD6DAE1C618F59FC9255DF3003ADE264DB42909CD0F3D236F164A8116FBF28310C3B8D6D855323DF1BD0FBD8C52954A16977A522163752F16F9C466BEF5B509D8FF2700CD447C6F4B3FB0F7,Pow))。
时间:
% 16个推断,0.000 CPU在0.000秒内(99%CPU,130624 Lips)
相比之下,如果我们在Prolog语言开发中进行回归,并用
(is)/2
代替
(#=)/2
,我们会得到:
% 3个推断,1.847 CPU在1.852秒内(100%CPU,2 Lips)
原因:在SWI-Prolog中,涉及
(#=)/2
的某些目标
自动使用专门的算术谓词。您不需要学习这些谓词即可使用它们。
CLP(FD)为您完成。
建议:在Prolog中使用CLP(FD)约束来推理整数。它们通常使您的谓词更通用,有时甚至
大大提高效率。
clpfd
现在,
X
怎么样呢?为了看到它是什么,请考虑其十六进制编码:
?- format("~16r", [$X]).
1fffffff...fff003031300d060960864801650304020105000420651bdcdd90251f71a47a5d1bbc6f28486c94d2dc3739dcd58ecb09b3f224ee05
这听起来很熟悉:最后,你会看到证书的待签名部分的
哈希值。这意味着签名已经验证成功了!
使用
library(crypto)
中的
rsa_verify/4
进行签名验证也是可行的。
下面是完整的查询:
?- to_be_signed(TBS),
hex_bytes(TBS, Bytes),
crypto_data_hash(Bytes, Hash, [algorithm(sha256), encoding(octet)]),
signature(Sig),
key(Key),
rsa_verify(Key, Hash, Sig, [type(sha256)]).
由于此操作“成功”,我们知道与
Key
相对应的私钥被用来生成签名。
结束语:
我有一个重要的声明:通常,这当然是完全不必要的!
SWI-Prolog SSL基础设施会自动验证证书链,因此每次使用
http_open/3
和相关谓词通过TLS建立连接时都会验证所有签名。但是,自己进行这些计算是很有趣的。有时候甚至是必须的,例如在本例中,您正在推理存储在某个地方的证书。
还有一个小小的说明:请在您的代码中使用
setup_call_cleanup/3
。否则,如果在
close/1
之前出现任何问题,您可能会泄漏文件描述符,事实上,在您的示例中就是这种情况。