[英]How to check a digital signature from broswer with PHP
我有一个在浏览器中签署文本字符串的Javascript。 它使用Internet Explorer下的CAPICOM和Mozilla浏览器下的window.crypto。 在签名过程之后,我收到了BASE64编码的签名。
使用HTTPS,我将签名和文本字符串上载到具有PHP应用程序的Web服务器。 从SSL(HTTPS),我收到用户的证书。 从该证书中,我可以提取用户的公钥。
现在,我想根据签名的文本字符串以及用户的证书和公钥来验证签名。 我尝试用openssl_verify PHP函数没有成功。
我总是收到一个错误:
错误:0408D077:RSA例程:FIPS_RSA_VERIFY:签名长度错误
不幸的是我无法验证签名? 我无法提供演示或示例,因为它目前仅在本地网络中。
好的,最后我找到了解决方法。
现在的问题:
openssl_verify()
函数也没有得到很好的说明,并且始终返回零-签名无效。 那么解决方法是什么:
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名称。 现在。 我的源代码如下所示:
window.crypto
的CAPICOM生成的(我从Web浏览器收到一个附加参数,如何生成签名)如果使用了CAPICOM,我将像这样转换源数据: $_POST['source'] = iconv('UTF-8', 'UTF-16LE', $_POST['source'])
"-----BEGIN PKCS7-----\\n".$_POST['signature']."\\n-----END PKSC7-----"
注意,页眉和页脚必须位于单独的行上。 行分隔符只能是\\ r \\ n(ASCII#13#10)的\\ n(ASCII#10)而不是\\ r(ASCII#13)。 smime
使用CMD工具的SMIME功能 -verify
进行验证 -in filepath
在步骤4中创建的签名文件的路径 -inform PEM
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
Verification successful
开头-签名正常 Verification failure
开头-签名不正确 以上对我有用。 不幸的是,openssl,CAPICOM和window.crypto
非常棘手,并且总是有可能发生问题。 希望这会帮助到别人。
最好的祝福
好的,最后我找到了解决方法。
我有一个用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 。 不幸的是,Web浏览器将文本字符串发送到以UTF-8编码的Web服务器。 这意味着您必须在验证签名之前将字符串从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字符/行编码的证书的SHA1哈希,不包括页眉和页脚)与证书的序列号进行比较,找到合适的证书。 只需阅读MSDN,它有据可查。 现在。 我的源代码如下所示:
如果签名是由window.crypto
的CAPICOM
生成的(我从Web浏览器收到一个附加参数,如何生成签名)如果使用了CAPICOM
,我将像这样转换源数据:
$_POST['source'] = iconv('UTF-8', 'UTF-16LE', $_POST['source'])
我生成2个临时文件。 可以使用PHP函数uniqid()
生成文件名。 可以使用sys_get_temp_dir()
找到默认的临时目录。 源文件的保存与从POST
数组接收的完全相同。 如果签名是由CAPICOM
生成的,则必须将其转换为UTF-16LE。
签名来自BASE64编码的Web浏览器。 不要解码。 只需将其保存在新文件中,然后添加一个特殊的页眉和页脚,如下所示:
"-----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根-每个证书必须以标头开头,并以脚注字符串结尾。 所有证书都存储在一个文件中。使用以下参数运行CMD工具openssl
:
smime
使用CMD工具的SMIME功能 -verify
进行验证 -in filepath
在步骤4中创建的签名文件的路径 -inform PEM
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调用此函数并读取输出。 如果输出字符串以“验证成功”开头-签名确定。 如果输出字符串以“验证失败”开头-签名不正确。 如果输出字符串不同-发生了一些错误。 错误说明存储在输出字符串中。
以上对我有用。 不幸的是, openssl
, CAPICOM
和window.crypto
非常棘手,并且总是有可能发生问题。 希望这会帮助到别人。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.