ECDSA 使用 public 密钥和来自 Java 的签名在 C# 中验证签名

ECDSA Verify Signature in C# using public key and signature from Java

我在 Java 中生成了一个 public 密钥和签名,我想在 C# 中使用 ECDsaCng 对其进行验证。 public 键是 MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExeg15CVOUcspdO0Pm27hPVx50thn0CGk3/3NLl08qcK+0U7cesOUUwxQetMgtUHrh0lNao5XRAAurhcBtZpo6w==

我通过获取最后 64 个字节并在其前面加上 0x45、0x43、0x53、0x31...,将其转换为可供 C# ECDsaCng 使用的密钥。

签名是使用 SHA256 在 Java 中生成的。有趣的是,如果我使用这里的工具测试签名 https://kjur.github.io/jsrsasign/sample/sample-ecdsa.html,它说这是一个有效的签名。

网上已经翻遍了,还是没有喜感。

代码如下

static void VerifySignature()
{
  var publicKey = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExeg15CVOUcspdO0Pm27hPVx50thn0CGk3/3NLl08qcK+0U7cesOUUwxQetMgtUHrh0lNao5XRAAurhcBtZpo6w==";
  byte[] publicKeyBytes = Convert.FromBase64String(publicKey);

  var keyType = new byte[] { 0x45, 0x43, 0x53, 0x31 };
  var keyLength = new byte[] { 0x20, 0x00, 0x00, 0x00 };
  var key = keyType.Concat(keyLength).Concat(publicKeyBytes.TakeLast(64)).ToArray(); // 4543533120000000c5e835e4254e51cb2974ed0f9b6ee13d5c79d2d867d021a4dffdcd2e5d3ca9c2bed14edc7ac394530c507ad320b541eb87494d6a8e5744002eae1701b59a68eb

  // For testing in online tool
  Debug.WriteLine(ByteArrayToString(publicKeyBytes.TakeLast(65).ToArray())); //04c5e835e4254e51cb2974ed0f9b6ee13d5c79d2d867d021a4dffdcd2e5d3ca9c2bed14edc7ac394530c507ad320b541eb87494d6a8e5744002eae1701b59a68eb


  var signature = "MEQCIFNEZQRzIrvr6dtJ4j4HP8nXHSts3w3qsRt8cFXBaOGAAiAJO/EjzCZlNLQSvKBinVHfSvTEmor0dc3YX7FPMnnYCg==";
            var signatureBytes = Convert.FromBase64String(signature); // 30440220534465047322bbebe9db49e23e073fc9d71d2b6cdf0deab11b7c7055c168e1800220093bf123cc266534b412bca0629d51df4af4c49a8af475cdd85fb14f3279d80a

  var data = Encoding.UTF8.GetBytes("ABCDEFGH");

  CngKey cngKey = CngKey.Import(key, CngKeyBlobFormat.EccPublicBlob);
  ECDsaCng eCDsaCng = new ECDsaCng(cngKey);

  bool result = eCDsaCng.VerifyData(data, signatureBytes); // result is false

  string ByteArrayToString(byte[] ba)
  {
    StringBuilder hex = new StringBuilder(ba.Length * 2);
    foreach (byte b in ba)
      hex.AppendFormat("{0:x2}", b);

    return hex.ToString();
  }
}


原因是格式不同。 Microsoft 期望格式为 r|s,而您的签名以 ASN.1 格式指定(在 ECDSA here 的上下文中进行了解释):

0x30|b1|0x02|b2|r|0x02|b3|s
b1 = Length of remaining data
b2 = Length of r
b3 = Length of s 

您的签名

30440220534465047322bbebe9db49e23e073fc9d71d2b6cdf0deab11b7c7055c168e1800220093bf123cc266534b412bca0629d51df4af4c49a8af475cdd85fb14f3279d80a

可以分为以下几部分

30 44 02 20 534465047322bbebe9db49e23e073fc9d71d2b6cdf0deab11b7c7055c168e180 02 20 093bf123cc266534b412bca0629d51df4af4c49a8af475cdd85fb14f3279d80a

以便可以轻松识别各个部分:

b1 = 0x44
b2 = 0x20
r  = 0x534465047322bbebe9db49e23e073fc9d71d2b6cdf0deab11b7c7055c168e180 
b3 = 0x20
s  = 0x093bf123cc266534b412bca0629d51df4af4c49a8af475cdd85fb14f3279d80a

因此

r|s = 534465047322bbebe9db49e23e073fc9d71d2b6cdf0deab11b7c7055c168e180093bf123cc266534b412bca0629d51df4af4c49a8af475cdd85fb14f3279d80a

如果您运行您的代码使用这种格式,则验证成功。