Linux/Ubuntu 上的 ECDsaCng

ECDsaCng on Linux/Ubuntu

我是 .NET 库的作者,该库允许开发人员处理第三方提供的数据。在我的图书馆提供的众多功能中,有一项功能是能够验证接收到的数据是否确实由相关的第 3 方签名。第三者提供以下资料:

开发人员希望我的库 return 一个布尔值,指示数据是否合法。感谢 and, similarly, I figured out how to convert the public key into Microsoft CNG supported format thanks to ,我能够弄清楚如何将签名转换为 Microsoft CNG 支持的格式。我将它们放在以下 C# 代码片段中:

// Convert the signature and public key provided by the 3rd party into formats usable by the .net crypto classes
var signatureBytes = Convert.FromBase64String(signature);
var sig = ConvertECDSASignature.LightweightConvertSignatureFromX9_62ToISO7816_8(256, signatureBytes);
var cngBlob = Utils.ConvertSecp256R1PublicKeyToEccPublicBlob(publicKey);

// Verify the signature
var cngKey = CngKey.Import(cngBlob, CngKeyBlobFormat.EccPublicBlob);
var eCDsaCng = new ECDsaCng(cngKey);
var verified = eCDsaCng.VerifyData(data, sig);

在开发人员最近抱怨 Linux/Ubuntu 机器上的 System.PlatformNotSupportedException 之前,它一直运行良好。经过快速研究,我发现 ECDsaCng 是 Windows 特定的,我应该使用跨平台的 ECDsa。

所以我想出了下面的代码,它不仅是跨平台的,而且比我原来的代码简单得多,因为我不再需要将签名和 public 密钥转换为不同的格式:

var signatureBytes = Convert.FromBase64String(signature);
var publicKeyBytes = Convert.FromBase64String(publicKey);

// Verify the signature
var eCDsa = ECDsa.Create();
eCDsa.ImportSubjectPublicKeyInfo(publicKeyBytes, out _);
var verified = eCDsa.VerifyData(data, signatureBytes, HashAlgorithmName.SHA256, DSASignatureFormat.Rfc3279DerSequence);

唯一需要注意的是,Microsoft 在更新的 .NET 框架版本中的 ECDsa class 中引入了 ImportSubjectPublicKeyInfo 方法(我可能是错的,但我相信它是在 .NET 中引入的core 3.1) 和我的库目标 netstandard2.0 因此它可以供不一定使用最新 .NET 的开发人员使用。

所以,所有这些都是说:有没有一种方法可以使用提供的签名和 public 密钥以跨平台并可在 netstandard2.0 中使用的方式验证数据?

ECDsa.ImportSubjectPublicKey() is supported in .NET Core 3.0 and later, but not in .NET Framework. An alternative would be ECDsa.ImportParameters(), which according to the documentation is supported as of .NET Framework 4.7 and .NET Core 1.0. DSASignatureFormat 自 .NET 5.0 起受支持。

因此,一个可能的替代方案是当前代码,密钥导入修改如下:

// Test data
byte[] data = Encoding.UTF8.GetBytes("The quick brown fox jumps over the lazy dog");               
string signature = "MEUCIFBfoMubs82ExlbPQHR2LKKcJpvODxaoo4NO4VoKmRfxAiEAg6tug3ctzSAZrkF175B71D7Uynn9Bc1O40XIpxD93MY=";
string publicKey = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMpHT+HNKM7zjhx0jZDHyzQlkbLV0xk0H/TFo6gfT23ish58blPNhYrFI51Q/czvkAwCtLZz/6s1n/M8aA9L1Vg==";

// Convert the signature and public key provided by the 3rd party into formats usable by the .net crypto classes
var signatureBytes = Convert.FromBase64String(signature);
var sig = lightweightConvertSignatureFromX9_62ToISO7816_8(256, signatureBytes);
ECParameters ecParameters = ConvertSecp256r1PublicKeyToECParameters(publicKey);     // Replaced!

// Verify the signature
var eCDsa = ECDsa.Create();
eCDsa.ImportParameters(ecParameters);
var verified = eCDsa.VerifyData(data, sig, HashAlgorithmName.SHA256);

其中 ConvertSecp256r1PublicKeyToECParameters()ConvertSecp256R1PublicKeyToEccPublicBlob() 的略微修改版本:

private static readonly byte[] s_secp256r1Prefix = Convert.FromBase64String("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE");
private static ECParameters ConvertSecp256r1PublicKeyToECParameters(string base64)
{
    byte[] subjectPublicKeyInfo = Convert.FromBase64String(base64);

    if (subjectPublicKeyInfo.Length != 91)
        throw new InvalidOperationException();

    byte[] prefix = s_secp256r1Prefix;

    if (!subjectPublicKeyInfo.Take(prefix.Length).SequenceEqual(prefix))
        throw new InvalidOperationException();

    byte[] x = new byte[32];
    byte[] y = new byte[32];
    Buffer.BlockCopy(subjectPublicKeyInfo, prefix.Length, x, 0, x.Length);
    Buffer.BlockCopy(subjectPublicKeyInfo, prefix.Length + x.Length, y, 0, y.Length);

    ECParameters ecParameters = new ECParameters();
    ecParameters.Curve = ECCurve.NamedCurves.nistP256; // aka secp256r1 aka  prime256v1
    ecParameters.Q.X = x;
    ecParameters.Q.Y = y;

    return ecParameters;
}

从 .NET Framework 4.7 和 .NET Core 2.0 开始,我的机器 (Windows 10) 支持该代码(在 1.0 下不支持 运行,但这可能是本地的问题)。我看不出为什么 Linux/Ubuntu 会出现问题。但是,我没有测试过这个。