ECDsaCng is an ECDSA implementation using Windows CNG. It's specific to Windows, so not supported on Linux.
The cross-platform way to do this would be
using (ECDsa ecdsa = ECDsa.Create())
{
ecdsa.ImportParameters(Pkcs8ToParameters(privateKey));
// the stuff in your current using
}
Of course, PKCS#8 to ECParameters isn't the easiest thing in the world. But we can give it a go. In another answer there's a breakdown of building a PKCS#8 for RSA.
Let's take this blob:
308187020100301306072A8648CE3D020106082A8648CE3D030107046D306B02
0101042070A12C2DB16845ED56FF68CFC21A472B3F04D7D6851BF6349F2D7D5B
3452B38AA144034200048101ECE47464A6EAD70CF69A6E2BD3D88691A3262D22
CBA4F7635EAFF26680A8D8A12BA61D599235F67D9CB4D58F1783D3CA43E78F0A
5ABAA624079936C0C3A9
It breaks down like
30 /* SEQUENCE */
81 87 (payload is 0x87 bytes)
02 /* INTEGER */ 01 (1 byte) 00 // Integer: 0. // validate this
30 /* SEQUENCE */ 13 (0x13 bytes)
06 /* OBJECT IDENTIFIER */ 07 (7 bytes)
2A8648CE3D0201 (1.2.840.10045.2.1 / ecPublicKey) // validate this
06 /* OBJECT IDENTIFIER */ 08 (8 bytes)
2A8648CE3D030107 (1.2.840.10045.3.1.7 / secp256r1) // save this, curveName
04 /* OCTET STREAM (byte[]) */ 6D (0x6D bytes)
// Since the constructed (0x20) bit isn't set in the tag normally we stop here,
// but we know from the ecPublicKey context that this is also DER data.
30 /* SEQUENCE */ 6B (0x6B bytes)
02 /* Integer */ 01 (1 byte) 01 // Integer: 1. // validate this.
04 /* OCTET STREAM (byte[]) */ 20 (0x20 bytes / 256 bits)
70A12C2DB16845ED56FF68CFC21A472B3F04D7D6851BF6349F2D7D5B3452B38A // save this: D
A1 /* CONSTRUCTED CONTEXT SPECIFIC 1 */ 44 (0x44 bytes)
03 /* BIT STRING (byte[] if the first byte is 0x00) */ 66 (0x66 bytes)
00 // Oh, good, it's a normal byte[]. Validate this.
// Formatting will become apparent. Save this.
04
8101ECE47464A6EAD70CF69A6E2BD3D88691A3262D22CBA4F7635EAFF26680A8
D8A12BA61D599235F67D9CB4D58F1783D3CA43E78F0A5ABAA624079936C0C3A9
The BIT STRING at the end is "the public key". Since it starts with 04 (which it usually will, unless the sender is mad at you) it represents an "uncompressed point", meaning the first half of what's left is the X coordinate, and the remainder is the Y coordinate. So from this structure you might get something like
string curveOid;
// You can decode the OID, or special case it.
switch (curveName)
{
case "2A8648CE3D030107":
// secp256r1
curveOid = "1.2.840.10045.3.1.7";
break;
case "2B81040022"
// secp384r1
curveOid = "1.3.132.0.34";
break;
case "2B81040023":
// secp521r1
curveOid = "1.3.132.0.35";
break;
default:
throw new InvalidOperationException();
}
return new ECParameters
{
Curve = ECCurve.CreateFromValid(curveOid),
// We saved this.
D = d,
Q = new ECPoint
{
X = x,
Y = y
},
}
This happens to be the key used in section D.1 (NIST P-256 / secp256r1) of Suite B Implementer’s Guide to FIPS 186-3 (ECDSA).
Since the EC key format is mercifully short on INTEGER values (which can require padding bytes) you can build a manual extractor for each keysize you want to support. Or you can go the live DER reading route. Or you can try to get your private key serialized in a more friendly form for your application.