手动验证十六进制格式的 SSL 证书数字签名

Manually verifying a digital signature of SSL cert in hex format

我正在创建一个程序,给定纯十六进制格式的证书,将提取并验证数字签名。

我有以下十六进制格式的示例证书:

308206cd308205b5a003020102021001c3f8388b669f492cfac8731937fc8e300d06092a864886f70d01010b0500304f310b300906035504061302555331153013060355040a130c446967694365727420496e633129302706035504031320446967694365727420544c532052534120534841323536203230323020434131301e170d3232303132313030303030305a170d3233303230353233353935395a3079310b3009060355040613025553311330110603550408130a43616c69666f726e6961310f300d06035504071306497276696e6531253023060355040a131c426c697a7a61726420456e7465727461696e6d656e742c20496e632e311d301b0603550403131475732e61637475616c2e626174746c652e6e657430820122300d06092a864886f70d01010105000382010f003082010a0282010100b3838051fd24a13681e97e557476f22891b05355157c9ded1c7ca610f07f4d5a5c987e88318c213a62481fc0fd43ece409170cef6ac95ab106dd4cd046e4c3660097d2aeefffc7fe470307b17c9febd3799352a1971b0303360bd9c028c1939d9fbd0148eb0d4dbebfad23e5e3ab3b94943351cd8fe11556bf1ec7370a615ed74c5f5d66d62799f66bb1d1f3a4b661173cafed4b722b03f046572e73eae216b8e35e73add6cb86243fe4b457ffb8e6baf50af563110da121255c676248e8976ab1fe8c221a964cc550901b5191730aa6d616dc6aa9ee87fcaf7ac56e48f1592eeeb836065dd6c1078e724db7607677298169d9225eac5fdc974a21565621d4fb0203010001a382037930820375301f0603551d23041830168014b76ba2eaa8aa848c79eab4da0f98b2c59576b9f4301d0603551d0e04160414402287da2dd56eddfa85ccda1f946e493a08a99b301f0603551d1104183016821475732e61637475616c2e626174746c652e6e6574300e0603551d0f0101ff0404030205a0301d0603551d250416301406082b0601050507030106082b0601050507030230818f0603551d1f0481873081843040a03ea03c863a687474703a2f2f63726c332e64696769636572742e636f6d2f4469676943657274544c53525341534841323536323032304341312d342e63726c3040a03ea03c863a687474703a2f2f63726c342e64696769636572742e636f6d2f4469676943657274544c53525341534841323536323032304341312d342e63726c303e0603551d20043730353033060667810c0102023029302706082b06010505070201161b687474703a2f2f7777772e64696769636572742e636f6d2f435053307f06082b0601050507010104733071302406082b060105050730018618687474703a2f2f6f6373702e64696769636572742e636f6d304906082b06010505073002863d687474703a2f2f636163657274732e64696769636572742e636f6d2f4469676943657274544c53525341534841323536323032304341312d312e637274300c0603551d130101ff0402300030820180060a2b06010401d679020402048201700482016c016a007700e83ed0da3ef5063532e75728bc896bc903d3cbd1116beceb69e1777d6d06bd6e0000017e7eaf5af30000040300483046022100ce2d33ce8de3764ccc9d8ac03b936612fd10a4fc1815b3e092352643aa8d07e9022100e2a741a4c3ef5c90882f66951075be12ad54bdc573bd3ae3b17c662f63c6e29400760035cf191bbfb16c57bf0fad4c6d42cbbbb627202651ea3fe12aefa803c33bd64c0000017e7eaf5b14000004030047304502201ef42884353339d4921bf2e47af540b15af6bab94de74c3e3a1224418e9bc4ef0221008846deaa381d08b65607dc3290ba152f1c5aab5c7d03cf116682da8a45922f18007700b3737707e18450f86386d605a9dc11094a792db1670c0b87dcf0030e7936a59a0000017e7eaf5b4400000403004830460221008dc3b79af62dce53af04b8959afcb1f858bea16872eba97b5cc2c8f308b32d490221009b658042c5a0841d670ac1303c06b42e6a494596cb5e333fbdeddc9248e05dcd300d06092a864886f70d01010b0500038201010010170d137389daa010c477c0dde1af6529725489ad07822ace988cb78969683e1686a9fcde2d166b2c7ae5774e782ce7270904fb5abfdc0de25123d7cbcbd855598d35a027d1f5bdf3bd754eb3c6c9b7cc74de740b0b576c629dfd9ff5cca4d773f8bb499cc6f0aa39f269d219f019e62cbb354f32fa171226f58a4582b711e779268baa5d4bcc44f9dda3f2b867344ea29ecb6e28f4f818ef3594da16dc882cfd65cd2875a50d9ee9dff5d297135b5890ce4f583ab770c1d79bd5e7c4b2e1ae2ca0425cf63e12f151e8b6c6228ee53be299ee5ef09c85df0ad24b66e32b37f93193c2495f2de78d28126b3aa8a406b44d8469292fc242c5f39db44be56f4d91

