6

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

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

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

我总是收到一个错误:

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

  • 我有证书,没问题;
  • 我有从证书中提取的公钥,它也经过验证并且可以;
  • 我有签名(BASE64 解码);

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

4

2 回答 2

2

好的,我终于找到了解决方法。

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

现在的问题:

  1. PHP的openssl_verify()功能也没有很好的文档记录,并且总是返回零 - 签名无效。
  2. CMD 工具 openssl 也没有验证签名。
  3. 因为我的 Web 服务器需要 SSL 证书身份验证,所以我希望用户使用他登录的相同证书对文本字符串进行签名。

那么解决方法是什么:

  1. 我发现 CAPICOM 在签名之前将签名字符串转换为 UTF-16LE。不幸的是,网络浏览器将文本字符串发送到以 UTF-8 编码的网络服务器。这意味着您必须在验证签名之前将字符串从 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/line 编码证书的 SHA1 哈希,不包括页眉和页脚)和证书的序列号来找到正确的证书。只需阅读 MSDN,它就有很好的文档记录。

现在。我的源代码是什么样的:

  1. 如果签名是由 CAPICOM 生成的window.crypto(我从 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 编码证书
    • 页脚“-----END CERTIFICATE-----”
    • 空行
    • 如果您有多个受信任的 CA 根 - 每个证书必须以标头开头并以页脚字符串结尾。所有证书都存储在一个文件中
  6. 使用下一个参数运行 CMD 工具 openssl:
    • 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
  7. 现在使用shell_exec()函数从 PHP 调用它并读取输出。
    • 如果输出字符串以Verification successful- 开头,则签名正常
    • 如果输出字符串以Verification failure- 开头,则签名不正确
    • 如果输出字符串不同 - 发生了一些错误。错误描述存储在输出字符串中。

以上对我有用。不幸的是,openssl、CAPICOM 和window.crypto非常棘手,总是有可能出现问题。希望这会对某人有所帮助。

最好的祝福

于 2012-10-01T08:51:28.987 回答
0

好的,我终于找到了解决方法。

我有一个以 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

opensslCMD 工具现在工作正常。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,它就有很好的文档记录。现在。我的源代码是什么样的:

如果签名是由CAPICOMof生成的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。如果输出字符串以验证失败开头 - 签名不正确。如果输出字符串不同 - 发生了一些错误。错误描述存储在输出字符串中。

以上对我有用。不幸的是opensslCAPICOM并且window.crypto非常棘手,并且总是有可能出现问题。希望这会对某人有所帮助。

于 2013-08-12T02:35:19.050 回答