.NET Core 1.0 and .NET Framework 4.7 have the ECParameters struct to import/export keys. The ToByteArray()
method you called is producing a CNG EccPublicBlob which has very little to do with the SEC-1 ECParameters format.
I'm going to assume that you wanted to use secp384r1/NIST P-384, even though you specified that as a hash algorithm. If you want some other curve, you'll need to do some translations.
The (.NET) ECParameters struct will only help you get started. Turning that into a file requires translating it into a PEM-encoded DER-encoded ASN.1-based structure. (But if you're sticking with NIST P-256/384/521, you can do it with the byte[] you currently have)
In SEC 1 v2.0 we get the following structures:
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier {{ECPKAlgorithms}} (WITH COMPONENTS {algorithm, parameters}),
subjectPublicKey BIT STRING
}
ECPKAlgorithms ALGORITHM ::= {
ecPublicKeyType |
ecPublicKeyTypeRestricted |
ecPublicKeyTypeSupplemented |
{OID ecdh PARMS ECDomainParameters {{SECGCurveNames}}} |
{OID ecmqv PARMS ECDomainParameters {{SECGCurveNames}}},
...
}
ecPublicKeyType ALGORITHM ::= {
OID id-ecPublicKey PARMS ECDomainParameters {{SECGCurveNames}}
}
ECDomainParameters{ECDOMAIN:IOSet} ::= CHOICE {
specified SpecifiedECDomain,
named ECDOMAIN.&id({IOSet}),
implicitCA NULL
}
An elliptic curve point itself is represented by the following type
ECPoint ::= OCTET STRING
whose value is the octet string obtained from the conversion routines given in Section 2.3.3.
Distilling this down to the relevant parts, you need to write
SEQUENCE (SubjectPublicKeyInfo)
SEQUENCE (AlgorithmIdentifier)
OBJECT IDENTIFIER id-ecPublicKey
OBJECT IDENTIFIER secp384r1 (or whatever named curve you're using)
BIT STRING
public key encoded as ECPoint
The AlgorithmIdentifier contains data that's fixed given you don't change the curve:
SEQUENCE (AlgorithmIdentifier)
30 xx [yy [zz]]
OBJECT IDENTIFIER id-ecPublicKey (1.2.840.10045.2.1)
06 07 2A 86 48 CE 3D 02 01
OBJECT IDENTIFIER secp384r1 (1.3.132.0.34)
06 05 2B 81 04 00 22
and we can now count how many bytes were in the payload: 16 (0x10), so we fill in the length:
30 10 06 07 2A 86 48 CE 3D 02 01 06 05 2B 81 04
00 22
The public key encoding that everyone understands is "uncompressed point", which is
04 th eb yt es of x. th eb yt es of y.
Turns out, that has a fixed size for a given curve, too, so unlike most things that are DER encoded, you can do this in one pass :). For secp384r1 the x and y coordinate are each 384 bit values, or (384 + 7)/8 == 48 bytes, so the ECPoint is 48 + 48 + 1 == 97 (0x61) bytes. Then it needs to be wrapped in a BIT STRING, which adds one payload byte and the length and tag. So, we get:
private static byte[] s_secp384r1PublicPrefix = {
// SEQUENCE (SubjectPublicKeyInfo, 0x76 bytes)
0x30, 0x76,
// SEQUENCE (AlgorithmIdentifier, 0x10 bytes)
0x30, 0x10,
// OBJECT IDENTIFIER (id-ecPublicKey)
0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01,
// OBJECT IDENTIFIER (secp384r1)
0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x22,
// BIT STRING, 0x61 content bytes, 0 unused bits.
0x03, 0x62, 0x00,
// Uncompressed EC point
0x04,
}
...
using (ECDiffieHellman ecdh = ECDiffieHellman.Create())
{
ecdh.KeySize = 384;
byte[] prefix = s_secp384r1PublicPrefix;
byte[] derPublicKey = new byte[120];
Buffer.BlockCopy(prefix, 0, derPublicKey, 0, prefix.Length);
byte[] cngBlob = ecdh.PublicKey.ToByteArray();
Debug.Assert(cngBlob.Length == 104);
Buffer.BlockCopy(cngBlob, 8, derPublicKey, prefix.Length, cngBlob.Length - 8);
// Now move it to PEM
StringBuilder builder = new StringBuilder();
builder.AppendLine("-----BEGIN PUBLIC KEY-----");
builder.AppendLine(
Convert.ToBase64String(derPublicKey, Base64FormattingOptions.InsertLineBreaks));
builder.AppendLine("-----END PUBLIC KEY-----");
Console.WriteLine(builder.ToString());
}
Running the output I got from that into OpenSSL:
$ openssl ec -pubin -text -noout
read EC key
(paste)
-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEwpbxYmcsNvr14D8k+0VQCkSY4WCV/3V10AiIq7sFdmUX
9+0DMuuLDmcKjL1ZFEFk0yHCPpY+pdkYtzPwE+dsApCPT3Ljk0AxHQBTSo4yjwsElMoA4Mtp8Qdo
LZD1Nx6v
-----END PUBLIC KEY-----
Private-Key: (384 bit)
pub:
04:c2:96:f1:62:67:2c:36:fa:f5:e0:3f:24:fb:45:
50:0a:44:98:e1:60:95:ff:75:75:d0:08:88:ab:bb:
05:76:65:17:f7:ed:03:32:eb:8b:0e:67:0a:8c:bd:
59:14:41:64:d3:21:c2:3e:96:3e:a5:d9:18:b7:33:
f0:13:e7:6c:02:90:8f:4f:72:e3:93:40:31:1d:00:
53:4a:8e:32:8f:0b:04:94:ca:00:e0:cb:69:f1:07:
68:2d:90:f5:37:1e:af
ASN1 OID: secp384r1
NIST CURVE: P-384