从这里,我能够成功地导出数字签名(它位于上面十六进制证书的末尾)

10170d137389daa010c477c0dde1af6529725489ad07822ace988cb78969683e1686a9fcde2d166b2c7ae5774e782ce7270904fb5abfdc0de25123d7cbcbd855598d35a027d1f5bdf3bd754eb3c6c9b7cc74de740b0b576c629dfd9ff5cca4d773f8bb499cc6f0aa39f269d219f019e62cbb354f32fa171226f58a4582b711e779268baa5d4bcc44f9dda3f2b867344ea29ecb6e28f4f818ef3594da16dc882cfd65cd2875a50d9ee9dff5d297135b5890ce4f583ab770c1d79bd5e7c4b2e1ae2ca0425cf63e12f151e8b6c6228ee53be299ee5ef09c85df0ad24b66e32b37f93193c2495f2de78d28126b3aa8a406b44d8469292fc242c5f39db44be56f4d91

然后我获取颁发此证书的 public 密钥,并用它来解密数字签名摘要,如下所示:

3031300d0609608648016503040201050004200bf3dcf2340b972e97fe3c8493e11eeee01f298939734690d0b4e79e1f5701b4

此时我正在尝试通过创建整个 SSL 证书的散列来验证数字签名(此证书使用 SHA256 散列作为摘要):

hash('sha256', hex2bin($CertWithoutSignature))

其中 $CertWithoutSignature 与上面的十六进制相同(上面的第一个十六进制字符串),但没有数字签名(上面的第二个十六进制字符串)。

在这一点上我有点困惑,因为不仅sha256散列的长度不匹配,数据也不匹配。我知道我正在正确解密摘要,否则如果密钥无效等我会收到错误消息。但提取的值有 102 个字符长,并且与我从 sha256 获得的字符串长度不匹配是完全不同的。基本上我知道如果我能让它们匹配是有效的,因为这意味着文档完全相同,因此将获得相同的哈希值。任何帮助表示赞赏。谢谢

假设您在证书中使用 PKCS#1 v1.5 填充,您忘记了对哈希进行编码然后填充到签名中。

对签名之前的所有内容进行哈希处理也不正确,您需要对 TBSCertificate 进行哈希处理,其中 TBS 表示待签名.

有关更多信息,我强烈建议阅读 RFC 5280 中的 X.509 规范。而且,由于 ASN.1 编码器/解码器相当复杂,您可能希望使用库函数而不是自己编程。

您将需要使用实际的 SSL 库才能正确验证签名,这不仅仅是比较几个哈希值。您至少还需要签名证书。

首先,您需要将该证书转换为 PEM 格式,以便 OpenSSL 会喜欢它。

// hex-encoded binary format
$cert_hex = '308206cd...';

// convert to PEM format
$cert_pem = sprintf(
    "-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----\n",
    implode("\n", str_split(base64_encode(hex2bin($cert_hex)), 64))
);

// read in as an OpenSSL resource
$cert = openssl_x509_read($cert_pem);

// check the fingerprint just to verify it loaded
$fp = openssl_x509_fingerprint($cert, 'sha256');

var_dump($fp);

输出:

string(64) "d74157547fb287694b95b2533588c71f8706b0960e023fc4317f4f9a49ad2721"

在此之后,您可能希望以相同的方式加载签名证书并将它们都提供给 openssl_x509_verify(),例如:

openssl_x509_verify($cert, $signing_cert);