如何使用PHP从浏览器检查数字签名

时间:2012-09-27 08:30:28

标签: php javascript digital-signature digital-certificate capicom

我有一个在浏览器中签名文本字符串的Javascript。它在Internet Explorer下使用CAPICOM,在Mozilla浏览器下使用window.crypto。签名过程后,我收到一个BASE64编码签名。

使用HTTPS我将签名和文本字符串上传到带有PHP应用程序的Web服务器。从SSL(HTTPS)我收到用户的证书。通过此证书,我可以提取用户的公钥。

现在我想验证签名文本字符串的签名以及用户的证书和公钥。我尝试使用openssl_verify PHP函数但没有成功。

我总是收到错误:

  

错误:0408D077:rsa例程:FIPS_RSA_VERIFY:签名长度错误

  • 我有证书,没关系;
  • 我从证书中提取了公钥,并且还经过验证并确定无误;
  • 我有签名(BASE64已解码);

不幸的是我无法验证签名?我无法提供演示或示例,因为它目前仅在本地网络中。

2 个答案:

答案 0 :(得分:2)

好的,最后我找到了解决方法。

  1. 我有一个用UTF-8编码的网页(这对后续步骤非常重要);
  2. 生成签名:
    • 如果用户使用Mozilla / Firefox / Chrome,则会使用window.crypto生成签名。有关详细信息,请参阅this
    • 如果用户使用Internet Explorer,则使用CAPICOM(Crypot应用程序接口COM对象)生成签名。有关更多信息,请阅读MSDN 对于使用网络而言,window.crypto和CAPICOM都没有很好的记录(CAPICOM不仅适用于网络!)
  3. 文本字符串和签名通过POST请求发送到网络服务器。
  4. 服务器(Linux,Apache和PHP)必须验证签名。
  5. 现在出现问题:

    1. PHP的openssl_verify()函数也没有很好地记录,并且总是返回零 - 签名无效。
    2. CMD工具openssl也未验证签名。
    3. 由于我的网络服务器需要SSL证书身份验证,因此我希望用户使用他登录的相同证书对文本字符串进行签名。
    4. 那么解决方法是什么:

      1. 我发现CAPICOM在签名之前将签名字符串转换为UTF-16LE。不幸的是,webbrowser将文本字符串发送到以UTF-8编码的web服务器。这意味着您必须在验证签名之前将字符串从UTF-8转换为UTF-16LE。但只有当签名由CAPICOM生成时才有效。
      2. openssl CMD工具现在正常工作。 CMD工具和PHP函数之间的区别是:
        • 签名和源代码以字符串形式发送给PHP函数。在使用CMD工具的情况下,签名和源作为文件路径发送。因此,如果您使用CMD而不是PHP函数,则首先将签名和源文件保存为文件。如果需要,请记住转换编码。
        • PHP函数需要作为参数来接收签名者的公钥。因此,在此之前,您必须检查证书是否由受信任的证书颁发机构(CA)颁发。相反 - CMD工具期望作为参数接收包含受信任证书颁发机构的根证书列表的文件。这意味着 - 您必须在验证前检查签名者。
      3. 似乎在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名称。
      4. 在Internet Explorer下,使用CAPICOM可以只向签名对象发送一个证书对象,因此不会打开对话框从列表中选择证书。您可以通过比较根证书指纹(除了页眉和页脚的BASE64 65 chr /行编码证书的SHA1哈希值)和证书的序列号来找到正确的证书。只需阅读MSDN就可以了解它。
      5. 现在。我的源代码是什么样的:

        1. 如果签名是由window.crypto的CAPICOM生成的(我从webbrowser收到一个额外的参数,如何生成签名)如果使用CAPICOM,我将转换源数据,如下所示:$_POST['source'] = iconv('UTF-8', 'UTF-16LE', $_POST['source'])
        2. 我生成了2个临时文件。可以使用PHP函数uniqid()生成文件的名称。可以使用sys_get_temp_dir()找到默认的临时目录。
        3. 源文件的保存方式与从POST阵列收到的完全相同。如果签名是由CAPICOM生成的,则必须将其转换为UTF-16LE。
        4. 签名来自网络浏览器BASE64编码。不要解码它。只需将其保存在一个新文件中并添加一个特殊的页眉和页脚如下:"-----BEGIN PKCS7-----\n".$_POST['signature']."\n-----END PKSC7-----"请注意页眉和页脚必须在单独的行上。行分隔符必须仅为\ n(ASCII#10)而不是\ r \ n(ASCII#13#10)的\ r(ASCII#13)。
        5. 您必须拥有一个包含所有可信CA根证书的文件。该文件的格式必须如下:
          • 标题" ----- BEGIN CERTIFICATE -----"
          • BASE64编码证书
          • 页脚" -----结束证书-----"
          • 空行
          • 如果您有多个受信任的CA根目录 - 每个证书必须以标头开头,并以页脚字符串结尾。所有证书都存储在一个文件中
        6. 使用下一个参数运行CMD工具openssl:
          • smime - 使用CMD工具的SMIME功能
          • -verify - 进行验证
          • -in filepath - 步骤4中创建的签名文件的路径
          • -inform PEM - 签名的forat是带有页眉和页脚的BASE64编码文件
          • -binary - 阻止将源从二进制转换为文本
          • -content filepath - 步骤3中创建的源文件的路径
          • -CAfile filepath - 在步骤5中创建的可信CA根证书文件的路径 所以最终命令如下所示:openssl smime -verify -in file.pem -inform PEM -binary -content source.txt -CAfile root.pem
        7. 现在使用shell_exec()函数从PHP调用此函数并读取输出。
          • 如果输出字符串以Verification successful开头 - 签名正常
          • 如果输出字符串以Verification failure开头 - 签名不正确
          • 如果输出字符串不同 - 发生了一些错误。错误描述存储在输出字符串中。
        8. 以上对我有用。不幸的是,openssl,CAPICOM和window.crypto非常棘手,并且始终可能出现问题。希望这会对某人有所帮助。

          最好的问候

答案 1 :(得分:0)

好的,最后我找到了解决方法。

我有一个用UTF-8编码的网页(这对后续步骤非常重要); 生成签名: 如果用户使用Mozilla / Firefox / Chrome,则使用window.crypto生成签名。有关更多信息,请阅读此 如果用户使用Internet Explorer,则使用CAPICOM(加密应用程序接口COM对象)生成签名。有关详细信息,请参阅阅读MSDN两者 - window.cryptoCAPICOM对于使用网络的文档记录不充分(CAPICOM不仅适用于网络!) 文本字符串和签名通过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 chr /行编码证书的SHA1哈希值)和证书的序列号来找到正确的证书。只需阅读MSDN,它就是有据可查的。 现在。我的源代码是什么样的:

