【问题标题】:RSA-SHA1 signature differs in JavaScript and PHPRSA-SHA1 签名在 JavaScript 和 PHP 中不同
【发布时间】:2021-03-31 01:18:45
【问题描述】:

我需要在nodeJS中创建RSA-SHA1签名,我使用的是下面的代码

const crypto = require("crypto");
const sign = crypto.createSign('RSA-SHA1');
sign.update(data);
const result = sign.sign(privateKey, 'base64')
console.log(result);

我仔细检查了我的代码结果是否正确,如果我选择 sha1WithRSA 算法,我使用在线工具会得到相同的结果,当我使用 openssl_sign 函数时,我也会在 PHP 中得到相同的结果

我需要为银行 API 签署数据,不幸的是他们不接受我的签名,而且他们没有 NodeJS 实现

他们的文档也是一团糟,这是他们文档的C# 实现

RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.FromXmlString(“<RSAKeyValue><Modulus>oQRshGhLf2Fh...”);
string data = "DATA";
byte[] signMain = rsa.SignData(Encoding.UTF8.GetBytes(data), new
SHA1CryptoServiceProvider());
sign = Convert.ToBase64String(signMain); 

谁能帮我理解为什么我的签名不等于 C# 实现(我不检查 C# 的结果,但他们的服务器是用 C# 编写的,他们不验证我的签名)

以下链接到 PHP 中与服务器一起工作的其他官方代码片段,但结果与 PHP 中的标准 openssl_sign 函数不同:index L211-L216RSAProcessor L45rsa.class L17-L22rsa.class L63-L82

  1. 他们的实现是非标准的 RSA-SHA1 吗?
  2. 他们不接受我的 NodeJS 代码有什么问题?

【问题讨论】:

  • 我无法重现该问题。发布的 NodeJS 代码和 C# 代码为我的测试数据生成相同的签名。我建议您发布一个完整的示例,即要签名的测试消息和测试密钥,以及使用这两个代码生成的签名。
  • @Topaco 感谢您的回复,正如我之前提到的,他们的文档一团糟,而且我不是用 C# 代码生成的,但是我用他们的 PHP 代码进行了测试,签名结果不同,请尝试 PHP 版本pep.co.ir/uploads/pep-phpsample(v3.3.3).rar 它会生成不同的符号
  • 抱歉,我没有时间为这个 PHP 套件创建 MCVE。您应该这样做,以便可以使用 repro 来测试您的 NodeJS 代码。无论如何,如果银行 API 的 C# 代码与银行 API 的关于签名的 PHP 代码不一致,那就太奇怪了(但当然可能)。
  • 首先,显而易见:(a)验证的公钥与用于签名的私钥匹配? (b) 带符号的数据完全相同,包括字符编码和字节大小? (c) 签名的编码相同(如果签名大小相差超过 1 或 2 个字节,则表示不同的编码)。参考代码在签名上使用base-64,问题没有! (d) 带有 SHA-1 的 RSA 可以是 RSASSA-PKCS1-v1_5(旧但常见)或 RSASSA-PSS。

标签: javascript node.js cryptography rsa digital-signature


【解决方案1】:

NodeJS 代码和发布的 C# 代码为相同的待签名数据和相同的私钥提供相同的签名。

从发布的link 可以看出,SHA1 散列数据被传递给RSA::rsa_sign,但没有任何地方添加 SHA1 摘要 ID。与此一致的是函数参数中也没有指定摘要。

填充本身发生在RSA::add_PKCS1_padding。在这里,为了签名,应用了 RSASSA-PKCS1-v1_5。如前所述,处理传递的(已经散列的)数据,而不考虑摘要 ID。

因此,PHP代码创建的签名不符合RSASSA-PKCS1-v1_5,而NodeJS代码和C#代码都遵循标准。

据我所知,当使用 NodeJScrypto 模块的sign 方法签名时,必须始终指定摘要并且始终设置摘要 ID 隐式,所以PHP代码的签名不能用这个来复制。

允许设置摘要 ID 的替代方法是crypto.privateEncrypt。如果使用相同的数据和私钥,则以下代码生成PHP代码的签名或发布的link中描述的签名过程:

var crypto = require('crypto')

var sha1 = crypto.createHash('sha1')
sha1.update(data)
var hash = sha1.digest()

var signature = crypto.privateEncrypt({key: privateKey, padding: crypto.constants.RSA_PKCS1_PADDING}, hash) 
console.log(signature.toString('base64'))

另一个问题是这个签名是否可以被银行 API 验证。毕竟 API 的文档中显然包含了两个不一致的实现,所以文档看起来不是很可靠。此外,当然也可能存在其他缺陷,请参阅 fgrieu 的评论。

【讨论】:

  • 感谢您的回复,您能否告诉我有关摘要 ID 的更多信息,以及您能否使用 privateEncrypt 向我展示等效于 sign 的内容,仅用于我的教育目的
  • RSASSA-PKCS1-v1_5RFC 8017, sec. 8.2中有详细说明。这是在签名上下文中使用的填充。应用的摘要的 ID 放在散列数据之前,例如对于 SHA1,这是 0x3021300906052b0e03021a05000414here。如果在 privateEncrypt 调用中考虑到摘要 ID:Buffer.concat([Buffer.from("3021300906052b0e03021a05000414", "hex"), hash]),则复制 sign 调用的签名。
猜你喜欢
  • 2012-02-18
  • 2018-08-11
  • 2011-02-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-19
  • 1970-01-01
  • 2017-07-30
相关资源
最近更新 更多