从 ECSsaP192 public 密钥导入 CngKey

CngKey import from a ECSsaP192 public key

我正在验证提供 public 密钥的签名 MEkwEwYHKoZIzj0CAQYIKoZIzj0DAQEDMgAEMyHD625uvsmGq4C43cQ9BnfN2xslVT5V1nOmAMP6qaRRUll3PB1JYmgSm+62sosG

经过大量研究,我认为这是一个 ECDsaP192 标准密钥(如果我错了请纠正我)。 所以密钥的分解将是

      30 13
        06 07 2A 86 48 CE 3D 02 01
        06 08 2A 86 48 CE 3D 03 01 01
    03 32 00
    04
    33 21 C3 EB 6E 6E BE C9 86 AB 80 B8 DD C4 3D 6 77 CD DB 1B 25 55 3E 55  // Qx, 24 bytes
    D6 73 A6 0 C3 FA A9 A4 51 52 59 77 3C 1D 49 62 68 12 9B EE B6 B2 8B 6   // Qy, 24 bytes

我看到了一个 键的例子,它与我的情况非常相似,但我仍然无法让它为我工作。 我的代码:

private static readonly byte[] p192r1Prefix =
    Convert.FromBase64String("MEkwEwYHKoZIzj0CAQYIKoZIzj0DAQEDMgAE");

private static readonly byte[] s_cngBlobPrefix = { 0x45, 0x43, 0x53, 0x31, 0x18, 0, 0, 0 };

void Main()
{
    var pubkey = @"MEkwEwYHKoZIzj0CAQYIKoZIzj0DAQEDMgAEMyHD625uvsmGq4C43cQ9BnfN2xslVT5V1nOmAMP6qaRRUll3PB1JYmgSm+62sosG";
    var key = ImportECDsa256PublicKey(pubkey);
}

private static CngKey ImportECDsa256PublicKey(string base64)
{
    byte[] subjectPublicKeyInfo = Convert.FromBase64String(base64);
    byte[] prefix = p192r1Prefix;

    byte[] cngBlob = new byte[s_cngBlobPrefix.Length + 48];
    Buffer.BlockCopy(s_cngBlobPrefix, 0, cngBlob, 0, s_cngBlobPrefix.Length);

    Buffer.BlockCopy(
        subjectPublicKeyInfo,
        p192r1Prefix.Length,
        cngBlob,
        s_cngBlobPrefix.Length,
        48);

    return CngKey.Import(cngBlob, CngKeyBlobFormat.EccPublicBlob);  // Error: The parameter is incorrect.
}

编辑:使用 BouncyCastle

void Main()
{
    // Documentation https://developer.apple.com/documentation/storekit/skadnetwork/verifying_an_install_validation_postback
    var applePublicKey = @"MEkwEwYHKoZIzj0CAQYIKoZIzj0DAQEDMgAEMyHD625uvsmGq4C43cQ9BnfN2xslVT5V1nOmAMP6qaRRUll3PB1JYmgSm+62sosG";
    var keyBytes = Convert.FromBase64String(applePublicKey);
    var param = GetPublicKeyParam(keyBytes);
    
    var dataStr = "2.0" + '\u2063' + "com.example" + '\u2063' + "42" + '\u2063' + "525463029" + '\u2063' + "6aafb7a5-0170-41b5-bbe4-fe71dedf1e28" + '\u2063' + "1" + '\u2063' + "1234567891";
    var data = Encoding.UTF8.GetBytes(dataStr);
    var signature = "MDYCGQCsQ4y8d4BlYU9b8Qb9BPWPi+ixk/OiRysCGQDZZ8fpJnuqs9my8iSQVbJO/oU1AXUROYU=";
    var sigBytes = Convert.FromBase64String(signature);
    
    ISigner signer = SignerUtilities.GetSigner("SHA-256withECDSA");
    signer.Init(false, param);
    signer.BlockUpdate(data, 0, dataStr.Length);
    Console.WriteLine(signer.VerifySignature(sigBytes));
}
private ECPublicKeyParameters GetPublicKeyParam(byte[] publicKeyBytes)
{
    // parse based on asn1 format the content of the certificate
    var asn1 = (Asn1Sequence)Asn1Object.FromByteArray(publicKeyBytes);
    var at1 = (DerBitString)asn1[1];
    var xyBytes = at1.GetBytes();
    //retrieve preddefined parameters for P192(?) curve
    X9ECParameters x9 = NistNamedCurves.GetByName("P-192");
    //establish domain we will be looking for the x and y
    ECDomainParameters domainParams = new ECDomainParameters(x9.Curve, x9.G, x9.N, x9.H, x9.GetSeed());
    ECPublicKeyParameters publicKeyParams = new ECPublicKeyParameters(x9.Curve.DecodePoint(xyBytes), domainParams);
    return publicKeyParams;
}

发布的 public 密钥是 NIST P-192(又名 secp192r1 或 prime192v1)的 X.509/SPKI 密钥。签名以 ASN.1 格式给出。这可以使用 ASN.1 解析器最容易地验证,例如here.

代码中存在错误。在行

signer.BlockUpdate(data, 0, dataStr.Length);

dataStr.Length 必须替换为 data.Length。除此之外,代码有效。

然而,您的数据验证失败,即由于某种原因数据和签名不一致(可能public和私钥不匹配,或者签名是为其他数据创建的,等等)。

代码本身不是原因。我已经使用以下私有 (PKCS8) 和 public (X.509) 密钥(对于 NIST P-192)成功测试了(固定)代码:

-----BEGIN PRIVATE KEY-----
MG8CAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQEEVTBTAgEBBBgN4QXSZ9VyMP0sfb/E
vPObk83EHj2gemmhNAMyAAQESDhrEDN9oOetGgTzf+hN5Wm6xQqjOgjrDIdlXunl
gvQU9HS0dd/wzNuFy2pqD4I=
-----END PRIVATE KEY-----

-----BEGIN PUBLIC KEY-----
MEkwEwYHKoZIzj0CAQYIKoZIzj0DAQEDMgAEBEg4axAzfaDnrRoE83/oTeVpusUK
ozoI6wyHZV7p5YL0FPR0tHXf8Mzbhctqag+C
-----END PUBLIC KEY-----

如果您发布的明文使用上面的私钥签名,您将得到,例如,如下签名(Base64编码):

MDYCGQDrACykwYbQ6lQppw5PEcu5Bm7BuHjkVHoCGQDZ+RD3KvanoOzYj9bqQP2GHGhyrH6NOwA=

如果使用您的(固定)代码和上面的 public 密钥验证此签名,则验证成功

顺便说一句,导入 public 密钥更容易:

using Org.BouncyCastle.Security;
...
//var param = GetPublicKeyParam(keyBytes);          // remove
var param = PublicKeyFactory.CreateKey(keyBytes);   // add