如果签名是由CAPICOM的{​​{1}}生成的(我从Web浏览器收到一个额外的参数,如何生成签名)如果使用window.crypto我将源数据转换为这个:

CAPICOM

我生成了2个临时文件。可以使用PHP函数$_POST['source'] = iconv('UTF-8', 'UTF-16LE', $_POST['source']) 生成文件的名称。可以使用uniqid()找到默认的临时目录。 源文件的保存完全与从sys_get_temp_dir()数组接收的文件一样。如果签名是由POST生成的,则必须将其转换为UTF-16LE。

签名来自网页浏览器BASE64编码。不要解码它。只需将其保存在一个新文件中,然后添加一个特殊的页眉和页脚:

CAPICOM

请注意,页眉和页脚必须位于不同的行上。行分隔符必须仅为\ n(ASCII#10)而不是\ r \ n(ASCII#13)或\ r \ n(ASCII#13#10)。 您必须具有包含所有受信任的CA根证书的文件。该文件的格式必须如下:

"-----BEGIN PKCS7-----\n".$_POST['signature']."\n-----END PKSC7-----" 

如果您有多个受信任的CA根目录 - 每个证书必须以标头开头,并以页脚字符串结尾。所有证书都存储在一个文件中 使用下一个参数运行CMD工具A header "-----BEGIN CERTIFICATE-----" BASE64 encoded certificate A footer "-----END CERTIFICATE-----" empty line

  • openssl - 使用CMD工具的SMIME功能
  • smime - 进行验证
  • -verify - 步骤4中创建的签名文件的路径
  • -in filepath - 签名的forat是带有页眉和页脚的BASE64编码文件
  • -inform PEM - 阻止将源从二进制转换为文本
  • -binary - 步骤3中创建的源文件的路径
  • -content filepath - 步骤5中创建的可信CA根证书文件的路径

所以最后的命令看起来像这样:

-CAfile filepath

现在使用openssl smime -verify -in file.pem -inform PEM -binary -content source.txt -CAfile root.pem 函数从PHP调用此函数并读取输出。 如果输出字符串以验证成功开始 - 签名就可以了。 如果输出字符串以验证失败开始 - 签名不正确。 如果输出字符串不同 - 发生了一些错误。错误描述存储在输出字符串中。

以上对我有用。遗憾的是,shell_exec()opensslCAPICOM非常棘手,并且始终可能出现问题。希望这会对某人有所帮助。