生成 RSA 密钥的签名给错误的数据

Generate signature of RSA key give wrong data

我正在使用 https://github.com/phpseclib/phpseclib 生成签名。但是当我用我的 RSA 私钥生成签名时,它生成了签名,但当我用我的 public 密钥验证时它也会给我 unverified .

这是我的代码:

<?php

// Include library
include('Crypt/RSA.php');

// Create an Instance
$rsa = new Crypt_RSA();
$hash = new Crypt_Hash('sha256');

$data = array(
     "name" => "hello"
);

$data = json_encode($data, true);

// Load Private Key
$rsa->loadKey('-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAlwK9IAETYwSW6p0hVEue3+WyMtRW5MA3BS2Bzf3B0Qimnvfl
....
-----END RSA PRIVATE KEY-----');

// Message to be signed
$plaintext = $data;

// Set signing signature
$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
$hashed = $hash->hash($plaintext); 
$encrypted = $rsa->sign($hashed); // Sign Data
$signature = base64_encode($encrypted); // Encode to base64 the signed Data

echo $signature;

$rsa->loadKey('-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlwK9IAETYwSW6p0hVEue
.....
-----END PUBLIC KEY-----'); // public key
echo $rsa->verify($plaintext, $signature) ? 'verified' : 'unverified';

现在上面的代码生成了签名但是不正确。

现在我已经尝试 BouncyCastle lib 仅在 .NET 中使用上述密钥生成签名。它生成了完美的签名,并且已经过验证。

我不知道 phpseclib 库有什么问题。我也直接尝试用 openssl 生成,但它仍然没有生成正确的。

有两个问题:

  • 要签名的数据必须传递给 sign 方法,而不是这些数据的散列。 sign 方法本身执行散列。默认情况下,sha1 用作摘要。必须使用 setHash.
  • 明确指定不同的摘要
  • 必须将签名传递给 verify 方法,而不是 Base64 编码的签名。

尝试以下修改:

...
$rsa->setHash('sha256');                                                         // Specify digest, e.g. sha256 (default is sha1)
$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
$signature = $rsa->sign($plaintext);                                             // Pass plaintext instead of plaintext hash.
echo base64_encode($signature) . "\n";
...
echo $rsa->verify($plaintext, $signature) ? 'verified' : 'unverified' . "\n";    // Pass signature instead of Base64 encoded signature
...

文档中也有对应的example


更新:

C# 代码中的规范 SignerUtilities.GetSigner("RSA") 意味着数据已签名 没有 之前的散列,即因此散列必须 显式 之前执行过,现在的C#代码也是如此(使用SHA256)。

但是,这种方式与标准方式不同RSASSA-PKCS1-v1_5 (described in RFC 8017) in that RSASSA-PKCS1-v1_5 places a digest ID before the hash (in the case of SHA256, this is the ID 0x3031300d060960864801650304020105000420)。因此,使用 phpseclib(当然)使用 RSASSA-PKCS1-v1_5 的验证失败。

要在 C# 代码中也使用 RSASSA-PKCS1-v1_5,请手动添加摘要 ID 或使用 SignerUtilities.GetSigner("SHA256withRSA"),以便在签名之前对数据进行哈希处理(然后当然是 explicit 必须省略哈希)。


如果 C# 代码是参考,则需要一个库,无需事先散列即可用于签名,类似于 C# 代码。

phpseclib 使用 RSASSA-PKCS1-v1_5 填充 signing/verifying (对于签名模式 CRYPT_RSA_SIGNATURE_PKCS1)和不同的 RSAES-PKCS1-v1_5 填充encryption/decryption(用于加密模式CRYPT_RSA_ENCRYPTION_PKCS1)。因此,这些都不是这里的选项。

如@neubert 的评论所述,phpseclib 提供了在 encryption/decryption 期间禁用填充的可能性(对于加密模式 CRYPT_RSA_ENCRYPTION_NONE 填充是通过 0x00 值)。这允许通过实施自定义填充然后使用私钥加密来进行自定义签名。在当前情况下,这将需要实施 RSASSA-PKCS1-v1_5 填充 而没有 添加摘要 ID 的部分。

这里更简单的是使用已经应用RSASSA-PKCS1-v1_5填充如果提供了摘要ID和散列的替代方法,这在当前情况下意味着只需要传递散列。此类替代方案的示例是 openssl_private_encrypt for signing and openssl_public_decrypt 用于验证:

$data = array(
    "name" => "hello"
);
$data = json_encode($data, true);

$hashed = hash('sha256', $data, true);                            // Hash data using sha256

// Signing
openssl_private_encrypt($hashed, $signature, $privateKey);        // Encrypt data using the private key
echo base64_encode($signature) . "\n";                            // Output: csorNNekiVXJzQaT/FVCKrlSPfonQYw7dStKsjgVuW/jAcRafk4Yeuzw4rc1WxfgheMaa31DROnhrRCBS6VNUm8w2N/XwX2zmImFLj5KZlnRPne7CZX3YHM3qa5CRf/1VWdNnXMZnBecIe6QltECQLsQ/8fRzaYJpZO6tZjIRf4fBYwqMHWhHlTn6UR3A0GDKSEU5mI2lvzl+9ov+x6AFCLT3sa8hc3aZDfO7SWmQHNHCwrBC9QFNTOHY+/oJXtKYMfY3IskvLSmKmvKc6vh8XANBYp+NCl3/9hNdhbF4psdJXuZgf3aSpy0koBttdcE3js0NoS8Q91ry2WJtkcCrA==

// Verifying
openssl_public_decrypt($signature, $decrypted, $publicKey);       // Decrypt data using the public key
$verified = ($decrypted == $hashed) ? 'verified' : 'unverified';  // Verify data 
echo $verified;