【问题标题】:Validate EC SHA 256 signature in .net without bouncy castle在没有充气城堡的情况下验证 .net 中的 EC SHA 256 签名
【发布时间】:2021-09-12 07:54:51
【问题描述】:

我正在实施 Apple 的 App Attestation 服务。

作为流程的一部分,我收到一个 EC 密钥和一个签名。

示例键:

-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEd34IR9wYL76jLyZ148O/hjXo9iaF
z/q/xEMXCwYPy6yxbxYzWDZPegG4FH+snXaXQPYD6QIzZNY/kcMjIGtUTg==
-----END PUBLIC KEY-----

样本签名:

MEUCIQDXR/22YAi90PUdKrtTHwigrDxWFoiCqPLB/Of1bZPCKQIgNLxFAeUU2x+FSWfhRGX0SOKUIDxPRoigsCHpJxgGXXU=

示例 sha256 哈希:

S3i6LAEzew5SDjQbq59/FraEAvGDg9y7fRIfbnhHPf4=

如果我把它放到几个 txt 文件中:

System.IO.File.WriteAllBytes("/wherever/sig", Convert.FromBase64String(sampleSignature));

System.IO.File.WriteAllBytes("/wherever/hash", Convert.FromBase64String(sampleSha256Hash));

然后我可以像这样使用 Openssl 验证签名

openssl dgst -sha256 -verify sampleKey.pem -signature /wherever/sig /wherever/hash

(以上输出)

验证正常

我可以像这样使用 Bouncy Castle 验证签名:

var bouncyCert = DotNetUtilities.FromX509Certificate(certificate);
var bouncyPk = (ECPublicKeyParameters)bouncyCert.GetPublicKey();
var verifier = SignerUtilities.GetSigner("SHA-256withECDSA");
verifier.Init(false, bouncyPk);
verifier.BlockUpdate(sha256HashByteArray, 0, sha256HashByteArray.Length);
var valid = verifier.VerifySignature(signature); // Happy days, this is true

由于我不想在这里分享我的整个证书,因此可以通过以下方式实现相同的样本:

// these are the values from the sample key shared at the start of the post
// as returned by BC. Note that .Net's Y byte array is completely different.

 Org.BouncyCastle.Math.BigInteger x = new Org.BouncyCastle.Math.BigInteger(Convert.FromBase64String("d34IR9wYL76jLyZ148O/hjXo9iaFz/q/xEMXCwYPy6w="));
Org.BouncyCastle.Math.BigInteger y = new Org.BouncyCastle.Math.BigInteger(Convert.FromBase64String("ALFvFjNYNk96AbgUf6yddpdA9gPpAjNk1j+RwyMga1RO"));

X9ECParameters nistParams = NistNamedCurves.GetByName("P-256");
ECDomainParameters domainParameters = new ECDomainParameters(nistParams.Curve, nistParams.G, nistParams.N, nistParams.H, nistParams.GetSeed());
var G = nistParams.G;
Org.BouncyCastle.Math.EC.ECCurve curve = nistParams.Curve;
Org.BouncyCastle.Math.EC.ECPoint q = curve.CreatePoint(x, y);

ECPublicKeyParameters pubkeyParam = new ECPublicKeyParameters(q, domainParameters);

var verifier = SignerUtilities.GetSigner("SHA-256withECDSA");
verifier.Init(false, pubkeyParam);
verifier.BlockUpdate(sha256HashByteArray, 0, sha256HashByteArray.Length);
var valid = verifier.VerifySignature(signature); // again, happy days.

但是,我真的想避免使用充气城堡。

所以我正在尝试使用 .net 核心中提供的 ECDsa:

using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

var certificate = new X509Certificate2(cert);
var publicKey = certificate.GetECDsaPublicKey();
var valid = publicKey.VerifyHash(sha256HashByteArray, signature); // FALSE :(

如果您想尝试运行上面的代码,这里是创建没有整个证书的密钥的示例:

using System.Security.Cryptography;

var ecParams = new ECParameters();
ecParams.Curve = ECCurve.CreateFromValue("1.2.840.10045.3.1.7");
ecParams.Q.X = Convert.FromBase64String("d34IR9wYL76jLyZ148O/hjXo9iaFz/q/xEMXCwYPy6w=");
// I KNOW that this is different from BC sample - i got each respective values from
// certificates in respective libraries, and it seems the way they format the coordinates
// are different.
ecParams.Q.Y = Convert.FromBase64String("sW8WM1g2T3oBuBR/rJ12l0D2A+kCM2TWP5HDIyBrVE4=");

var ecDsa = ECDsa.Create(ecParams);

var isValid = ecDsa.VerifyHash(nonce, signature); // FALSE :(

