javacryptographyelliptic-curvejava-security

Verify that ECPoint is valid on EllipticCurve object given x y coordinates and curve name


Given the x and y coordinates of a public key and a curve name, I need to determine whether those coordinates represent a valid point on the curve or not. If yes, test passed. If no, test failed.

My code so far is:

                    String curve = (String) testGroupHeaders.get("curve");
                    String curve_num = curve.split("-")[1];
                    String specName = "secp" + curve_num + "r1";
                        String qx = (String) testsData.get("qx");
                        String qy = (String) testsData.get("qy");
                        BigInteger x = new BigInteger(qx, 16);
                        BigInteger y = new BigInteger(qy, 16);
                        ECPoint ecPoint = new ECPoint(x, y);
                        if (ecPoint.equals(ECPoint.POINT_INFINITY)) {
                            testResultsObject.put("testPassed", false);
                        } else {
                            try {
                                AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC");
                                parameters.init(new ECGenParameterSpec(specName));
                                ECParameterSpec ecParameters = parameters.getParameterSpec(ECParameterSpec.class);
                                EllipticCurve ellCurve = ecParameters.getCurve();
                                ECPublicKeySpec keySpec = new ECPublicKeySpec(ecPoint, ecParameters);
                                KeyFactory keyFactory = KeyFactory.getInstance("EC");
                                ECPublicKey publicKey = (ECPublicKey) keyFactory.generatePublic(keySpec);
                                testResultsObject.put("testPassed", true);
                            } catch (Exception e) {
                                System.out.println(e);
                                testResultsObject.put("testPassed", false);
                            }

My hope was that the construction of the public key would fail if the points were invalid, but this code still results in the tests passing when inputting known invalid points.

Any help would be greatly appreciated.

I found this question How to determine whether EC Point is on curve? which has a vague answer:

"There might be a more straightforward way but if you've got the EllipticCurve object, you can always just substitute the coordinates of your point into the equation of the curve."

This could be a potential solution, but I am unsure of how to implement it. I tried:

EllipticCurve ellC = publicKey.getParams().getCurve();

in the hopes that it would fail to construct a curve, but alas it still succeeds.

Note that BouncyCastle is not an option for me, I must use java.security provider.

I also found this answer Elliptic curve point

But I was not able to get anything out of that either.

EDIT

Okay, I'm very close with this code after adapting from the Bouncy Castle code in the last linked answer above:


                                EllipticCurve ellCurve = ecParameters.getCurve();
                                BigInteger a = ellCurve.getA();
                                BigInteger b = ellCurve.getB();
                                ECField ecField = ellCurve.getField();
                                ECFieldFp fp = (ECFieldFp) ecField;
                                BigInteger p = ((ECFieldFp) ecField).getP();
                                BigInteger rhs = x.multiply(x).multiply(x).add(a.multiply(x)).add(b).mod(p);
                                BigInteger lhs = y.multiply(y).mod(p);
                                System.out.println(rhs);
                                System.out.println(lhs);

This is working for most cases, except for this particular case with secp521r1:


          {
            "tcId": 8,
            "qx": "02ED3613D77D9096DC0545F3EEC44270762BF52E63044D29FC880556114F4FC176DBE20A9BC717C58FA63BB3308D3136355A072704C0B8BE9A6B3CFFFE36467722C0",
            "qy": "00E857AE0D11FB1B79AC9531980A3E3BD1AC9E457E39A107D0AF5C9E09F2D6243F31C697C74CFD6A80F1CA5FCDA5950754DCC8724B9B21ED3A6705EBA1B9D2926B8F"
          },

When I apply the above code and compare lhs to rhs, I get the same output which indicates to me that this is a valid point on the curve.

rhs: 1634177850809752323211745106139613474061093186782816308703279366414782386677365518237753355552098931968864191666289807321598625802278923850729659158219971712
lhs: 1634177850809752323211745106139613474061093186782816308703279366414782386677365518237753355552098931968864191666289807321598625802278923850729659158219971712

So there must be a different check that I'm missing that indicates this as a failure.


Solution

  • Figured out the answer, posting it here for myself later or anyone else who runs into this. In addition to the edit in the main post, I was missing a check if the affine x and y were in the range [0, p-1]. Reference: https://neilmadden.blog/2017/05/17/so-how-do-you-validate-nist-ecdh-public-keys/

    Here is the completed working code without bouncy castle:

                        String curve = (String) testGroupHeaders.get("curve");
                        String curve_num = curve.split("-")[1];
                        String specName = "secp" + curve_num + "r1";
                        JSONArray tests = (JSONArray) testGroupHeaders.get("tests");
                        JSONArray testResultsArray = new JSONArray();
                        for (int k = 0; k < tests.size(); k++){
                            JSONObject testResultsObject = new JSONObject();
                            JSONObject testsData = (JSONObject) tests.get(k);
                            long tcId = (long) testsData.get("tcId");
                            testResultsObject.put("tcId", tcId);
                            String qx = (String) testsData.get("qx");
                            String qy = (String) testsData.get("qy");
                            BigInteger x = new BigInteger(qx, 16);
                            BigInteger y = new BigInteger(qy, 16);
                            ECPoint ecPoint = new ECPoint(x, y);
                            if (ecPoint.equals(ECPoint.POINT_INFINITY)) {
                                testResultsObject.put("testPassed", false);
                            } else {
                                try {
                                    AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC");
                                    parameters.init(new ECGenParameterSpec(specName));
                                    ECParameterSpec ecParameters = parameters.getParameterSpec(ECParameterSpec.class);
                                    EllipticCurve ellCurve = ecParameters.getCurve();
                                    BigInteger a = ellCurve.getA();
                                    BigInteger b = ellCurve.getB();
                                    ECField ecField = ellCurve.getField();
                                    BigInteger p = ((ECFieldFp) ecField).getP();
                                    BigInteger rhs = x.multiply(x).multiply(x).add(a.multiply(x)).add(b).mod(p);
                                    BigInteger lhs = y.multiply(y).mod(p);
    
                                    // Do this part to try and generate an exception
                                    ECPublicKeySpec keySpec = new ECPublicKeySpec(ecPoint, ecParameters);
                                    KeyFactory keyFactory = KeyFactory.getInstance("EC");
                                    ECPublicKey publicKey = (ECPublicKey) keyFactory.generatePublic(keySpec);
    
                                    BigInteger affineX = publicKey.getW().getAffineX();
                                    BigInteger affineY = publicKey.getW().getAffineY();
                                    if (affineX.compareTo(BigInteger.ZERO) < 0 || affineX.compareTo(p) >= 0
                                            || affineY.compareTo(BigInteger.ZERO) < 0 || affineY.compareTo(p) >= 0) {
                                        testResultsObject.put("testPassed", false);
                                    } else {
                                        if (rhs.equals(lhs)) {
                                            testResultsObject.put("testPassed", true);
                                        } else {
                                            testResultsObject.put("testPassed", false);
                                        }
                                    }
                                } catch (Exception e) {
                                    testResultsObject.put("testPassed", false);
                                }
    

    Note that this is just POC code and could be cleaned up/optimized quite a bit. All of the .get calls are coming from the JSON requests that I am parsing, so how you get the String values/inputs may vary.