Search code examples
c#cryptographybouncycastle.net-4.7.2

Get uncompressed form of P-384 curve PK


I have a base-64 public key of a P-384 elliptic curve.

Trying to write a C# (.NET 4.7.2) code to get the uncompressed form (manage to do with OpenSSL, but from operatives reasons, cannot use it in production).

I can use Microsoft cryptography lib or Bouncy Castle.


Solution

  • It is not quite clear what format your public EC key has, probably X.509/SPKI. In this case the raw key is right at the end. However, it can be uncompressed or compressed. If the P-384 key starts with 0x3046, e.g.:

    3046301006072A8648CE3D020106052B8104002203320003873BE2A6061DA748EDC12609B0705226E041CED7109477E672ECE355181C861C975670372637F96FE9E79CE431E9C54E
    

    the raw key is compressed and the last 49 bytes are this raw compressed key (starting with 0x02 or 0x03).

    If it starts with 0x3076, e.g.:

    3076301006072A8648CE3D020106052B8104002203620004873BE2A6061DA748EDC12609B0705226E041CED7109477E672ECE355181C861C975670372637F96FE9E79CE431E9C54EF91B94E3AF12C084DBAF436DB786AAFC8251F847D604161823FDE990730650E3750E980C96D025E9B2EE4D783FF0BF15
    

    the raw key is uncompressed and the last 97 bytes are this raw uncompressed key (starting with 0x04).

    The raw key can thus be derived directly in this way. Depending on the key and your requirements, a conversion from compressed to uncompressed may be necessary (or vice versa), see the last section for this.


    The derivation of the raw key can also be done programmatically. For .NET 4.7.2 it is most convenient to use BouncyCastle. Here the key can be parsed directly as PEM and the raw key can be read:

    using Org.BouncyCastle.Crypto.Parameters;
    using Org.BouncyCastle.OpenSsl;
    using Org.BouncyCastle.Utilities.Encoders;
    ...
    string x509withUncomp = @"-----BEGIN PUBLIC KEY-----
                            MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEhzvipgYdp0jtwSYJsHBSJuBBztcQlHfm
                            cuzjVRgchhyXVnA3Jjf5b+nnnOQx6cVO+RuU468SwITbr0Ntt4aq/IJR+EfWBBYY
                            I/3pkHMGUON1DpgMltAl6bLuTXg/8L8V
                            -----END PUBLIC KEY-----";
    PemReader pemReaderPrivate = new PemReader(new StringReader(x509withUncomp));
    ECPublicKeyParameters ecPublicParameters = (ECPublicKeyParameters)pemReaderPrivate.ReadObject();
    byte[] compressed = ecPublicParameters.Q.GetEncoded(true);
    byte[] uncompressed = ecPublicParameters.Q.GetEncoded(false);
    Console.WriteLine(Hex.ToHexString(compressed));   // 03873be2a6061da748edc12609b0705226e041ced7109477e672ece355181c861c975670372637f96fe9e79ce431e9c54e
    Console.WriteLine(Hex.ToHexString(uncompressed)); // 04873be2a6061da748edc12609b0705226e041ced7109477e672ece355181c861c975670372637f96fe9e79ce431e9c54ef91b94e3af12c084dbaf436db786aafc8251f847d604161823fde990730650e3750e980c96d025e9b2ee4d783ff0bf15
    

    In the above example, the X.509/SPKI key contains the raw uncompressed key. However, the code works just as well with an X.509/SPKI key that contains a raw compressed key.

    Note: If your key is a Base64 DER encoded key, it has to be encoded as PEM by adding header and footer in separate lines. In the body there is a line break after every 64 characters (the latter is not mandatory for PemReader).


    If only the compressed key is to be converted to an uncompressed one, this is possible with:

    using Org.BouncyCastle.Asn1.Sec;
    using Org.BouncyCastle.Asn1.X9;
    using Org.BouncyCastle.Math.EC;
    using Org.BouncyCastle.Utilities.Encoders;
    ...
    X9ECParameters x9Params = SecNamedCurves.GetByName("secp384r1"); ;
    ECPoint ecpoint = x9Params.Curve.DecodePoint(Hex.Decode("03873BE2A6061DA748EDC12609B0705226E041CED7109477E672ECE355181C861C975670372637F96FE9E79CE431E9C54E"));
    byte[] compressed = ecpoint.GetEncoded(true);
    byte[] uncompressed = ecpoint.GetEncoded(false);
    Console.WriteLine(Hex.ToHexString(compressed));   // 03873be2a6061da748edc12609b0705226e041ced7109477e672ece355181c861c975670372637f96fe9e79ce431e9c54e
    Console.WriteLine(Hex.ToHexString(uncompressed)); // 04873be2a6061da748edc12609b0705226e041ced7109477e672ece355181c861c975670372637f96fe9e79ce431e9c54ef91b94e3af12c084dbaf436db786aafc8251f847d604161823fde990730650e3750e980c96d025e9b2ee4d783ff0bf15
    

    For the reverse direction, simply import the uncompressed key with DecodePoint().