我尝试改用VerifyData() 并提供原始数据和HashAlgorithmName.SHA256,但没有成功。

我在这里找到了一个回复 (https://stackoverflow.com/a/49449863/2057955),这似乎表明 .net 期望签名为 r,s 连接,所以我将它们从我从我的设备返回的 DER 序列中提取出来(参见示例签名)但是那根本没有运气,我就是无法找回那个“真实”。

问题:如何在 LINUX/MacO 上使用 .Net Core 验证这个 EC 签名(因此无法使用 ECDsaCng 类)?

【问题讨论】:

    标签: cryptography ecdsa devicecheck


    【解决方案1】:

    SignerUtilities.GetSigner() 隐式散列,即sha256HashByteArray 再次散列。因此,必须使用 ECDsa#VerifyData()(隐式哈希)方法而不是 ECDsa#VerifyHash()(不隐式散列)。
    此外,SignerUtilities.GetSigner() 会返回 ASN.1 格式的签名,ECDsa#VerifyData() 需要 r|s 格式的签名(如您所见)。
    如果两者都考虑,则验证成功:

    byte[] publicKey = Convert.FromBase64String("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEd34IR9wYL76jLyZ148O/hjXo9iaFz/q/xEMXCwYPy6yxbxYzWDZPegG4FH+snXaXQPYD6QIzZNY/kcMjIGtUTg==");
    byte[] sha256HashByteArray = Convert.FromBase64String("S3i6LAEzew5SDjQbq59/FraEAvGDg9y7fRIfbnhHPf4=");
    byte[] signatureRS = Convert.FromBase64String("10f9tmAIvdD1HSq7Ux8IoKw8VhaIgqjywfzn9W2Twik0vEUB5RTbH4VJZ+FEZfRI4pQgPE9GiKCwIeknGAZddQ==");
    
    var ecDsa = ECDsa.Create();
    ecDsa.ImportSubjectPublicKeyInfo(publicKey, out _);
    
    var isValid = ecDsa.VerifyData(sha256HashByteArray, signatureRS, HashAlgorithmName.SHA256);
    Console.WriteLine(isValid); // True
    

    关于签名格式:

    ASN.1 格式的张贴签名

    MEUCIQDXR/22YAi90PUdKrtTHwigrDxWFoiCqPLB/Of1bZPCKQIgNLxFAeUU2x+FSWfhRGX0SOKUIDxPRoigsCHpJxgGXXU=
    

    十六进制编码

    3045022100d747fdb66008bdd0f51d2abb531f08a0ac3c56168882a8f2c1fce7f56d93c229022034bc4501e514db1f854967e14465f448e294203c4f4688a0b021e92718065d75
    

    由此,r|s 格式的签名可以推导出为 (s.here)

    d747fdb66008bdd0f51d2abb531f08a0ac3c56168882a8f2c1fce7f56d93c22934bc4501e514db1f854967e14465f448e294203c4f4688a0b021e92718065d75
    

    或 Base64 编码:

    10f9tmAIvdD1HSq7Ux8IoKw8VhaIgqjywfzn9W2Twik0vEUB5RTbH4VJZ+FEZfRI4pQgPE9GiKCwIeknGAZddQ==
    

    【讨论】:

    • 这太棒了。您能否展示如何以编程方式将 ASN.1 签名转换为 r|s 格式? (例如,显示From this, the signature in r|s format can be derived as 的代码)
    • (我可以再次使用 BouncyCastle 做到这一点,但我真的很想避免它并且没有太多来自System.Formats.Asn1.AsnDecoder 的爱)
    • 我也试过System.Formats.Asn1.AsnReader,就像var reader = new AsnReader(signature, AsnEncodingRules.DER); var seqZ = reader.ReadSequence(); var rZ = seqZ.ReadIntegerBytes(); var sZ = seqZ.ReadIntegerBytes(); var b64 = Convert.ToBase64String(rZ.ToArray().Concat(sZ.ToArray()).ToArray());,我的b64包含"ANdH/bZgCL3Q9R0qu1MfCKCsPFYWiIKo8sH85/Vtk8IpNLxFAeUU2x+FSWfhRGX0SOKUIDxPRoigsCHpJxgGXXU="而不是你发布的内容
    • @zaitsman - 你的方法几乎是正确的。 rs 是最小尺寸的有符号大端和正数,前面可能需要一个额外的 0x00 字节。这在answer 中有详细说明。相关问题本身解释了转换。在您的示例中,r 有一个前导 0x00。如果去掉这个,结果是正确的。
    猜你喜欢
    • 2014-08-18
    • 2015-05-04
    • 1970-01-01
    • 2018-04-20
    • 1970-01-01
    • 1970-01-01
    • 2016-12-02
    • 2011-06-12
    • 1970-01-01
    相关资源
    最近更新 更多