好的,我终于找到了解决方法。
我有一个以 UTF-8 编码的网页(这在接下来的一些步骤中非常重要);生成签名:如果用户使用 Mozilla/Firefox/Chrome - 使用window.crypto
. 有关更多信息,请阅读此内容 如果用户使用 Internet Explorer - 使用CAPICOM
(加密应用程序接口 COM 对象)生成签名。有关更多信息,请阅读 MSDN 两者 -window.crypto
并且CAPICOM
没有很好地记录用于 Web 使用(CAPICOM
不仅适用于 Web!)文本字符串和签名正在通过 POST 请求发送到 Web 服务器。服务器(Linux、Apache 和 PHP)必须验证签名。现在的问题:
PHP的openssl_verify()
功能也没有很好的文档记录,并且总是返回零 - 签名无效。CMD 工具openssl
也不会验证签名。因为我的 Web 服务器需要 SSL 证书身份验证,所以我希望用户使用他登录的相同证书对文本字符串进行签名。那么解决方法是什么:
我发现在签名之前CAPICOM
将签名字符串转换为UTF-16LE 。不幸的是,网络浏览器将文本字符串发送到以UTF-8编码的网络服务器。这意味着您必须在验证签名之前将字符串从 UTF-8 转换为 UTF-16LE。但这仅在签名由 生成时才有效CAPICOM
。
openssl
CMD 工具现在工作正常。CMD工具和PHP函数的区别在于:
签名和来源作为字符串发送到 PHP 函数。在使用 CMD 工具的情况下,签名和源将作为文件路径发送。因此,如果您使用 CMD 而不是 PHP 函数,您必须首先将签名和源代码保存为文件。如果需要,请记住转换编码。
PHP 函数期望作为参数接收签名者的公钥。因此,在此之前,您必须检查证书是否由受信任的证书颁发机构 (CA) 颁发。相反 - CMD 工具期望作为参数接收包含受信任证书颁发机构的根证书列表的文件。这意味着 - 您必须在验证之前检查签名者。
似乎在 Firefox/Mozilla/Chrome 浏览器下,无法准确限制用户使用哪个证书进行签名。但是可以限制选项。当然,这根本没有记录(或者我没有找到任何有关此的适当信息)。因此,该signText()
函数需要第三个参数,该参数必须是受信任的证书颁发机构名称。首先,我希望这必须是客户证书颁发者的 CN。但实际上并非如此。它必须是用逗号分隔的所有发行者字符串,例如:
C=Country,ST=State,L=Location,O=Organization,CN=CommonName,STREET=Address
openssl
不幸的是,这个字符串与看起来像的发行者字符串略有不同
issuer=/streetAddress=Address/CN=CommonName/O=Organization/L=Location/ST=State/C=Country.
如果有人在 Linux 下使用 Firefox,那么检查该字符串的格式是否相同会非常有趣。我不知道如何在单个字符串中分隔更多 CA 名称。在 Internet Explorer 下,使用CAPICOM
它可以只向签名对象发送一个证书对象,因此它不会打开从列表中选择证书的对话框。您可以通过比较根证书指纹(BASE64 65 chr/line 编码证书的 SHA1 哈希,不包括页眉和页脚)和证书的序列号来找到正确的证书。只需阅读 MSDN,它就有很好的文档记录。现在。我的源代码是什么样的:
如果签名是由CAPICOM
of生成的window.crypto
(我从 Web 浏览器收到一个附加参数,签名是如何生成的)如果CAPICOM
使用,我将源数据转换如下:
$_POST['source'] = iconv('UTF-8', 'UTF-16LE', $_POST['source'])
我生成 2 个临时文件。文件的名称可以使用 PHP 函数生成uniqid()
。默认临时目录可以使用sys_get_temp_dir()
. 源文件完全按照从POST
阵列接收到的方式保存。如果签名是由它生成的,CAPICOM
则必须将其转换为 UTF-16LE。
签名来自网络浏览器 BASE64 编码。不要解码它。只需将其保存在一个新文件中并添加一个特殊的页眉和页脚,如下所示:
"-----BEGIN PKCS7-----\n".$_POST['signature']."\n-----END PKSC7-----"
请注意,页眉和页脚必须位于不同的行上。行分隔符只能是 \n (ASCII #10) 而不是 \r (ASCII #13) 或 \r\n (ASCII #13#10)。您必须拥有一个包含所有受信任 CA 根证书的文件。该文件的格式必须如下:
A header "-----BEGIN CERTIFICATE-----"
BASE64 encoded certificate
A footer "-----END CERTIFICATE-----"
empty line
如果您有多个受信任的 CA 根 - 每个证书必须以标头开头并以页脚字符串结尾。所有证书都存储在一个文件中openssl
使用以下参数运行 CMD 工具:
smime
- 使用 CMD 工具的 SMIME 功能
-verify
- 做一个验证
-in filepath
- 步骤 4 中创建的签名文件的路径
-inform PEM
- 签名的格式是带有页眉和页脚的 BASE64 编码文件
-binary
- 防止将源代码从二进制转换为文本
-content filepath
- 在步骤 3 中创建的源文件的路径
-CAfile filepath
- 在步骤 5 中创建的可信 CA 根证书文件的路径
所以最终的命令如下所示:
openssl smime -verify -in file.pem -inform PEM -binary -content source.txt -CAfile root.pem
现在使用shell_exec()
函数从 PHP 调用它并读取输出。如果输出字符串以 Verification successful 开头 - 签名是 OK。如果输出字符串以验证失败开头 - 签名不正确。如果输出字符串不同 - 发生了一些错误。错误描述存储在输出字符串中。
以上对我有用。不幸的是openssl
,CAPICOM
并且window.crypto
非常棘手,并且总是有可能出现问题。希望这会对某人有所帮助。