Search code examples
javac#cryptographybouncycastleelliptic-curve

Java Bouncy Castle : Invalid point encoding 0x45


I am loading a public key in java using bouncy castle library but always getting error Invalid point encoding 0x45.

The public key is generated at client side using C# CNG APIs.

Java method 1:

public PublicKey loadPublicKey(String encodedPublicKey)
            throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException {
        
        byte[] keybytes = java.util.Base64.getDecoder().decode(encodedPublicKey);
        
        Security.addProvider(new BouncyCastleProvider());
        
        ECNamedCurveParameterSpec params = ECNamedCurveTable.getParameterSpec("P-256");
        
        ECPublicKeySpec keySpec = new ECPublicKeySpec(params.getCurve().decodePoint(keybytes), params);
        
        return new BCECPublicKey("ECDH", keySpec, BouncyCastleProvider.CONFIGURATION);

    }

Method 2

public PublicKey loadPublicKey(String pKey) throws Exception {
        byte[] keybytes = java.util.Base64.getDecoder().decode(pKey);
        Security.addProvider(new BouncyCastleProvider());
        ECParameterSpec params = ECNamedCurveTable.getParameterSpec("P-256");
        ECPublicKeySpec pubKey = new ECPublicKeySpec(params.getCurve().decodePoint(keybytes), params);
        KeyFactory kf = KeyFactory.getInstance("ECDH", "BC");
        return kf.generatePublic(pubKey);
    }

Exception

java.lang.IllegalArgumentException: Invalid point encoding 0x45
    at org.bouncycastle.math.ec.ECCurve.decodePoint(ECCurve.java:443)

Below method to create public key

public static (byte[] publicKey, byte[] privateKey) CreateKeyPair()
        {
            using (ECDiffieHellmanCng cng = new ECDiffieHellmanCng(
                // need to do this to be able to export private key
                CngKey.Create(
                    CngAlgorithm.ECDiffieHellmanP256,
                    null,
                    new CngKeyCreationParameters
                    { ExportPolicy = CngExportPolicies.AllowPlaintextExport })))
            {
                cng.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
                cng.HashAlgorithm = CngAlgorithm.Sha256;
                // export both private and public keys and return
                var pr = cng.Key.Export(CngKeyBlobFormat.EccPrivateBlob);
                var pub = cng.PublicKey.ToByteArray();
                return (pub, pr);
            }
        }

Public Key generated RUNLMSAAAAHddHI6TOEDG/Ka7naBbLQH0u/DSFfbKJI2w0WSoxrmFkwKm1tktz4wD0rqnwkZp8FwdHJ+8OVrTcpDMmxrwvS6

The key which I am receiving at java is of 72 bytes. But I think bouncy castle java supports 64 bytes of key.

I was also looking into this but did not get any help


Solution

  • The C# code exports the public key as a Base64 encoded EccPublicBlob whose format is described in the link given in the question:
    The first 4 bytes 0x45434B31 denote in little endian order a public ECDH key for curve P-256, the following 4 bytes are in little endian order the key length in bytes (0x20000000 = 32), the rest are the x and y coordinates of the EC point i.e. the public key, 32 bytes each.
    It is striking that in the key you posted, the second 4 bytes are 0x20000001, but the x and y coordinates are 32 bytes each. Possibly there is a copy/paste error here. Anyway, with the posted C# code, I cannot reproduce a key that has a value other than 0x20000000 in the second 4 bytes.

    Java/BC does not directly support importing an EccPublicBlob (which is MS proprietary), but it does support importing an uncompressed public key. This results when the x and y coordinates are concatenated and 0x04 is used as prefix. The import with Java/BC is then possible as follows:

    import java.security.KeyFactory;
    import java.security.PublicKey;
    import java.security.interfaces.ECPublicKey;
    import java.security.spec.ECPoint;
    import java.security.spec.ECPublicKeySpec;
    import org.bouncycastle.jce.ECNamedCurveTable;
    import org.bouncycastle.jce.ECPointUtil;
    import org.bouncycastle.jce.provider.BouncyCastleProvider;
    import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
    import org.bouncycastle.jce.spec.ECNamedCurveSpec;
    ...
    public static PublicKey getPubKeyFromCurve(byte[] uncompRawPubKey, String curveName) throws Exception {
          ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec(curveName);
          ECNamedCurveSpec params = new ECNamedCurveSpec(spec.getName(), spec.getCurve(), spec.getG(), spec.getN());
          ECPoint point = ECPointUtil.decodePoint(params.getCurve(), uncompRawPubKey);
          ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point, params);
          KeyFactory kf = KeyFactory.getInstance("ECDH", new BouncyCastleProvider());
          ECPublicKey pubKey = (ECPublicKey) kf.generatePublic(pubKeySpec);
          return pubKey;
    }
    

    Test (assuming EccPublicBlob is Base64 encoded like the posted one):

    import java.util.Base64;
    ...
    String publicKeyBlob = "RUNLMSAAAAAFzw4IGY4N8PKVt0MGF38SAKU5ixJhptVUdrWzuPhFDOcj/2k4SlGRN1RpRMbar9Iu7Uvcx7Vtm8Wa0HSzWJdE";
    byte[] rawPublic = new byte[65];
    rawPublic[0] = 0x04;
    System.arraycopy(Base64.getDecoder().decode(publicKeyBlob), 8, rawPublic, 1, 64);
    PublicKey pub = getPubKeyFromCurve(rawPublic, "P-256");
    System.out.println(Base64.getEncoder().encodeToString(pub.getEncoded())); // MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBc8OCBmODfDylbdDBhd/EgClOYsSYabVVHa1s7j4RQznI/9pOEpRkTdUaUTG2q/SLu1L3Me1bZvFmtB0s1iXRA==
    

    The test imports the EccPublicBlob and exports it as a Base64 encoded DER key in X.509/SPKI format. This can be read with an ASN.1 parser, e.g. https://lapo.it/asn1js/, and thus be verified.


    Note that C# also supports the export of other formats. However, this depends on the version. E.g. as of .NET Core 3.0 there is the method ExportSubjectPublicKeyInfo() that exports the public key in X.509/SPKI format, DER encoded. This format and encoding can be imported directly into Java using X509EncodedKeySpec (even without BouncyCastle).
    In other versions of C#, BouncyCastle for C# can be used for the export, which also supports the X.509/SPKI format.
    Since you didn't post your .NET version, it's unclear what specific alternatives exist for you.


    Keep in mind that an ECDH key for P-256 can also be created more simply with:

    ECDiffieHellmanCng cng = new ECDiffieHellmanCng(ECCurve.NamedCurves.nistP256)
    

    or cross-platform with

    ECDiffieHellman ecdh = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256)